一、指向函数的指针

函数名可以在表达式中被解读成“指向函数的指针”,因此,正如代码清单 2-2 的实验那样,写成 func 就可以取得指向函数的指针。

“指向函数的指针”本质上也是指针(地址),所以可以将它赋给指针型变量。

比如有下面的函数原型:

int func(double d);

保存指向此函数的指针的变量的声明如下:

int (*func_p)(double);

然后写成下面这样,就可以通过 func_p 调用 func,

int (*func_p)(double);  // 声明
func_p = func; // 将func 赋给func_p
func_p(0.5); // 此时,func_p 等同于func

将“指向函数的指针”保存在变量中的技术经常被运用在如下场合:

  • GUI 中的按钮控件记忆“当自身被按下的时候需要调用的函数”
  • 根据“指向函数的指针的数组”对处理进行分配

后者的“指向函数的指针的数组”,像下面这样使用:

int (*func_table[])(double) = {
func0,
func1,
func2,
func3,
};

func_table[i](0.5);  // 调用func_table[i]的函数,参数为0.5

使用上面的写法,不用写很长的 switch case,只需通过 i 的值就可以对处理进行分配。

哦?不明白为什么?

确实,像

int (*func_p)(double);  // 指向函数的指针

还有,

int (*func_table[])(double);  // 指向函数的指针的数组

这样的声明,是不能用普通的方法来读的。

关于这种声明的解读方式,会在后面进行说明。

二、关于指向函数的指针引起的混乱

正如上面说明的那样,对于 C 语言,表达式中的函数名可以被解读成“指向函数的指针”

在信号处理、事件驱动的程序中,这种特性往往以回调函数的形式被使用。

/*如果发生SIGSEGV(Segmentation falut),回调函数segv_handler */
signal(SIGSEGV, segv_handler);

可是,如果基于之前说明过的 C 语言声明规则,int func()这样的声明会被解释为“返回 int的函数”,如果 函数名在表达式中,只是取出 func,则解释成“指向返回 int 函数的指针”,是不是感觉很怪异?如果一定要使用指向函数的指针,必须要写成&func。

对于上面信号处理的函数,写成

signal(SIGSEGV, &segv_handler);

这样,实际上也能顺利地执行

相反,像

void (*func_p)();

这样,变量 func_p 声明为指向函数的指针,进行函数调用的时候,可以写成

func_p();

但是像 int func()这种声明,都是用 func()这样的方式进行调用的,从对称性的角度考虑,对于 void (*func_p)(),必须要写成

(*func_p)();* 

* 早期的 C 语言中,好像也只能这么写……

这样也是能毫无问题地执行的。

是不是感觉 C 语言的关于指向函数的指针的语法比较混乱?

混乱产生的原因就是:“表达式中 的函数可以解读成‘指向函数的指针’”这个意图不明的规则(难道就是为了和数组保持一致?)。

为了照顾到这种混乱,ANSI C 标准对语法做了以下例外的规定:

  • 表达式中的函数自动转换成“指向函数的指针”。但是,当函数是地址运算符&或者 sizeof 运算符的操作数时,表达式中的函数名不能变换成“指向函数的指针”。
  • 函数调用运算符()的操作数不是“函数”,而是“函数的指针”。

如果对“指向函数的指针”使用解引用*,它暂时会成为函数,但是因为在表达式中,所以它会被瞬间地变回成“指向函数的指针”。

结论就是,即使对“指向函数的指针”使用*运算符,也是对牛弹琴,因为此时的运算符*发挥不了任何作用。

因此,下面的语句也是能顺利执行的,

(**********printf)("hello, world\n");  // 无论如何,*就是什么也没做

延伸阅读:

《征服 C 指针》摘录1:什么是空指针?区分 NULL、0 和 '\0'

《征服 C 指针》摘录2:C变量的 作用域 和 生命周期(存储期)

《征服 C 指针》摘录3:数组 与 指针

《征服 C 指针》摘录4:函数 与 指针

《征服 C 指针》摘录5:函数形参 和 空的下标运算符[]

《征服 C 指针》摘录6:解读 C 的声明

《征服 C 指针》摘录7:练习——挑战那些复杂的声明

