前言
如果你希望更加充分的了解C语言在声明指针时,最终编译器是如何处理的。这篇文章可以帮助你,并且可以让你理解以下代码区声明。
顺便说一下,以下的内容启发都来自《C和指针》、《C专家编程》。你可以阅读完这篇文章,再去细品一下这两本书!
void (* test(int,void *))()
//你是否能够理解上面的究竟干了什么
PS
这篇文章并不适合一些C基础不好的人,需要到其他的地方补充一下基础知识。
之所以不添加那么多的内容,旨在能够把标题讲明白而已!
C指针
从几个大家相对熟悉的指针声明开始。
char *p;//char类型的指针
char *p();//返回char类型指针的函数
char *p[];//一个数组,元素类型为char *
这些都是比较熟悉的指针声明。
现在提高一点难度:
char *p[10]
和char (*p)[10]
的区别在哪里?
前者是这样的:
一个数组,包含10个元素,并且每个元素的类型为char 指针
后者是这样的:
一个指针,指向一个数组,这个数组的元素类型为char,并且包含10个元素
到这里,其实编写过一些C程序的人,可能也知道这个意思。但是,究竟是如何得出这样的结果呢?
答案都在这个操作符优先级上了。我们看一下(来自《C和指针》第81页,部分删减,需要的自行查找
)
操作符 | 描述 | 用法示例 | 结果类型 | 结合性 |
---|---|---|---|---|
() | 聚组 | (表达式) | 与表达式同 | N/A |
() | 函数调用 | rexp(rexp,rexp) | rexp | L-R |
[] | 下标引用 | rexp[rexp] | rexp | L-R |
. | 访问结构成员 | repx.member | lexp | L-R |
-> | 访问结构指针成员 | rexp->member_name | lexp | L-R |
++ | 后缀自增 | rexp++ | rexp | L-R |
– | 后缀自减 | rexp– | rexp | L-R |
! | 逻辑反 | !rexp | rexp | R-L |
++ | 前缀自增 | ++rexp | rexp | R-L |
– | 前缀自减 | –rexp | rexp | R-L |
* | 间接访问 | *rexp | rexp | R-L |
对照表格(优先级从高到低
),我们开始吧。
先从char *p[]
开始吧
- 找到声明的标识符,在这个例子上,我们找到了
p
- 我们对比一下p左右两边的操作符,此时发现
[]
和*
,对比优先级,发现[]
明显高于*
,因此此时的p
标识符会先和[]
结合,那么此时p
就会表示为数组 - 接着
p
会和*
结合,所以这个数组的元素为指针类型
char (*p)[]
- 找到声明的标识符,我们依旧找到
p
- 我们对比发现,p的附近已经被
()
包围,编译器会理解为聚组
(最高的优先级),会强制修改p
和*
先进行结合,这样一来,p
就是一个指针 - 接着
p
会和[]
结合,这意味这p
指针将指向一个数组,并且类型为char
这就能够说明这两者之间的区别是如何而来的
复杂的指针
那么接下来,我们讨论高级点的东西,如果你需要用指针指向函数呢?(函数指针)
/*比如一个返回int,无参数的函数foo
*/
int foo();
指针该如何指向他呢?
int *p()
,这个显然并不能指向foo
函数,因为p
标识符肯定会和优先级高的()
结合形成一个函数。那这注定不能是一个指针。所以我们得修改这种优先级。
修改的方式就是上一个例子的()
,结果就是:int (*p)()
。现在你肯定已经为什么要这样声明函数指针了。
数组中的元素为函数指针
简单说明一下这个小标题的意思:
我希望一个数组的元素里可以记录一下函数,并且通过下标调用这些函数,返回int值。我该怎么办呢?
尝试一下:
- 首先我们得让标识符先和
[]
结合,让其成为一个数组,即p[]
- 我们得让这个数组的元素为函数,因此得紧着
()
,即p[]()
着答案看似已经出来了,int p[]()
,但是很不幸编译器会报错,因为编译器不允许数组包含函数。那怎么办?
在《C和指针》的261页中这样描述的:数组元素必须具有相同的长度,因此函数很显然不符合这样的条件。但在《C专家编程》59页简单的总结了C语言下的声明。其中包括了数组元素不能是函数
在《C专家编程》59页中的声明总结中,数组元素可以为函数指针,因此我们可以通过这种方式用数组去存储函数(感觉说法不太对劲)。
回到前面的p[]()
,我们的p[]
后面的需要一个指针所以很简单的得出答案:*p[]
,接着这个指针是要指向一个函数的。但()
高于*
。所以我们还是得强制利用()
聚组去修改默认的优先级,所以答案就很明显了:
int (*p[])() = &foo
/*这里需要注意,函数名总是由编译器把它转换为函数指针,因此&只是显示表示这种情况*/
回到最开始的void (* test(int,void *))()
结果为:
test是一个函数
test(int,void *)
,这个函数需要两个参数:一个是int型的数值,一个是void *指针,它将返回一个指针* test(int,void *)
,这个指针是一个函数(即函数指针)(* test(int,void *))()
,这个函数指针是一个无参数,无返回值的函数