天天看點

指針是c語言的靈魂,[轉]C語言靈魂——指針

指針與數組1(c缺陷與陷阱3.1節)

c語言中的數組值得注意的地方有以下兩點:

1、c語言中隻有一維數組,而且數組的大小必須在編譯期間就作為一個常數确定下來(C99标準允許變長數組,GCC編譯器中實作了變長數組)。然而,c語言中數組的元素可以是任何類型的對象,當然也可以是另外一個數組。這樣,要仿真出一個多元數組就不是一件難事。

2、對于一個數組,我們隻能夠做兩件事:确定該數組的大小,以及獲得指向該數組下标為0的元素的指針。其他有關數組的操作,哪怕它們乍看上去是以數組下标進行運算的,實際上都是通過指針進行的。換句話說,任何一個數組下标運算都等同于一個對應的指針運算,是以我們完全可以依據指針行為定義數組下标的行為。

現在考慮下面的例子:

int i;

int *p;

int calendar[12][31];

上面聲明的calendar是一個數組,該數組擁有12個數組類型的元素,其中的每個元素都是一個擁有31個整型元素的數組。是以,sizeof(calendar)的值是:31×12×sizeof(int)。

考慮一下,calendar[4]的含義是什麼?因為calender是一個有着12個數組類型元素的數組,它的每個數組類型元素又是一個有着31個整型元素的數組,是以calendar[4]是calendar數組的第5個元素,是calendar數組中12個有着31個整型元素的數組之一。是以,calendar[4]的行為也表現為一個有着31個整型元素的數組的行為。例如,sizeof(calendar[4])的結果是:31×sizeof(int)。

又如,p = calendar[4];這個語句使指針p指向了數組calendar[4]中下标為0的元素。因為calendar[4]是一個數組,我們可以通過下标的形式來指定這個數組中的元素:i = calendar[4][7],這個語句也可以寫成下面這樣而表達的意思保持不變:i = *( calendar[4] + 7 ),還可以進一步寫成:i = *( *( calendar + 4 ) + 7 )。

下面我們再看:p = calendar; 這個語句是非法的,因為calendar是一個二維數組,即“數組的數組”,在此處的上下文中使用calendar名稱會将其轉換為一個指向數組的指針。而p是一個指向整型變量的指針,兩個指針的類型不一樣,是以是非法的。顯然,我們需要一種聲明指向數組的指針的方法。

int calendar[12][31];

int (*monthp)[31];

monthp = calendar;

int (*monthp)[31] 語句聲明的 *monthp 是一個擁有31個整型元素的數組,是以,monthp就是一個指向這樣的數組的指針。monthp指向數組calendar的第一個元素。

指針與數組2(c和指針.P141.)

·1、數組的名的值是一個指針常量,不能試圖将一個位址指派給數組名;

·2、當數組名作為sizeof操作符的操作數時,sizeof(arrayname)傳回的是整個數組的長度,而不是指向數組的指針的長度;

·3、當數組名作為單目操作符&的操作數,取一個數組名的位址所産生的是一個指向數組的指針,而不是一個指向某個指針常量值的指針。

·4、指針和數組并不總是相等的。為了說明這個概念,請考慮下面這兩個聲明:

int a[5];

int *b;

a和b能夠互換嗎?它們都具有指針值,它們都可以進行間接通路和下标操作。但是,它們還是有很大的差別的:聲明一個數組時,編譯器将根據聲明所指定的元素數量為數組保留記憶體空間,然後再建立數組名,它的值是一個常量,指向這段空間的起始位置。聲明一個指針變量時,編譯器隻為指針本身保留記憶體空間,它并不為任何整型值配置設定記憶體空間。而且,指針變量并未被初始化為指向任何現有的記憶體空間,如果它是一個自動變量,它甚至根本不會被初始化。把這兩個聲明用圖的方法表示,可以發現它們之間存在顯著的不同:

a

b

是以,上述聲明後,表達式*a是完全合法的,但表達式*b卻是非法的。*b将通路記憶體中某個不确定的位置,或者導緻程式終止。另一方面,表達式b++可以通過編譯,但是a++卻不能,因為a的值是一個常量。

#include

int main()

{

//注意sizeof(num)的長度應該為10*4=40

int num[] = {0,1,2,3,4,5,6,7,8,9};

printf(" sizeof(num) = %d\n", sizeof(num) );

//注意sizeof(str)的長度應該為11,包括字元串後面的'\0'

char str[] = "0123456789";

printf(" sizeof(str) = %d\n", sizeof(str) );

//注意sizeof(str1)的長度應該為10,不包括字元串後面的'\0',但是,最好将字元串的最後一個字元設定為空

char str1[] =

{'0','1','2','3','4','5','6','7','8','9'};

printf(" sizeof(str1) = %d\n", sizeof(str1) );

//&num的類型為'int (*)[10]',表示的是一個指向長度為10的整形數組的指針

int (*ptoint)[10] = #

printf(" sizeof(ptoint) = %d, (*ptoint)[9] = %d\n", sizeof(ptoint),

(*ptoint)[9] );

//&str的類型為'char (*)[11]',表示的是一個指向長度為11的字元數組的指針,注意str數組的長度是11,而不是10

char (*ptostr)[11] = &str;

printf(" sizeof(ptostr) = %d, (*ptostr)[9] = %c\n", sizeof(ptostr),

(*ptostr)[9] );

//由于p指向的是數組num[5],是以對下标取負值後,不會超出數組的正常取值範圍

//該例子也說明了為什麼下标檢查在c語言中是一項困難的任務:下标引用可以作用于任意的指針,而不僅僅是數組名

//作用于指針的下标引用的有效性即依賴于該指針當時恰好指向什麼内容,也依賴于下标的值

int *p = num + 5;

printf(" p[-1] = %d, p[0] = %d, p[1] = %d \n", p[-1],p[0],p[1]

);

//下面的表達式中,'num[5]和5[num]'的值是一樣的,把它們轉換成對等的間接通路表達式,它們都等同于*(num + 2)

//'5[num]'這個古怪的表達式之是以可行,緣于C實作下标的方法。對編譯器來說,這兩種形式并無差别

//但是,決不應該編寫形如'5[num]'的表達式,因為它會大大的影響程式的可讀性

printf(" num[5] = %d, 5[num] = %d \n", num[5], 5[num]

);

getchar();

return 0;

}

