在开始理解之前,先摆上我最喜欢的一句话:在《C语言深度剖析》中:数组就是数组,指针就是指针,它们是完全不同的两码事!他们之间没有任何关系,只是经常穿着相似的衣服来迷惑你罢了。
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身 决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节, 至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
A),int *p1[10];
B),int (*p2)[10];
既然数组和指针不同,能体现在哪?
主要在p+1;
看以下代码:
int main() { int a[3][4]; return 0; }
如果我们把上面的这个数组赋值给一个指针,为int (*p)[4],这里p是一个指针,它指向一个有三个元素的数组,那么p+1会跳过整个数组,也就是指向了二维数组的第二行,也称为行指针。
而如果是这样呢:int *p[3],这是一个数组,它有三个元素,它里面的元素是指针变量,p+1,执行后会指向的是下一个元素。也就是(arr+1).
到了激动人心的时刻:
该谈论函数指针,函数指针数组,指向函数指针数组的指针这三个内容了,别着急,慢慢来。
我们先来看一个准则:出处:点击打开链接
在上面的连接中有讲到:右左法则。
右左法则:首先从最里面的圆括号(未定义的标识符)看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明。
函数指针:顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数
这是定义一个函数指针数组。它是一个数组,数组名为 pf,数组内存储了 3 个指向函数的 指针。这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函 数。这念起来似乎有点拗口。不过不要紧,关键是你明白这是一个指针数组,是数组。
指向函数指针数组的指针(函数指针数组的指针):char* (*(*pf)[3])(char* p);
注意,这里的 pf 和上一节的 pf 就完全是两码事了。上一节的 pf 并非指针,而是一个数组名; 这里的 pf 确实是实实在在的指针。这个指针指向一个包含了 3 个元素的数组;这个数字里 面存的是指向函数的指针;
char* (*pf[3])(char* p),首先从最里面的圆括号(未定义的标识符)看起,即(*pf[3]),前面我们说过”[]”的优先级高于“*”,所以,pf和“[]”构成了一个数组。数组有三个元素,再看pf左边,“*”,表示数组元素的类型是指针。(*pf[3])是一个“()”,说明数组元素指向函数,是函数指针,函数的参数类型呢是(char* p),返回值类型是char*。总结起来就是一个数组有三个元素是指针类型,指向函数。
char* (*(*pf)[3])(char* p),先未定义的标识符(*pf),由于“*”和pf结合构成一个指针,再看(*pf)右边是一个数组,表明这个指针指向有三个元素的数组,(*pf)左边“*”表明数组的元素类型是指针。再跳出圆括号,来到(*(*pf)[3])右边,看到的是一个“()”,表明这个数组指向的是一个函数,而这个函数的返回值是char*,参数类型是(char* p)。
//函数指针数组 #include <stdio.h> #include <string.h> char* fun1(char* p) { printf("%s\n", p); return p; } char* fun2(char* p) { printf("%s\n", p); return p; } char* fun3(char* p) { printf("%s\n", p); return p; } int main() { char* (*pf[3])(char* p); pf[0] = fun1; // 可以直接用函数名 pf[1] = &fun2; // 可以用函数名加上取地址符 pf[2] = &fun3; pf[0]("fun1"); pf[0]("fun2"); pf[0]("fun3"); return 0; }
//函数指针数组的指针 #include <stdio.h> #include <string.h> char* fun1(char* p) { printf("%s\n", p); return p; } char* fun2(char* p) { printf("%s\n", p); return p; } char* fun3(char* p) { printf("%s\n", p); return p; } int main() { char* (*a[3])(char* p); char* (*(*pf)[3])(char* p); pf = &a; a[0] = fun1; a[1] = &fun2; a[2] = &fun3; pf[0][0]("fun1"); pf[0][1]("fun2"); pf[0][2]("fun3"); return 0; }
在上面提供的链接中对于右左法则的使用如果很熟练的话,那么就对这些理解的很清楚了。
(*(void(*) ())0)()这是《C语言深度剖析》中一个很经典的例子,可以用来锻炼下:
解释如下:还是用右左法则,先看到(*)是一个指针,(*)的右边是“()”,表明这是一个函数,再看(*)左边表明函数的返回值类型为void,跳出这个圆括号,来到(void(*) ())的右边是一个0,很多人都卡在这边。有一个小知识点:如何声明一个给定类型的标量,那么该类型的类型转换就很容易得到了:只需要把声明中的变量名 和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。例如:因为下面的声明:
float (*h)();
表示h是一个指向返回值为浮点类型的函数的指针,因此,
(float(*)())
表示一个“指向返回值为浮点类型的函数的指针”的类型转换。同理(void(*) ())也表示强制类型转换,将0强制类型转换为一个函数指针。0 是一个地址,也就是说一 个函数存在首地址为 0 的一段区域内。再看左边,(void(*) ())是“*”,表示对这个函数指针进行解引用,(*(void(*) ())0)右边的“()”表示对这个函数解引用之后进行调用。
再来看这个代码:
void (*signal(int , void(*)(int)))(int);
还是利用右左法则:先看未定义的标识符,
下面借助《C语言深度剖析》代码来具体了解:(*)表示是一个指针,它的右边是一个”()”,表示这是一个函数指针,它的参数是int,返回值是void。再看void(*)(int)左边,前面有“,”表明是同等级别的,说明int , void(*)(int)都是参数,是哪个函数的参数呢?l(int ,void(*)(int)外面的signa是函数名,*表示这个函数又是一个函数指针。跳出这个(*signal(int , void(*)(int))),来到右边,又是“()”,表明又是一个函数,函数的参数类型是int类型,返回值是void。总结一下:有一个函数指针,它的参数里面又嵌套了一个函数指针。