1. 多维数组与多维指针
1.1. 指向指针的指针
(1)指针的本质是变量,会占用一定的内存空间
(2)可以定义指针的指针来保存指针变量的地址值
(3)指针是个变量,同样也存在传值调用与传址调用
【实例分析】重置动态空间的大小
#include <stdio.h>
#include <malloc.h>
int reset(char**p,int size,int new_size)
{
int ret = 1;
int i = 0;
int len = 0;
char* pt = NULL;
char* pp = *p;
if((p != NULL)&&(new_size > 0))
{
pt = (char*)malloc(new_size);
len = (size < new_size)?size : new_size;//取较小者
//复制原内容
for(i=0; i<len;i++){
*pt = *pp++;
}
free(*p); //释放原空间
*p = pt; //*p指针指向新空间
}
else
{
ret = 0;
}
return ret;
}
int main(int argc,char* argv[], char* env[])
{
char*p =(char*)malloc(5);
printf("%p\n", p);//重置前二维数组的内存地址
//重置大小后二维数组的内存地址
//因为重置后的内存地址可以是新的地址,所以这里
//需传入p指针的地址,以便在reset函数内部改变p,让
//p指向新的地址。
if (reset(&p, 5, 3))
{
printf("%p\n", p);
}
free(p);
return 0;
}
1.2. 二维数组与二级指针
(1)二维数组在内存中以一维的方式排布
(2)二维灵敏组中的第一维是一维数组
(3)二维数组中的第二维才是具体的值
(4)二维数组的数组名可看做常量指针
#include <stdio.h>
//以一维数组的方式来遍历二维数组
void printfArray(int a[],int size)
{
int i = 0;
printf("printfArray:%d\n",sizeof(a)); //退化为指针
for(i=0; i<size; i++)
{
printf("%d\n",a[i]);
}
}
int main(int argc,char* argv[], char* env[])
{
int a[3][3] = {{0, 1, 2},{3, 4, 5},{6, 7, 8}};
int* p = &a[0][0];//指向a[0][0]元素
int i;
int j;
for(i = 0;i < 3; i++)
{
for(j = 0;j < 3;j++)
{
printf("%d, ",*(*(a + i) + j));//以指针方式访问元素
}
printf("\n");
}
printf("\n");
printfArray(p,9);//以一维数组的方式访问二维数组
return 0;
}
1.3. 数组名:
(1)一维数组名代表数组首元素的地址:int a[5];a的类型为int*
(2)二维数组名同样代表数组首元素的地址:如int a[3][5],a的类型为int(*)[5]。
#include <stdio.h>
int main(int argc,char* argv[], char* env[])
{
int a[3][5] = {0};
int c;
printf("Information for array:a[3][5]:\n");
printf("a = 0x%08X, a + 1 = 0x%08X, sizeof(a) = %d\n", a, a + 1, sizeof(a));
printf("&a = 0x%08X, &a + 1 = 0x%08X, sizeof(&a) = %d, sizeof(*&a) = %d\n",
&a, &a + 1, sizeof(&a),sizeof(*&a));
printf("\n");
//a[i]指向一个一维数组的首元素,a[i]+1指向该行第2个元素。sizeof(a[i])时不能看成首元素,而是这行整个一维数组
for(c=0;c< 5;c++)
{
printf("a[%d] = 0x%08X, a[%d] + 1 = 0x%08X, sizeof(a[%d]) = %d,\n",
c, a[c], c, a[c] + 1,c, sizeof(a[c]));
}
printf("\n");
//对a[i]进行&取地址符时,a[i]不能看作这一行的首元素,而是整个一维数组。即&a[i]表示第i+1的整个数组
//&a[i]+1表示下一行。
for(c=0;c< 5;c++)
{
printf("&a[%d] = 0x%08X, &a[%d] + 1 = 0x%08X, sizeof(&a[%d]) = %d, sizeof(*&a[%d]) = %d\n",
c, &a[c],c, &a[c] + 1,c, sizeof(&a[c]), c, sizeof(*&a[c]));
}
return 0;
}
/*
输出结果:
Information for array:a[3][5]:
a = 0x0023FE80, a + 1 = 0x0023FE94, sizeof(a) = 60
&a = 0x0023FE80, &a + 1 = 0x0023FEBC, sizeof(&a) = 4, sizeof(*&a) = 60
a[0] = 0x0023FE80, a[0] + 1 = 0x0023FE84, sizeof(a[0]) = 20,
a[1] = 0x0023FE94, a[1] + 1 = 0x0023FE98, sizeof(a[1]) = 20,
a[2] = 0x0023FEA8, a[2] + 1 = 0x0023FEAC, sizeof(a[2]) = 20,
a[3] = 0x0023FEBC, a[3] + 1 = 0x0023FEC0, sizeof(a[3]) = 20,
a[4] = 0x0023FED0, a[4] + 1 = 0x0023FED4, sizeof(a[4]) = 20,
&a[0] = 0x0023FE80, &a[0] + 1 = 0x0023FE94, sizeof(&a[0]) = 4, sizeof(*&a[0]) = 20
&a[1] = 0x0023FE94, &a[1] + 1 = 0x0023FEA8, sizeof(&a[1]) = 4, sizeof(*&a[1]) = 20
&a[2] = 0x0023FEA8, &a[2] + 1 = 0x0023FEBC, sizeof(&a[2]) = 4, sizeof(*&a[2]) = 20
&a[3] = 0x0023FEBC, &a[3] + 1 = 0x0023FED0, sizeof(&a[3]) = 4, sizeof(*&a[3]) = 20
&a[4] = 0x0023FED0, &a[4] + 1 = 0x0023FEE4, sizeof(&a[4]) = 4, sizeof(*&a[4]) = 20
*/
(3)二维数元素的访问方式:int a[i][j];
①a[i][j]
②*(*(a + i) + j);//a + i是第i+1行首元素(一维数组)的地址,即保存一个一维数的地址,*(a + i)取出当中保存的一维数组地址。注意这也
//是一个地址,而*(a + i)+j表示在这个一维数组地址基础上加j的偏移处,然后*(*(a + I)+ j)取出该元素出来。
【实例分析】如何动态申请二维数组
#include <stdio.h>
#include <malloc.h>
int** malloc2d(int row,int col)
{
int** ret = NULL;
if((row >0) && (col >0))
{
int* p = NULL;
//先申请第一维数组,用于存放行指针
ret = (int**)malloc(row * sizeof(int*));
//再申请整个数组存储空间的大小,用于存放所有元素
p = (int*)malloc(row * col * sizeof(int));
//将数组空间分为二维
if ((ret != NULL) && (p != NULL))
{
int i=0;
for(i=0; i<row; i++)
{
ret[i] = p + i*col;
}
}
else
{
free(ret);
free(p);
ret = NULL;
}
}
return ret;
}
void free2d(int** p)
{
if(*p != NULL) //p指向二维数组
{
//*p指向p[0],而这里保存的即是
//第1个元素的地址,也是整个数
//组元素空间地址。
free(*p); //释放元素所占空间
}
free(p);//释放行指针所占空间
}
int main()
{
int** a = malloc2d(3, 3);
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
printf("%d, ",a[i][j]);
}
printf("\n");
}
free2d(a);
return 0;
}
【实例分析】无法将指针变量本身传递给一个函数,须传递指针的地址
#include <stdio.h>
#include <malloc.h>
#include <string.h>
void GetMemory(char* p,int num)
{
//由于p是函数参数,当函数返回时,会被释放
p = (char*)malloc(num*sizeof(char));
}
void GetMemoryEx(char** p,int num)
{
//p指向一个指针,将该指针的值指向新的开辟的内存空间。
*p = (char*)malloc(num * sizeof(char));
}
int main()
{
char* str = NULL;
/*
//错误的做法
GetMemory(str,10);//试图让str让指向新开辟的内存空间。因为须改变
//指针str的值,所以得传递指针的地址过去。否则
//在传参时,GetMemory只是复制str的值过去。即,
//将NULL复制给参数。
strcpy(str,"hello");
free(str);//free并没有起作用,内存泄漏
*/
//正确的做法
GetMemoryEx(&str,10);//将str指针的地址传递到函数里,函数内部就
//可以改变str指针的值,让其指向新的地址。
strcpy(str,"hello");
free(str);//free并没有起作用,内存泄漏
return 0;
}
1.4. 小结
(1)C语言中只支持一维数组
(2)C语言中的数组大小必须在编译期就作为常数确定
(3)C语言中的数组元素可以是任何类型的数据
(4)C语言中的数组的元素可以是另一个数组
2. 数组参数和指针参数分析
2.1. 数组参数退化为指针的意义
(1)C语言中只会以值拷贝的方式传递参数,当向函数传递数组时,将整个数组拷贝一份传入函数导致执行效率低下,C语言以高效作是最初的设计目标,所以这种方法是不可取的。
(2)参数位于栈上,太大的数组拷贝将导致栈溢出。
(3)将数组名看做常量指针,传递的是数组的首元素地址,而不是整个数组。
2.2. 二维数组参数
(1)二维数组参数同样存在退化的问题:
二维数组可以看做是一维数组,其中的每个元素又是一个一维数组
(2)二维数维参数中第一维的参数可以省略
①void f(int a[5])←→void f(int a[])←→void f(int* a)
②void g(int a[5][3])←→void g(int a[][3])←→void g(int (*a)[3]);
(3)等价关系
(4)被忽视的知识点
①C语言中无法向一个传递任意多维数组。换一句话讲,形参int a[][3]是合法的,但int a[][]是非法的。
②因此,必须提供除第1维之外的所有其他维长度。第一维之外的维度信息用于完成指针运算。
【实例分析】传递与访问二维数组
#include <stdio.h>
void access(int a[][3],int row) //a[][3],形参第2维的长度必须提供
{
int col = sizeof(*a)/sizeof(int);
int i = 0;
int j = 0;
printf("sizeof(a) = %d\n",sizeof(a)); //指针,4字节
//a为int (*a)[3]类型,即a指向一个有3个int型元素的数组
printf("sizeof(*a) = %d\n",sizeof(*a));//3*4 =12字节
for(i = 0;i < row; i++)
for(j = 0; j < col; j++)
{
printf("%d\n", a[i][j]);
}
printf("\n");
}
void access_ex(int b[][2][3],int n) //数组参数的第2、3维长度必须提供
{
int i = 0;
int j = 0;
int k = 0;
printf("sizeof(b) = %d\n",sizeof(b)); //指针,4字节
//b为int (*b)[2][3]类型,即b指向一个有2行3列int型元素的二维数组
printf("sizeof(*b) = %d\n",sizeof(*b));//2 * 3 * 4 = 24字节
for(i = 0;i < n; i++)
for(j = 0; j < 2; j++)
for(k = 0; k < 3; k++)
{
printf("%d\n", b[i][j][k]);
}
printf("\n");
}
int main()
{
int a[3][3] = {{0, 1, 2},{3, 4, 5},{6, 7, 8}};
int aa[2][2] = {0};
int b[1][2][3] = {0};
access(a, 3);
//access(aa,2);//error,int(*)[2]与int (*)[3]类型不匹配
access_ex(b, 1);
//access_ex(aa,2);//int(*)[2]与int(*)[2][3]类型不匹配
return 0;
}
2.3. 小结
(1)C语言中中会以值拷贝的方式传递参数
(2)C语言中的数组参数必然退化为指针
(3)多维数组形参,必须提供除第1维之外的所有维长度。
(4)对于多维数组的函数参数,只有第一维是可变的(即无需提供)
3. 指针阅读技巧分析
3.1. 指针阅读技巧:右左法则
(1)从最里层的圆括号中未定义的标示符看起
(2)首先往右看,再往左看
(3)遇到圆括号或方括号时可以确定部分类型,并调转方向
(4)重复2、3步骤,直到阅读结束
【编程实验】复杂指针的阅读
注意:当读出是数组时,须读出元素个数、元素类型
当读出是函数时,须读出参数及类型,返回值类型
当读出是指针时,须读出指针所指向的类型,有时也须读出指针的类型。
例题① int (*p)(int*, int (*f)(int*));
A、读未标示符f,向右遇”)”括号,调转向左遇*,所以f是个指针,返回第2步,向右读右边的(int *),遇到“(”表示一个函数,说明f是个函数指针,指向一个函数,该函数参数为int*,返回值为int型。即f是个函数指针,至此,f指针读完……
B、读p未标示符,向右遇”)”括号,调转向左遇*,所以p是个指针,返回第2步,向右读遇p指针右边的“(”括号,说明是个函数,该函数有2个参数,一个为int*,一个为f函数指针,返回值为int型,即p是个函数指针,指向两个参数,分别为int*和函数指针型,返回值为int型的函数指针。
例题② int (*p[5])(int*)
A、读指针p,向右,说明p是数组,该数组有5个元素,每个元素的类型为指针类型,它们指向一个函数,该函数参数为int*,返回值为int型。
B、综上:p为一个数组,有5个元素,每个元素是一个函数指针,它们指向一个参数为int*,返回值为int的函数。
例题③ int (*(*p)[5])(int*);
A、读指针p,向右遇)括号,向左*,说明p是指针,部分确定类型回第2步,向右,说明p指针指向一个数组,该数组有5个元素,每个元素为指针,这个是个函数指针,指向参数为int*,返回值为int型的函数。
B、综上:p为一个数组的指针,该数组有5个元素,每个元素为都是一个函数指针,函数参数为int*,返回值为int型。
例题④ int (*(*p)(int*))[5];
A、首先p是个指针,指向函数,该函数参数为int*,返回值为指针。这个返回值指向一个数组,该数组有5个元素,每个元素为int型。
B、综上:p是个函数指针。该函数:
参数:int*
返回值是个数组指针,指向5个int型元素的数组。
例题⑤ void (*func(void (*p)(void *)))(void *)
A、func首先是个函数 :
参数(红色部分)为p,p是个函数指针,指向是参数为void*,返回值为void的函数。
返回值(蓝色部分,func前的星号),是一个指针,指向一个参数为void*,返回值为void的函数。即返回值是一个函数指针。
B、综上,func是个函数,参数是函数指针p,返回值也是函数指针。
3.2. 小结
(1)右左法则总结于编译器对指针变量的解析过程
(2)指针阅读练习的意义在于理解指针的组合定义
(3)可通过typedef简化复杂指针的定义