《征服 C 指针》摘录4:函数 与 指针的更多相关文章

  1. C语言函数指针(指向函数的指针)

    转载:http://c.biancheng.net/cpp/html/3248.html 一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常 ...

  2. C:函数指针、回调函数

    函数指针 是一个指针,指向函数的指针,指针存放的都是地址,所以函数指针存放的是函数的地址.数组名就是数组的首地址,函数名就是函数的首地址.与数组类似. 代码demo int (*p) (int ,in ...

  3. C语言函数指针变量和指针函数以及指针数组

    C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址.我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数.然后通过指针变量就可以找到并调用这 ...

  4. 看懂c/c++ 函数、指针、数组定义

    读懂 函数 + 指针 + 数组 c语言运算符机器优先级,看这里 结合运算符优先级,我们试着读懂函数和指针 优先级简单看 表达式提升():一级优先 函数():二级优先 数组[]:二级优先 指针定义*:三 ...

  5. C 函数指针与回调函数

    函数指针是指向函数的指针变量. 通常我们说的指针变量是指向一个整型.字符型或数组等变量,而函数指针是指向函数. 函数指针可以像一般函数一样,用于调用函数.传递参数. 函数指针变量的声明: #inclu ...

  6. c指针 --笔记2返回指针值的函数

    返回指针值的函数 一般带回指针值的函数,定义形式为: int *a (int x, int y); 看这个经典案例: #include <stdio.h> int main(int arg ...

  7. C语言中函数和指针的參数传递

    近期写二叉树的数据结构实验.想用一个没有返回值的函数来创建一个树,发现这个树就是建立不起来,那么我就用这个样例讨论一下c语言中指针作为形參的函数中传递中隐藏的东西. 大家知道C++中有引用的概念,两个 ...

  8. C++(二十二) — 指针变量、函数指针、void指针

    1.指针变量 (1)指针变量必须在初始化后才可以正常使用,初始化就是给他分配一个有效的数据地址. 先初始化,后使用. (2)指针可以进行加减运算,++ 或者 --:将指针的位置向前或者向后移动一个数据 ...

  9. C++指向函数的指针

    直接上代码: #include<iostream> #include<string> #include<vector> using namespace std; t ...

随机推荐

  1. HDU2089 不要62[数位DP]

    不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  2. POJ1703Find them, Catch them[种类并查集]

    Find them, Catch them Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 42416   Accepted: ...

  3. python的历史

    Python的诞生 Python的创始人吉多·范罗苏姆(Guido van Rossum),在1989年12月的圣诞节期间,为了打发时间,决定开发一种新的脚本解释程序,作为ABC语言的继承. 现在,p ...

  4. Java的容器类Collection和Map

    一,概念 JAVA集合只能存放引用类型的的数据,不能存放基本数据类型. java的容器类一共有两种主要类型,Colllection和Map. 两者的区别是:Collection是单个元素,而Map是存 ...

  5. 一步一步学习.NET Core 介绍篇 01

    什么是 ASP.NET Core? ASP.NET Core 是一个新的开源和跨平台的框架,用于构建如 Web 应用.物联网(IoT)应用和移动后端应用等连接到互联网的基于云的现代应用程序.ASP.N ...

  6. Promiscuous Mode

      简介 Monitor mode 与 promiscuous mode 比较 这是在网卡上的的两个特殊的模式,简而言之,都是将网卡的过滤器关闭. Monitor mode 这是我们常常提到的snif ...

  7. 【原】浅谈KL散度(相对熵)在用户画像中的应用

    最近做用户画像,用到了KL散度,发现效果还是不错的,现跟大家分享一下,为了文章的易读性,不具体讲公式的计算,主要讲应用,不过公式也不复杂,具体可以看链接. 首先先介绍一下KL散度是啥.KL散度全称Ku ...

  8. CBOW and Skip-gram model

    转自:https://iksinc.wordpress.com/tag/continuous-bag-of-words-cbow/ 清晰易懂. Vector space model is well k ...

  9. C#稳固基础:传统遍历与迭代器

    Hello,Coders.我们除了天天的码 if…else…之外,还会不断的码出foreach.我今天要说的是:传统遍历需实现的接口及我们还有一种更简洁优雅的方式实现多种迭代器. 传统遍历 传统的遍历 ...

  10. mui事件绑定和可以用的js dom操作方法

    <script> //事件绑定 对象 方法 子元素 回调函数 mui('body').on('shown', '.mui-popover', function(e) { //console ...