輸出結果為:

指針是c語言的靈魂,[轉]C語言靈魂——指針

指針和數組的相同與不同(c專家程式設計.P199.)

在實際應用中,數組和指針可以互換的情形要比兩者不可互換的情形更為常見。讓我們分别考慮“聲明”和“使用”這兩種情況。聲明本身還可以進一步分為3種情況:

·外部數組的聲明;

·數組的定義(定義是聲明的一種特殊情況,它配置設定記憶體空間,并可能提供一個初始值);

·函數參數的聲明;

指針是c語言的靈魂,[轉]C語言靈魂——指針

也既是:作為函數參數時、在語句或表達式中使用數組時,我們可以采用數組或者指針的任何一種形式,除此之外的其他情況下,指針和數組不要互換。下面就數組和指針相同的情況做詳細的說明:

·規則1、表達式中的數組名被編譯器當作一個指向該數組第一個元素的指針。

假如我們聲明:

int a[10];

int *p = a;

就可以通過一下任何一種方式來通路a[i]:

p[i]

*( p + i )

*( a + i )

·····

事實上,可以采用的方法很多。對數組的引用如a[i] 在編譯時總是被編譯器改寫成*(a+i)的形式,C語言标準要求編譯器必須具備這個概念性的行為。

編譯器自動把下标值的步長調整到數組元素的大小。如果整型數的長度是4個位元組,那麼a[i+1]和a[i]在記憶體中的距離就是4。對起始位址執行加法操作之前,編譯器會負責計算每次增加的步長。這就是為什麼指針總是有類型限制,每個指針隻能指向一種類型的原因所在,因為編譯器需要知道對指針進行解除引用操作時應該取幾個位元組,以及每個下标的步長應取幾個位元組。

·規則2、下标總是和指針的偏移量相同。

把數組下标作為指針加偏移量是c語言從BCPL(C語言的祖先)繼承過來的技巧。在人們的正常思維中,在運作時增加對c語言下标的範圍檢查是不切實際的。因為取下标操作隻是表示将要通路該數組,但并不保證一定要通路。而且程式員完全可以使用指針來通路數組,進而繞過下标操作符。在這種情況下,數組下标範圍檢測并不能檢測所有對數組的通路的情況。事實上,下标範圍檢測被認為不值得加入到c語言當中。

還有一個說法是,在編寫數組算法時,使用指針比使用數組更有效率。這個頗為人們所接收的說法在通常情況下是錯誤的。使用現代的産品品質優化的編譯器,一維數組和指針引用所産生的代碼并不具有顯著的差别。不管怎樣,數組下标是定義在指針的基礎上,是以優化器常常可以把它轉化為更有效率的指針表達式,并生成相同的機器指令。

·規則3、在函數參數的聲明中,數組名被編譯器當作指向該數組第一個元素的指針。

在函數形參定義這個特殊情況下,編譯器必須把數組形式改寫成指向數組第一個元素的指針形式。編譯器隻向函數傳遞數組的位址,而不是整個數組的拷貝。這種轉換意味着在聲明函數的時候,以下三種形式都是合法的(同時無論實參是數組還是真的指針也都是合法的):

my_function( int *turnip ) {···}

my_function( int turnip[] ) {···}

my_function( int turnip[200] )

{···}

array_name和&array_name的異同

前者是指向數組中第一個元素的指針,後者是指向整個數組的指針。char a[MAX];//array

of MAX characters

char *p = a;

//p為指向數組的指針char *pa = &a;//該語句是不正确的,pa的類型為'char *',而&a的類型為'char (*)[MAX]'

char (*pb)[MAX] = &a;//該語句是正确的,pb的類型為'char

(*)[MAX]'

#include

void main()

{

char a[5] = {'a','b','c','d','\0'};

char *p = a;

//運作下面這句後,

vc6.0 提示的錯誤為:cannot convert from 'char (*)[5]' to 'char

*',&a的類型應該是指向一個數組的指針

//char *pa = &a;

//是以,應該定義一個指向相同類型和大小的數組的指針來獲得“&a”的值

char (*point_to_str)[5];

point_to_str = &a;

printf("%d\n%d\n",&p,

&point_to_str);

printf("%s\n%s\n",

p,

point_to_str);

}

運作結果為:

1245044

1245040

abcd

abcd