C语言中的复杂声明和定义,以及如何使用typedef进行简化

原创 2015年09月21日 10:12:16
  • 理解复杂的声明和定义

       在阅读Linux的内核代码是经常会遇到一些复杂的声明和定义,例如:

        (1)  void * (* (*fp1) (int)) [10];

        (2)  float (* (*fp2) (int, int, float)) (int);

        (3)  typedef double (* (* (*fp3) ()) [10]) ();

               fp3 a;

        (4)  int (* (*fp4()) [10]) ();

        刚看到这些声明或者定义时,一些初学者甚至有一定经验的工程师都有可能头皮发毛,基于大惑不解。如果缺乏经验和方法来对这些内容进行理解,势必会让我们浪费大量的时间。

        我尝试对这些内容进行疏理和总结,为自己和有同样困惑的同学答疑解惑。要理解这些复杂的声明和定义,我觉得首先不能着急,应该由浅而深,逐步突破。下面先看一些简单的定义:

        1. 定义一个整型数

            int a;

        2. 定义一个指向整型数的指针

            int *p;

        3. 定义一个指向指针的指针,它指向的指针指向一个整型数

            int **pp;

        到这一步我想大多数人都还好理解,我们可以用一些简单的代码把这三条给串起来:

int a;
int *p;
int **pp;

p = &a;   // p指向整数a所在的地址
pp = &p;  // pp指向指针p

        4. 定义一个包含10个整型数的数组

            int arr[10];

        5. 定义一个指向包含10个整型数数组的指针

            int (*pArr) [10];

        用几行代码将4、5两个定义串起来:

int arr[10];
int (*pArr) [10];

pArr = &arr;
         6. 定义一个指向函数的指针,被指向的函数有一个整型参数并返回整型值

             int (*pfunc) (int);

         7. 定义一个包含10个指针的数组,其中包含的指针指向函数,这些函数有一个整型参数并返回整型值

             int (*arr[10]) (int);

        用几行代码将6、7两个定义串起来:

int (*pfunc) (int);
int (*arr[10]) (int);

arr[0] = pfunc;
        到这一步,似乎就不是那么好理解了。现在需要请出用于理解复杂定义的“右左法则”:

        从变量名看起,先往右,再往左,碰到圆括号就调转阅读的方向;括号内分析完就跳出括号,还是先右后左的顺序。如此循环,直到分析完整个定义。

        让我们用这个方法来分析上面的第6条定义:int (*pfunc) (int);

        找到变量名pfunc,先往右是圆括号,调转方向,左边是一个*号,这说明pfunc是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*pfunc)是一个函数,所以pfunc是一个指向这类函数的指针,即函数指针,这类函数具有一个int类型的参数,返回值类型是int。

        接着分析第7条定义:int (*arr[10]) (int);

        找到变量名arr,先往右是[]运算符,说明arr是一个数组;再往左是一个*号,说明arr数组的元素是指针(注意:这里的*修饰的不是arr,而是arr[10]。原因是[]运算符的优先级比*要高,arr先与[]结合。);跳出圆括号,先往右又遇到圆括号,说明arr数组的元素是指向函数的指针,它指向的函数有一个int类型的参数,返回值类型是int。

        分析完这两个定义,相信多数人心里面应该有点谱了。可应该还有人会问:怎么判断定义的是函数指针(定义6),还是数组指针(定义5),或是数组(定义7)?可以抽象出几个模式:

  • type (*var)(...); // 变量名var与*结合,被圆括号括起来,右边是参数列表。表明这是函数指针
  • type (*var)[];    //变量名var与*结合,被圆括号括起来,右边是[]运算符。表示这是数组指针
  • type (*var[])...;     // 变量名var先与[]结合,说明这是一个数组(至于数组包含的是什么,由旁边的修饰决定)   

        至此,我们应该有能力分析文章开始列出来了几条声明和定义:

        (1)  void * (* (*fp1) (int)) [10];

        找到变量名fp1,往右看是圆括号,调转方向往左看到*号,说明fp1是一个指针;跳出内层圆括号,往右看是参数列表,说明fp1是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;再跳出外层圆括号,往右看是[]运算符,说明函数返回的是一个数组指针,往左看是void *,说明数组包含的类型是void *。简言之,fp1是一个指向函数的指针,该函数接受一个整型参数并返回一个指向含有10个void指针数组的指针。

        (2) float (* (*fp2) (int, int, float)) (int);

        找到变量名fp2,往右看是圆括号,调转方向往左看到*号,说明fp2是一个指针;跳出内层圆括号,往右看是参数列表,说明fp2是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;再跳出外层圆括号,往右看还是参数列表,说明返回的指针是一个函数指针,该函数有一个int类型的参数,返回值类型是float。简言之,fp2是一个指向函数的指针,该函数接受三个参数(int, int和float),且返回一个指向函数的指针,该函数接受一个整型参数并返回一个float。

        (3)  typedef double (* (* (*fp3) ()) [10]) ();

               fp3 a;

        如果创建许多复杂的定义,可以使用typedef。这一条显示typedef是如何缩短复杂的定义的。

        跟前面一样,先找到变量名fp3(这里fp3其实是新类型名),往右看是圆括号,调转方向往左是*,说明fp3是一个指针;跳出圆括号,往右看是空参数列表,说明fp3是一个函数指针,接着往左是*号,说明该函数的返回值是一个指针;跳出第二层圆括号,往右是[]运算符,说明函数的返回值是一个数组指针,接着往左是*号,说明数组中包含的是指针;跳出第三层圆括号,往右是参数列表,说明数组中包含的是函数指针,这些函数没有参数,返回值类型是double。简言之,fp3是一个指向函数的指针,该函数无参数,且返回一个含有10个指向函数指针的数组的指针,这些函数不接受参数且返回double值。

        这二行接着说明:a是fp3类型中的一个。

        (4)  int (* (*fp4()) [10]) ();

        这里fp4不是变量定义,而是一个函数声明。

        找到变量名fp4,往右是一个无参参数列表,说明fp4是一个函数,接着往左是*号,说明函数返回值是一个指针;跳出里层圆括号,往右是[]运算符,说明fp4的函数返回值是一个指向数组的指针,往左是*号,说明数组中包含的元素是指针;跳出外层圆括号,往右是一个无参参数列表,说明数组中包含的元素是函数指针,这些函数没有参数,返回值的类型是int。简言之,fp4是一个返回指针的函数,该指针指向含有10个函数指针的数组,这些函数不接受参数且返回整型值。

  • 用typedef简化复杂的声明和定义

        以上我们已经看到了不少复杂的声明和定义,这里再举一个例子:

        int *(*a[10]) (int, char*);

        用前面的“右左法则”,我们可以很快弄清楚:a是一个包含10个函数指针的数组,这些函数的参数列表是(int, char*),返回值类型是int*。理解已经不成问题,这里的关键是如果要定义相同类型的变量b,都得重复书写:

        int *(*b[10]) (int, char*);

        这里有没有方便的办法避免这样没有价值的重复?答案就是用typedef来简化复杂的声明和定义。

        typedef可以给现有的类型起个别名。这里用typedef给以上a、b的类型起个别名:       

