函数指针是指针变量,而不是函数,可见在 C 语言的世界里,函数不可以为变量,尽管如此,函数指针的存在使 C 语言离函数式编程一步之遥。

函数指针

函数指针是指针(一种特殊的指针),而指针是一种变量,函数指针自然不是函数,但可将匹配的函数名赋值给函数指针。

int* (*func)(int* );        // 函数指针,也即指向函数的指针
int* foo(int *){return NULL;
}                   // 普通函数
func = foo;         // 函数指针指向某一具体的函数

右左法则

首先看看如下一个声明:

int* ( *( *fun )( int* ) )[10];// 可拆解为:// typedef int* (*PARA)[10]; // 数组指针,而偏偏 数组又由指针构成// typedef PARA (*fun)(int *);// fun 是函数指针,参数为 int* ,返回值为数组指针;

这是一个会让初学者感到头晕目眩、感到恐惧的函数指针声明。在熟练掌握C/C++的声明语法之前,不学习一定的规则,想理解好这类复杂声明是比较困难的。

C/C++所有复杂的声明结构,都是由各种声明嵌套构成的。如何解读复杂指针声明?右左法则是一个很著名、很有效的方法。不过,右左法则其实并不是C/C++标准里面的内容,它是从C/C++标准的声明规定中归纳出来的方法。C/C++标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,从嵌套的角度看,两者可以说是一个相反的过程。右左法则的英文原文是这样说的:

The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.

这段英文的翻译如下:

右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。

笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。

现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:

两个简单的小例子

int (*func)(int *p);

首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是int。

int (*func)(int *p, int (*f)(int*));

func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。

int (*func[5])(int *p);

func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。

int* foo(int *) { return 0; }
int* (*func[5])(int *);func[0] = foo;
...

int (*(*func)[5])(int *p);

func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。

int foo(int *) { return 0; }
int (*(*func )[5])(int *);
(*func)[0] = foo;       // *func[0] = foo;

int (*(*func)(int *p))[5];

func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。

一些非法的复杂指针声明

int func(void) [5];

func是一个返回值为具有5个 int 元素的数组的函数。但 C 语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,那么接收这个数组的内容的东西,也必须是一个数组,但C/C++语言的数组名是一个不可修改的左值,它不能直接被另一个数组的内容修改,因此函数返回值不能为数组。

int func[5](void);

func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素必须是对象,但函数不是对象,不能作为数组的元素。

typedef:拨云见日

实际编程当中,需要声明一个复杂指针时,如果把整个声明写成上面所示这些形式,将对可读性带来一定的损害,应该用typedef来对声明逐层分解,增强可读性。

typedef 是一种声明,但它声明的不是变量,也没有创建新类型,而是某种类型的别名。typedef有很大的用途,对一个复杂声明进行分解以增强可读性是其作用之一。例如对于声明:

int (*(*func)(int *p))[5];

可以这样分解:

typedef int (*PARA)[5];// 数组指针
typedef PARA (*func)(int *);// 返回数组指针的函数指针

References

[1] 第七章 右左法则—-复杂指针解析

趣学 C 语言(九)—— 复杂指针解析相关推荐

  1. 从零开始学C语言 : 初阶指针

    目录: 1. 指针是什么  2. 指针和指针类型  3. 野指针  4. 指针运算  5. 指针和数组  6. 二级指针  7. 指针数组 1.指针是什么: 1. 指针是内存中一个最小单元的编号,也就 ...

  2. C语言--const修饰指针解析

          这种例子在我们平时编程时可能会很少用,但是在面试时被问的可能性就很大了. 用const修饰指针,常见的情况可以分为四种: 1.const int *p; // p 可变,p 指向的对象不可 ...

  3. 趣学 C 语言(十三)—— 标准输入输出重定向

    在 C 语言中,可以使用 freopen 将输入输出重定向. C语言的标准输入输出为 stdin 和 stdout,这两个变量的类型为FILE*类型,也就是说,标准输入输出操作,其本质还是文件操作,只 ...

  4. 趣学 C 语言(十二)—— 文件操作

    对于 C 语言而言,无论是标准输入 stdin,还是标准输出 stdout,还是标准错误输出 stderr,本质上都是一种文件操作,只不过读写的文件变成了控制台(console),或者说 stdin/ ...

  5. 趣学 C 语言(八)—— 文件内部位置

    rewind, ftell, fgetpos, fsetpos, lseek fseek()用来移动文件流的读写位置. int fseek(FILE * stream, long offset, in ...

  6. 趣学 C 语言(七)—— 文件读写操作

    (1)rewind:函数把文件内部位置指针移到文件首: (2)fseek:移动文件流的读写位置: 1. 字符读写函数 :fgetc和fputc 在fgetc函数调用中,读取的文件必须是以读或读写方式打 ...

  7. 趣学 C 语言(四)—— 字符串与字符数组

    char *ps1 = "hello";// 字符串// ps1 ⇒ 类型为 char*// const char * char ps2[] = "hello" ...

  8. 再学C语言36:指针和数组

    指针提供了一种用来使用地址的符号方法,可以很有效地处理数组 数组标记实际上是一种变相使用指针的形式 数组名同时也是该数组首元素的地址,如果arr是一个数组,则: arr == &arr[0]; ...

  9. 趣学 C 语言(十)—— 控制台交互(getch() vs. getchar())

    getch() vs getchar() getch()的功能是从键盘输入一个字符,但不在屏幕上显示.在这里,该行的作用是等待,只有当用户从键盘敲任一键时,程序才继续执行, 因此用户可利用这个等待时间 ...

最新文章

  1. group by 查找订单的最新状态 join
  2. Matlab如何从dat或者txt文件读入数据
  3. Redis 你该懂的点
  4. java - 方法的重载
  5. 大数据WEB阶段(二十)更新丢失
  6. Vuejs-踩坑/注意事项记录
  7. linux 查看链接最终目标,linux学习笔记7-链接
  8. 华为免费虚拟服务器,免费试用虚拟服务器
  9. jQuery选择元素
  10. Redis,Memcache,MongoDb的特点与区别
  11. 华为中兴腾讯拿下国家科学技术进步奖,中南大学一作奖项总数全国第三
  12. YOLO系列专题——Complex-YOLO
  13. IPXX防护等级中关于防水实验的规定
  14. json数据格式分析
  15. linux学习笔记(13)终端清屏
  16. 系统动力学软件vensim学习之lookup
  17. 超星阅读器pdz文件转为xps文件或pdf文件说明
  18. python计算代码运行时间
  19. Android 蓝牙驱动专题分析(2)--- 蓝牙驱动代码流程、kernel dump、tombstone问题分析
  20. 51单片机数据存储器扩展实验(汇编)

热门文章

  1. 如何打开电脑就自动显示html文件,电脑教程:Win10怎么打开html文件
  2. 从714里连续减去6减几次得0_一年级下册数学想加算减、破十法、连减法,家长来看看...
  3. html禁止页面动画,如何在页面加载时阻止CSS动画?
  4. Navicat过期问题的解决
  5. 剑指offer面试题57. 和为s的两个数字(双指针)
  6. Redis教程:数据对象分析(二)
  7. 两个实际任务掌握图像分类【Keras】(转)
  8. mysql-innoDB学习笔记
  9. ajax的两个重要参数contentType 和dataType
  10. symfony3 yml配置文件详解