1 二維數組
多維數組即數組維數不止1個。例如,可用如下兩種方式聲明二維數組:
1. char Lion[3][5]; 2. typedef char Animal[5]; Animal Tiger[3]; |
Lion或Tiger可視為包含3個元素的一維數組,只不過每個元素本身是個包含5個char型元素的一維數組。亦即,二維數組是個一維數組的一維數組(C語言實際只支持“數組的數組”)。
多維數組的元素存儲順序按照最右邊的下標率先變化且一行存滿換至下一行的原則,稱為行主序(Row Major Order)。對於數組 int arr[row][col],編譯器按照(arr + i*col + j)的尋址方式訪問數組元素arr[i][j]。
2 高級指針
2.1 二級指針
int i = 5; int *pi = &i; int **ppi = π |
ppi表示指向一個指向整數的指針的指針,在32位平台下占用4字節空間。
二級指針常用於鏈表操作中,間接訪問需要修改的變量內存(此時函數作用域內變量名不可見)。
2.2 數組指針
數組指針也稱指向一維數組的指針,亦稱行指針(對於二維數組)。
int a[3][5]; int (*p)[5]; p = a; //將該二維數組的首地址賦給p,也就是a[0]或&a[0][0] p++; //執行該句后,p跨過5個整型數據的長度(即行a[0][]),指向行a[1][] |
指針p指向包含5個元素的一維整型數組或每行包含5個元素的二維整型數組。 示例中p指向二維數組a,該二維數組的列數為5或分解為一維數組的長度為5。此時p的增量以它所指向的一維數組長度為單位,如p+i指向二維數組a的i行的起始地址,*(p+2)+3表示a數組2行3列元素地址(第一行為0行,第一列為0列),*(*(p+2)+3)表示a[2][3]的值。
2.3 指針數組
int *p[5]; |
下標引用([])優先級高於間接訪問(*),故p是個數組,其元素是5個指向整型的指針,占有多個指針變量的存儲空間。
指針數組常用於長度不等的字符串表,如:
char const *KeyWord[] = { "do", "for", "if", "return", NULL }; |
若用二維數組表示KeyWord則效率較低,因為每行的長度被固定為剛好能容納最長的關鍵字。
以上三種形式均可用形如p[1][2]的方式訪問二維數組內容,盡管表示方法和意義有所不同:
定義 |
元素 |
訪問 |
int Arr[3] = {7,8,9}; int *Ptr = Arr; |
一維數組第i個元素 |
*(p+i)、p[i] 如:Ptr[1], *(Ptr +1) |
int Arr[2][3] = {{1,2,3}, {4,5,6}}; int *PtrArr[2] = {Arr[0], Arr[1]}; int (*ArrPtr)[3] = Arr; int **PtrPtr = PtrArr; |
二維數組第i行j列元素 |
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j] 如:PtrArr[1][2]、ArrPtr[1][2]、PtrPtr[1][2]等 |
注: 1. 對一維數組a而言,&a和a都指向數組的起始地址,但其類型不同。&a是一個指向數組的指針,類型為int (*)[5],以整個數組為單位取其地址。而a是數組的首地址即&a[0]。a+1=a+sizeof(a[0])指向數組下一元素的地址,即a[1];&a+1則偏移sizeof(a)個字節,指向下一個對象(數組)的首地址,即a[sizeof(a)]。 2. 對二維數組a而言, a+i,a[i],&a[i],*(a+i),&a[i][0]均表示i行(一維數組a[i])首地址;a[i]+j、&a[i][j]則表示i行j列元素首地址。 |
下圖給出三種形式操作二維數組時的指向關系:
使用指針比數組下標更快,但更易出錯。
2.4 動態分配二維數組
上述動態分配生成的二維數組訪問時與普通二維數組一樣,如 PtrPtr[1][3]。

