首页 > 代码库 > 二维数组与高级指针
二维数组与高级指针
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 }