typedef int *(*A[10]) (int, char*); // 在之前定义的前面加入typedef,然后将变量名a替换成类型名A
        现在要再定义相同类型的变量c,只需要:

        A c;


        再看一例:

        void (*b[10]) (void (*)());

        先替换右边括号里面的参数,将void (*)()的类型起个别名pParam:       

typedef void (*pParam) ();
        再替换左边的变量b,为b的类型起个别名B:

typedef void (*B) (pParam);
        原声明的简化版:

B b[10];


参考:

1. 《c++编程思想》,章节3.10.2 复杂的声明和定义,P.99 ~ P.100

2.  http://www.cnblogs.com/charley_yang/archive/2010/12/15/1907384.html



C复杂声明

C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既著名又常用的方法。不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的...
  • hshl1214
  • hshl1214
  • 2010年06月04日 15:02
  • 3089

C语言复杂声明解析

C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既著名又常用的方法。不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的...
  • wangweixaut061
  • wangweixaut061
  • 2011年06月17日 00:42
  • 5897

c语言中typedef的几种用法

几种用法:1.常规变量类型定义例如:typedef unsigned char uchar 描述:uchar等价于unsigned char类型定义 uchar c声明等于unsigned char...
  • drdairen
  • drdairen
  • 2016年07月07日 09:41
  • 7758

C语言基础:C语言typedef(2) - typedef与宏定义的对比

上一节我们简单的介绍了一下typedef的基本认识, 这次我们来讲解一下它与宏定义的一些对比~~ 有研究的朋友肯定会发现, 使用宏定义一样可以达到类似typedef的效果, 比如: #in...
  • qq350116542
  • qq350116542
  • 2015年01月11日 14:35
  • 526

typedef (包含对复杂函数声明的理解)

typedef的语法描述 在现实生活中,信息的概念可能是长度,数量和面积等。在C语言中,信息被抽象为int、float和 double 等基本数据类型。从基本数据类型名称上,不能够看出其所代表的...
  • yangruibao
  • yangruibao
  • 2011年08月30日 18:13
  • 1937

【转载】关于typedef的用法总结(重点看一下代码简化)

在C还是C++代码中,typedef都使用的很多,在C代码中尤其是多。typedef与#define有些相似,其实是不同的,特别是在一些复杂的用法上,看了网上一些C/C++的学习者的博客,其中有一篇关...
  • as14569852
  • as14569852
  • 2017年09月01日 11:10
  • 111

C语言再学习 -- 关键字typedef

参看:C语言再学习 -- 结构和其他数据形式 参看:C语言再学习 -- 关键字struct(转) typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。比如人们常常使用 typedef ...
  • qq_29350001
  • qq_29350001
  • 2016年12月26日 10:15
  • 1078

C语言 typedef的使用

一、typedef作用简介 * 我们可以使用typedef关键字为各种数据类型定义一个新名字(别名)。 1 #include 2 3 typedef int Integer; ...
  • VNanyesheshou
  • VNanyesheshou
  • 2016年01月10日 20:52
  • 619

解决类型重复定义(typedef)的方法

在写代码过程中,在调用两个工程时,
  • wsbeibei
  • wsbeibei
  • 2014年11月18日 10:29
  • 2435

typedef重定义错误

记录一个问题。 Item.h文件: typedef int Item; Link.h文件: #include "Item.h" void LINKinit(int); ...
  • wangst4321
  • wangst4321
  • 2013年01月10日 10:23
  • 2393
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C语言中的复杂声明和定义,以及如何使用typedef进行简化
举报原因:
原因补充:

(最多只允许输入30个字)