數組是C內建的基本數據結構,數組表示法和指針表示法緊密關聯。一種常見的錯誤認識是數組和指針完全可以互換,盡管數組名字有時可以當做指針來用,但數組的名字不是指針。數組和指針的區別之一就是,盡管數組的名字可以返回數組地址,但是名字不能作為賦值操作的目標。
數組是能用索引訪問的同質元素連續集合。數組的元素在內存中是相鄰的,而且元素都是同一類型的。數組的長度是固定的,realloc函數和變長數組提供了應對長度需要變化的數組的技術。
一維數組是線性結構,用一個索引訪問成員。取決於不同的內存模型,數組的長度可能會有不同。
int vector[5];
數組的名字只是引用了一塊內存,對數組使用sizeof操作符會得到為該數組分配的字節數。要知道數組的元素數,只要對其除以元素長度即可。
int vector[5]; printf("vector is %p\nsize is %d\n", &vector, sizeof(vector)); //vector is 0xbffb8c08 //size is 20
可以用一個塊語句初始化一維數組。
int vector[5] = {1,2,3,4,5};
聲明一個二維數組。可以將二維數組當做數組的數組,只使用一個下標訪問二維數組得到的是對應行的指針。
int matrix[2][3] = {{1,2,3},{4,5,6}}; printf("0 is %p\n1 is %p\n", &matrix[0], &matrix[1]); //0 is 0xbf984044 //1 is 0xbf984050
可以看到,兩個地址剛好差12個字節,也就是matrix數組一行的長度。
把數組地址賦給指針。
int* pv = vector;
注意這種寫法和&vector[0]是等價的,不同於&vector是返回的整個數組的指針。一個是數組指針,一個是整數指針,指針類型不同。pv實際代表的是一個地址,*(pv+i)的寫法等價於pv[i],前者稱作指針表示法,後者稱作數組表示法。
printf("pv[0] is %d\n*(pv+1) is %d\n ",pv[0],*pv); //pv[0] is 1 //*(pv+1) is 1
pv指針包含一個內存塊的地址,方括號表示法會取出pv中包含的地址,用指針算數運算符把索引i加上,然後接引新地址返回其內容。給指針加上一個整數實際是給它加了整數與數據類型長度的乘積,這一點對數組名字也同樣適用,*(pv+1)和*(vector+1)是等價的。因此數組表示法可以理解為“偏移並解引”操作。vector[i]和*(vector+i)兩種方法結果相同,僅僅在實現上略有差別,可以忽略。注意指針可以作為左值,但是數組名字不可以作為左值使用,vector=vector+1是錯誤的寫法。
如果從堆上分配內存並把地址賦給一個指針,那就肯定可以對指針使用下標並把這塊內存當做一個數組。用malloc創建的已有數組的長度可以通過realloc函數來調整。C99支持變長數組,但是變長數組只能在函數內部聲明。所以如果數組的生命周期需要比函數長,或者你沒有使用C99,那就只能使用realloc。
下面這個函數接受用戶的輸入,並且使用realloc函數動態的申請所需的內存,按回車結束輸入。
char* getLine(void) { const size_t sizeIncrement = 10; char* buffer = malloc(sizeIncrement); char* currentPosition = buffer; size_t maximumLength = sizeIncrement; size_t length = 0; int character; if(currentPosition == NULL){return NULL;} while(1) { character = fgetc(stdin); if(character == '\n'){break;} if(++length >= maximumLength) { maximumLength += sizeIncrement; char* newBuffer = realloc(buffer,maximumLength); if(newBuffer == NULL){free(buffer); return NULL;} currentPosition = newBuffer + (currentPosition - buffer); buffer = newBuffer; } *currentPosition++ = character; } *currentPosition = '\0'; printf("buffer is %s\n", buffer); return buffer; } getLine();
將一維數組作為參數傳遞給函數實際是通過值來傳遞數組的地址,我們需要告訴函數數組的長度,否則函數只有一個地址,不知道數組到底有多長。對於字符串來說,可以依靠NUL字符來判斷其長度。對於有些數組則無法判斷。
聲明一個指向數組的指針和指針數組、數組指針是不同的。指向數組的指針是一個指針指向數組下標為0的元素,指針數組是一個元素為指針的數組,數組指針是數組類型的指針。
int vector[2] = {1,2}; int* pv1 = vector;//指向數組的指針 int* pv2[2];//指針數組,可以用一個循環來為每個指針分配內存 int (*pv3)[2];//數組類型的指針
現在再來區分一下數組表示法和指針表示法在指針數組的應用。
int* array[5]; array[0] = (int*) malloc (sizeof(int)); *array[0] = 100; *(array+1) = (int*) malloc (sizeof(int)); **(array+1) = 200;
在這裡array[1]和*(array+1)是等價的,實際都是指針類型,使用malloc函數為指針在堆上分配內存,解引指針來為數據賦值,因此**(array+1)其實不難理解,一個*解引得到一個指針,對指針再解引才得到數據,而[]括號前面已經 解釋過,就是相當於一個取地址和加索引的操作。當然,還可以使用array[0][0]來代替*array[0]。
可以將多維數組的一部分看做子數組,比如二維數組的每一行當做一個一維數組。數組按行-列順序存儲,第二行的第一個元素的內存地址緊跟在第一行最後一個元素後面。
int matrix[2][3] = {{1,2,3},{4,5,6}}; int (*pmat)[3] = matrix;//3是二維數組的列數 printf("size of pmat[0] is %d\n", sizeof(pmat[0])); //size of pmat[0] is 12
可以看到,該數組指針的第一個元素的長度是12個字節,也就是說是第一行的長度。如果要訪問第一行第一個元素,需要用pmat[0][0]來訪問。array[i][j]等於 array+i*sizeof(row) + j* sizeof(element)。sizeof(row)是一行的總大小,sizeof(element)是單個元素的大小,array是array[0][0]的地址。
給函數傳遞數組參數時,要考慮如何傳遞數組的維數以及每一維度的大小。下面是兩種傳遞二維數組的方法:
void display2DArray(int arr[][3], int rows){} void display2DArray(int (*arr)[3], int rows){}
兩種方法都指定了行數。如果傳遞的數組維度超過了二維,除了一維的部分,需要指定其它維度的長度。如果傳遞一個array3D[3][2][4]的數組:
void display3DArray(int (*arr)[2][4], int rows){}
當使用malloc分別為二維數組的不同子數組分配內存時,可能導致內存分配不連續的問題。使用塊語句一次性初始化不會有這個問題。使用malloc來為二維數組分配連續的內存有兩種策略。假設二維數組有三行四列。第一種一次性分配所有內存3*4*sizeof(element),然後使用的時候通過前面提到的如何訪問array[i][j]的方法手動計算要訪問的內存地址。第二種方法分為兩步。第一步先使用malloc分配一塊內存用來存放指向二維數組每一行的指針的指針。第二步為array[0][0]的指針分配所有內存,然後計算array[1][0]和array[2][0]的位置,分別給這兩個關鍵指針賦值,這樣就可以根據每一行的指針來使用下標訪問了。
int rows = 3; int columns = 5; //第一種方法 int* matrixx = (int*) malloc (rows * columns * sizeof(int)); //第二種方法 int **matrixy = (int**) malloc (rows * sizeof(int*)); matrixy[0] = (int*) malloc (rows * columns * sizeof(int)); int i = 1; while(i<rows) { i++; matrixy[i] = matrix[0] + i * columns; }
不規則數組是每一行的列數不一樣的二維數組。可以使用復合字面量來初始化不規則數組。
(const int) {100} (int [3]) {10, 20, 30}
不規則數組的訪問和維護都比較麻煩,使用前應慎重考慮。