关于该死的函数指针和指针函数
先来个目录
常量指针、指针常量
数组指针、指针数组
函数指针、指针函数
1、先看第一组
int const *p;// 常量指针,读作:指向常量的指针
int *const p;// 指针常量,读作:是常量的指针
2、第二组
int (*p)[5];// 数组指针,读作:指向数组的指针
int *p[5];// 指针数组,读作:元素是指针的数组
3、最后一组
int (*fun)(void );// 函数指针,读作:指向函数的指针
int *fun(void );// 指针函数,读作:返回值是指针的函数
好了,看完上面的,是不是很想吐
我们就干脆恶心到底
一、仔细分析第一组,论类型,我们从前看int const *和int *const都是整形指针。再看变量,此时我们从变量名开始看,*p我们理解时是向p指向的内存取值,再加上const,就变成了我们用指针取值是常量,就是指向了常量。而const p则是p保存的内容为常量,即地址为常量,也就是这个指针是常量。
二、再看第二组,
首先我们要对指针(*)和数组([])这两个符号的优先级补充一下,关于地址操作的两个符号(*(解引用)&(取址))都是单目运算符,而[]操作是初等运算符,初等运算符的优先级是高于单目运算符的,所以我们常常定义的指针数组int *p[5]结合起来应该是int *(p[5])。
然后,我们还有一个在理解指针时的bug,int *p喜欢写成int* p,我们理解的理所当然:“定义指针,数据类型是int*而不是int“。再结合这里我们把int *(p[5])看成int* (p[5])。
最后,其实在民间,数组可以这样理解,int p[5] -->int[5] p,是不是很JAVA。
总结上面三点的理解我们的int *p[5]变成了int* (p[5]),数据类型是大小为5个int*、命名为p的数组。
然后int (*p)[5]也就是 int[5]* p,数据类型是一个指向(数据类型是大小为5个int、没有命名的数组)的指针。
真的好恶心,,,想吐
在讲第三组前,乱入一个东西,巩固一下上面两组。
typedef
不用想太多,既然我在这里强调,一定是很恶心的
typedef int (*pT)[5];
int a[][5]={1,2,3,4,5,6,7,};
pT b = a;
printf("%d\n",b[1][0]);
是不是很惊悚,还有这种操作!。。。
我不多说了,脑补一下也就知道了它的作用
三、我们再回到正题,讲第三组
其实第三组从内容上,并没有什么太特殊的。
所以后面,,,我准备了点小惊喜: ).....
我们都知道,变量名前面加类型,表示这个变量返回的是什么类型的数据。例如:加指针,表示返回指针。
变量后加括号,表示这是一个函数名。
int (*fun)(void )这个样子确实不太好理解,我们知道指针和数组是有时可以互换的,我们变一下:int(fun[5])(void)。这样是不是好想多了,假定int A()是一个函数,那么我们把A换成a[5]就变成了五个用数组标号的函数。
那么,我们就可以把int (*fun)(void)中的fun理解成一个指针,一个指向(返回值是int、参数是void)函数的指针。只不过这里的函数是匿名的。
是不是看到这里恍然大悟,,,
然后我们也就很简单的理解
typedef int(*funT)(void);指的是定义了一个新的数据类型,就是上面的那种。。。。。。
放心吧,我还有更坑更恶心的
( * ( void ( * ) ( ) ) 0 ) ( );
是不是很意外很惊喜!!!
吃鸡。。。。。
我们说一下它的来源,出自
我们先回忆一下之前的知识点,当我们使用
typedef int(*funT)(void);
funT myFun = &function1;
可以看到我们生命了一个函数指针,并且指向了function1函数
那么我们该怎么使用myFun呢
定义指针时,
int *p;
p = &a;
b = *p;
所以,我们使用时,也是
(*myFun)();
所以,回到(* (void(*)()) 0)()
我们先忽略(void(*)()),因为是个强制转换我们先看其他,还剩下(*0)(),这个好理解吧,我们本来是要取址,只不过这里的址为0。但是我们要知道,0是没有数据类型的,我们取址出来的是不能使用的,所以就有了上面强制转换的部分,是不是很快就发现不要那个void其实和(*0)()的数据类型是一样的。正是如此,我们就是需要这种类型的指针。
再补上我们书上作者留的后言
最后补充一点:ANSI C允许我们将(*fp)()简写为fp(),所以上文有的地方为了方便理解就可能使用了生僻的用法。