1 #define FIR_DIM_SIZE 2 2 #define SEC_DIM_SIZE 5 3 void SecDimKnown(int iFirDimSize){ //已知第二維 4 char (*ArrPtr)[SEC_DIM_SIZE]; //指向數組的指針 5 ArrPtr = (char (*)[SEC_DIM_SIZE])malloc(sizeof(char*) * iFirDimSize); 6 printf("S(ArrPtr)=%d\n", sizeof(ArrPtr)); //4,指針 7 printf("S(ArrPtr[0])=%d\n", sizeof(ArrPtr[0])); //SEC_DIM_SIZE,一維數組 8 free(ArrPtr); 9 } //若返回ArrPtr則函數聲明應為char (*ArrPtrFunc(int))[SEC_DIM_SIZE],調用方式如char (*Ptr)[5] = ArrPtrFunc(2)。 10 void FirDimKnown(int iSecDimSize){ //已知第一維 11 char *PtrArr[FIR_DIM_SIZE]; //指針的數組 12 int i; 13 for(i = 0; i < FIR_DIM_SIZE; i++){ 14 PtrArr[i] = (char *)malloc(sizeof(char) * iSecDimSize); 15 } 16 printf("S(PtrArr)=%d\n", sizeof(PtrArr)); //4*FIR_DIM_SIZE,指針數組 17 printf("S(PtrArr[0])=%d\n", sizeof(PtrArr[0])); //4,指針 18 for(i = 0; i < FIR_DIM_SIZE; i++){ 19 free(PtrArr[i]); 20 } 21 } 22 //已知第一維,一次分配內存(保證內存的連續性) 23 void FirDimKnownOnceAlloc(int iSecDimSize){ 24 char *PtrArr[FIR_DIM_SIZE]; //指針的數組 25 PtrArr[0] = (char *)malloc(sizeof(char) * FIR_DIM_SIZE * iSecDimSize); 26 int i; 27 for(i = 1; i < FIR_DIM_SIZE; i++){ 28 PtrArr[i] = PtrArr[i-1] + iSecDimSize; 29 } 30 printf("S(PtrArr)=%d\n", sizeof(PtrArr)); //4*FIR_DIM_SIZE,指針數組 31 printf("S(PtrArr[0])=%d\n", sizeof(PtrArr[0])); //4,指針 32 free(PtrArr[0]); 33 } 34 void NeitherDimKnown(int iFirDimSize, int iSecDimSize){ //兩維均未知 35 char **PtrPtr = (char **)malloc(sizeof(char*) * iFirDimSize); //分配指針數組 36 int i; 37 for(i = 0; i < iFirDimSize; i++){ //分配每個指針所指向的數組 38 PtrPtr[i] = (char *)malloc(sizeof(char) * iSecDimSize); 39 } 40 printf("S(PtrPtr)=%d\n", sizeof(PtrPtr)); //4,指針 41 printf("S(PtrPtr[0])=%d\n", sizeof(PtrPtr[0])); //4,指針 42 for(i = 0; i < iFirDimSize; i++){ 43 free(PtrPtr[i]); 44 } 45 free(PtrPtr); 46 } 47 //兩維均未知,一次性分配所有空間(保證內存的連續性) 48 void NeitherDimKnownOnceAlloc(int iFirDimSize, int iSecDimSize){ 49 char **PtrPtr = (char **)malloc(sizeof(char*) * iFirDimSize);//分配指針數組 50 PtrPtr[0] = (char *)malloc(sizeof(char) * iFirDimSize * iSecDimSize); 51 int i; 52 for(i = 1; i < iFirDimSize; i++){ 53 PtrPtr[i] = PtrPtr[i-1] + iSecDimSize; //分配每個指針所指向的數組 54 } 55 printf("S(PtrPtr)=%d\n", sizeof(PtrPtr)); //4,指針 56 printf("S(PtrPtr[0])=%d\n", sizeof(PtrPtr[0])); //4,指針 57 free(PtrPtr[0]); 58 free(PtrPtr); 59 }
注意,malloc/free需配對使用,即調用malloc和free函數的次數必須相同,否則將產生內存泄漏。
3 靜態數組作為形參
3.1 一維數組形參
一維數組名的值就是一個指向數組第1個元素的指針常量,故傳遞給被調函數的是該指針的一個副本(傳值調用)。被調函數可通過下標引用對該指針副本執行間接訪問操作,進而訪問和修改主調函數內的數組元素(但不會修改主調函數的指針實參本身)。
以下兩種函數原型等價:
int GetStrLen(char *pStr); int GetStrLen(char pStr[]); //實參為指針,故sizeof(pStr)是指向字符的指針(而非字符數組)長度 |
函數原型中的一維數組形參無需寫明元素數目,因為編譯器並不為數組形參分配內存空間。形參只是指向實參數組第1個元素(亦即數組首地址)的指針,並不包含數組長度信息。因此,若被調函數內需要知道數組長度,必須將其作為一個顯式參數傳入。
3.2 二維數組形參
二維數組名的值是一個指向數組第1行元素的指針常量,每個元素本身是另外一個數組。
二維數組作為函數形參時,一般需要指明第二維長度(以便編譯器正確尋址);若未給出第二維長度或該長度不定,則可將其作為指針傳遞,利用數組的線性存儲特性,在函數體內手工轉化為對指定元素的訪問。
以下給出合法的聲明和調用方式:

1 void Func0(int Arr[2][3]){ //實際上第一維長度可為任意整數 2 printf("0Arr[1][2] = %d\n", Arr[1][2]); 3 } 4 void Func1(int Arr[][3]){ //int Arr[2][]和int Arr[][]均不合法 5 printf("1Arr[1][2] = %d\n", Arr[1][2]); 6 } 7 void Func2(int (*pArr)[3]){ 8 printf("2Arr[1][2] = %d\n", pArr[1][2]); 9 } 10 void Func3(int *pArr, int iCol){ 11 printf("pArr[1][2] = %d\n", pArr[1*iCol + 2]); 12 } 13 void Func4(int **ppArr, int iCol){ 14 printf("ppArr[1][2] = %d\n", *((int*)ppArr + iCol*1 + 2)); 15 } 16 17 int main(void){ 18 int Arr[2][3] = {{1,2,3}, {4,5,6}}; 19 Func0(Arr); 20 Func1(Arr); 21 Func2(Arr); 22 Func3((int*)Arr, 3); //強制類型轉換,以避免編譯警告 23 Func4((int**)Arr, 3); 24 return 0; 25 }