函数指针与C语言中的复杂声明

此文章所描述的内容适用于C语言,C++也可参考

下面是一条涉及到函数指针的复杂的声明

int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *);

很多小伙伴可能都蒙了,这究竟是在声明啥?可以先把答案告诉大家:

此行代码声明了一个名为 Register 的函数,Register 函数返回一个函数指针参数也是一个函数指针,参数名字叫pf
Register函数的返回的函数指针的类型和参数列表中的函数指针的类型相同,都指向有两个const char *类型参数、返回值是int的函数。

在C语言代码中如何应对这种复杂的声明?

如果可以,先将一次声明拆解为多次声明

关于一次声明与多次声明,请参考我的另一篇文章:C语言中的一次声明和多次声明

对于拆解后的每一条声明,按照以下方法分析

  1. 从左往右看,跳过类型名和运算符,找到第一个非类型名词语,这就是被声明的标识符,假设他叫 A

    在上述代码中,第一个非类型名词语是 Register ,因此这行代码声明了一个叫 Register 的标识符

    注意:类型名不全都是C语言中的关键字,也可以有自定义的部分。例如struct pire是一个类型名,但其中的pire就是用户自己定义的。下述代码中的高亮部分均视为一个类型名。(下述三行代码互不相关)

    struct x {int a;} x;  //先自定义结构体类型 struct x ,再引用结构体类型 struct x ,第一个非类型名词语是 x
    struct {int a;} s;  //先自定义一个未命名的结构体类型 struct {int a;} ,再引用此结构体类型,第一个非类型名词语是 s
    struct d y;     //引用已被定义的结构体类型 struct d ,第一个非类型名词语是 y

  2. 找到了名字,我们来找类型。

    首先看看 A 的左边是否有 ‘(’ 。如果没有,跳到 ②;如果有,按照下述步骤判断后直接跳到 ④。

     A 的左边有 '(' 说明 A 被这个括号与另外一个部分绑定在了一起。我们只要找到这个部分,就能知道 A 的实质是什么。
     找到与 A 左边的括弧相对应的括弧,看看这个括号中除了 A 之外的部分是什么形式(也就是括号中 A 的右边那部分),
     会有以下2种情况:
     	1) 方括号型:例如 (A[20])。方括号型说明 A 是一个数组。
     	2) 圆括号型:例如 (A(int a))。圆括号型说明 A 是一个函数,A 右边的这个括号就是 A 的参数列表
    

    看看 A 的右边是否有 ‘)’。如果没有,跳到 ③;如果有,按照下述步骤判断后直接跳到 ④。

     A 的右边有 ')' 说明 A 被这个括号与另外一个部分绑定在了一起。我们只要找到这个部分,就能知道 A 的实质是什么。
     找到与 A 右边的括弧相对应的括弧,看看这个括号中除了 A 之外的部分是什么形式(也就是括号中 A 的左边那部分),
     只可能有以下1种情况:
     	星型:A 的左边部分是个 * 号。这说明 A 是个指针
    

    A 的两边都没有括弧,就看 A 左右两边都有啥。按照以下步骤判断后跳到 ④。

     1) 先看右边是否紧挨着 '[',如果是,那么 A 就跟这个方括号以及方括号中的内容绑定在一起,说明 A 是个数组
     2) 如果右边没有方括号,看看右边是否紧挨着 '(',如果是,那么 A 就跟这个圆括号以及圆括号中的内容绑定在一起,
      	说明 A 是个函数,这个圆括号内就是 A 的参数列表
     3) 如果 A 右边没有 '(' 也没有 '[' ,那么看看 A 左边是否紧挨着 * 号,如果是,那么 A 就跟这个 * 号绑定在一起,
     	说明 A 是个指针
     4) 如果上述3种情况都不符合,那么不用继续往下看了,我相信这一定是一条最简单的声明语句
    

    如果你成功走到了这一步,那么已经成功知道了 A 的本质。A 要么是数组,要么是指针,要么是函数。我们把已经跟 A
    绑定在一起的那一部分(数组是方括号,函数是圆括号,指针是 * 号)和 A 视为一个整体,叫做第一部分。然后按照以下步骤继续判断:

    一、首先,如果第一部分左边紧挨着’(’,右边紧挨着’)’,那么第一部分外层这对括号是无用的,我们将它收进第一部分。重复此步骤,直到第一部分外层不存在无用的括号

    二、接下来我们开始判断第一部分的类型(这里的类型指的是函数的返回值类型、指针的类型或数组元素的类型):

    三、看看第一部分周围有什么:

     1) 如果有[ ]就说明第一部分的类型是数组,把[ ]收纳进来,形成第二部分
     2) 如果没有[ ]就看看是否有( ),如果有,说明第一部分的类型是函数,把( )收纳进来,形成第二部分
     3) 如果既没有[ ]也没有( ),就看看是否有 * 号,如果有,说明第一部分的类型是指针,把 * 号收纳进来,形成第二部分
     4) 如果上述3种都没有,就只可能剩下如 int,float 之类的类型名,这就是第一部分的类型,如果是这种情况,复杂声明
     	的判断就已经结束了
    

    如果判断还未结束,形成了第二部分,那么重复上述三个步骤,如果判断依旧未结束,形成了第三部分,则接着重复上述三个步骤,直到判断结束

  3. 到这里,判断就结束了。相信聪明的你已经完全明白了呢。如果还不明白,那就反复阅读此文章,多加揣摩,必成正果。已经完全明白了的同学可以回到文章开头用第一行代码进行实践,看看得出的答案是否是正确答案。

  4. 对于还未完全明白的同学,下面以不同颜色标出了判断文章开头代码时所划分出的各个部分,希望能有所帮助。
    (黄色为第一部分,黑框为第二部分,下划线为第三部分)

代码部分示意图

  1. 对于函数的参数列表中声明的形参,我们也可以按照这篇文章中的方法层层分解,最后就可以成功读懂错综乱杂的代码

  2. 函数指针声明的固定格式中的参数列表中的形参无标识符,只有关键字和运算符,例如文章开头代码中出现了4次的const char *,这种情况就不能通过这篇文章中的方法来判断形参究竟是什么类型。对于这种情况,有下面两种解决方案:

     1) 一般这种情况下的声明的类型都比较简单,聪明的你一定一眼就能看出来
     2) 多做解读复杂声明的练习,到时定会灵光一现,恍然大悟
    

最后


#define 欢迎小伙伴们留言~

#include 转载请注明出处哦

https://blog.csdn.net/weixin_43737206/article/details/99239833

fprintf(如果你觉得有用的话, "点个好看再走吧 %s" , "嘿嘿");

发布了19 篇原创文章 · 获赞 23 · 访问量 3899

猜你喜欢

转载自blog.csdn.net/weixin_43737206/article/details/99239833
今日推荐