聲明:該讀書筆記摘抄自《C和指針》——Kenneth A.Reek (著) 徐波(譯)。為了克服自己走馬觀花,提高閱讀和學習效率,決定将自己在讀書過程中遇到的一些知識點加以摘抄和總結備忘,在此感謝原書作者和翻譯。
一、數組名
1、一個誤區:數組名并不表示整個數組,它大多時候隻是一個指針常量,指向數組的第一個元素,它的類型取決于數組元素類型。這一點解釋了為什麼C語言的數組不能整體拷貝,隻能循環拷貝指派。如果你将數組名指派給另一個數組名,例如,int a[10] ; intb[10]; a = b; ,就已經錯的一塌糊塗:
1)數組名是一個常量指針,它不可以被指派;
2)就是上面提到的數組不能整體拷貝,因為你得到的僅僅是一個指向數組第一個元素的指針常量而已。
注:個人認為這兩點其實本質上是一個意思。即,假如數組名不是一個指針常量而是表示整個數組,那麼它可以整體指派。
2、數組名大多時候是一個常量指針,但是有且隻有兩種情況,數組名不用指針常量來表示:
1)作為sizeof的表達式的時候,數組名表示整個數組,sizeof會傳回整個數組占用的位元組數;
2)當數組名作為操作符&的操作數時,會産生一個指向該數組的指針。
二、數組的下标引用
1、當用下标來通路數組時,下标值可以為任意整數值,可以為負,但是程式員自己必須保證通路的有效性,即不能跳出數組的前邊界;下标值也可以大于數組實際長度,這通常用來實作不對稱邊界,但是要避免對超出數組有效長度的位址進行通路(隻要位址,不要值)。
2、C語言并不會對數組下标有效性進行安全檢查,C語言标準也并沒有規定編譯器必須對數組下标進行有效性檢查,對數組下标的有效性檢查會大大增加程式運作時間和空間的開銷。也就是說保證數組通路不越界的工作應該交由程式員來做。
3、a[2] 和2[a] 都是合法的,并且具有相同的效果,這緣于C語言實作下标的方式,因為在C語言中對下标的通路都可以轉換為對等的間接通路表達式。a[2] 、2[a]都會被編譯器轉換為*(2 + a),這兩種下标書寫形式對編譯器來說并無差異。當然,如果你不是在參加混亂代碼大賽,在代碼可讀性上沒有人會喜歡2[a]這種書寫方式。
三、指針與下标
1、加入下标和指針都可以實作某種需要,下标一定不會比指針更有效率,并且有時候指針比下标更有效率。
2、指針比下标更有效率的場合:當在數組中循環變量一次以固定步長移動時,固定數字與固定數字的相乘是在編譯期間完成的,不會占用運作時間,更有效率一些。
3、一個高效率的數組的拷貝, 将數組y的元素賦給數組x
#define SIZE 10
int x[size], y[size] ;
void array_to_array(void)
{
register int *p1, *p2;
for (p1=x, p2=y; p1<&x[SIZE]; ) {
*p1++ = *p2++;
}
}
四、數組和指針
1、數組和指針都具有指針值,都可以進行間接通路和下标引用操作,但是需要注意的是他們并不等價。聲明一個數組時,編譯器首先根據數組元素個數為該數組配置設定記憶體,然後再建立數組名,它是一個指針常量,指向數組的第一個元素。聲明一個指針變量時,編譯器隻為該變量配置設定4個位元組的記憶體。
2、當一個數組名作為參數傳遞給一個函數時,該數組名退化為指針,此時,在函數内部對其sizeof時,得到的結果是4(32位機器),
3、編譯器允許數組不完整初始化,但隻允許順序省略後面的初始值,被省略的初始值全部為0;例如,int a[10] = {1,2}; a[0] = 1, a[1] = 2, a[2]~a[9]全部為0。另外,對于全局數組,預設初始值都是0, 但是如果對于一個局部數組,并且所有元素都沒有初始化時,其所有元素初始值都是不确定的。
4、在數組聲明時,允許編譯器自動計算數組長度,例如,int a[ ] = {1, 2, 3, 4, 5};
5、字元數組的初始化,
方法1、char a[ ] = {'h', 'e', 'l', 'l', o'', '\0'}; 隻适用于較短的字元數組初始化
方法2、char a[ ] = "hello"; 注意這種初始化方法與字元串常量的差別:當它用于初始化一個字元數組時,它就是一個字元數組的初始化清單,其他的任何情況都是字元串常量。
五、多元數組
1、int a[3][4]; 可以将a看作一個具有三個元素的一維數組,隻不過每個元素又是一個具有4個元素的一位數組;
同樣,int a[2][3][4];将a看作一個具有兩個元素的一維數組,其中每個元素又是一個具有三個元素的一維數組,而這三個元素的每一個又是一個具有4個元素的一維數組。可見,多元數組可以看作是很多一維數組的嵌套集合。
2、多元數組在記憶體中的存儲順序
在C語言中,多元數組元素的存儲順序按照最右邊的下标率先變化,這稱為行主序。
3、指向數組的指針
例如:int a[3][4], *(p)[4] = a; 此時p指向該二維數組的第一行,p+1指向第二行,每一行是一個具有4個元素的數組,此時p就是一個指向數組的指針。
特别注意:若p初始化為int *p = a; 則是錯的,因為p是一個指向整型的指針,而二維數組名a是一個指向二維數組第一行的一維數組的指針常量,也就是說a是一個指向數組的指針,相當于二級指針,用一個二級指針初始化一個一級指針,顯然不合适。此時應該使用行指針變量,即int (*p)[4]才正确,要注意,除了第一維聲明為指針p外,其他各維必須明确用下标指定。 若将行指針變量定義為int (*p)[ ]; 此時p仍然是一個指向一個一維數組的行指針變量,但是此時一維數組的長度卻沒有指定,于是當p指針參與運算時,它指向的數組将會被視為空數組,即數組長度為0,計算指針p的步長時将與0相乘,這種情況可能不是你想要的,應該避免。
4、多元數組作為函數參數
例如有一個二維數組: int a[3][4];
a作為參數傳遞給函數: func(a);
函數func的聲明形式為: void func(int p[ ][4]) 或者void func(int (*p)[4])
注:要差別void func(int **p) ,這種聲明形式是錯誤的,p是一個真正的二級指針,即p被聲明為一個指向整型的指針的指針。
5、多元數組的初始化
原則:按照最右邊的下表率來變化。
6、多元數組的長度自動計算
注意,在多元數組中隻有第一維的長度才能自動計算,其餘各維必須顯示指定。
七、指針數組
下表優先級高于間接通路符優先級
以上為第八章“數組和指針”摘抄總結,未完待續。。。