转自知乎的一段解释:

作者:知乎用户
链接:https://www.zhihu.com/question/29798061/answer/144423125
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

不太想谈#define, 在题主的例子的这种用法里, 它就是个文本替换工具, 预处理器完成的, 无脑替换, 跟word里的replace一模一样, 不关编译器的事. 我想谈一下typedef.

搞懂了c++创始人写的<the design and evolution of cpp>中的下面这个例子, 有助于你理解typdef

typedef int P();
typedef int Q();
class X{
static P(); //等价于static int P();
static Q();
};
 

这是一个极好的例子, 先问一下 typedef int P()到底做了什么? 其实是:

declares a function type P as returning an int and taking no arguments.

1. 官方定义
初次接触此类typedef用法的程序员直观上理解这个例子比较困难, 我们来看一下typedef的官方定义:

Typedef does not work like typedef [type] [new name]. The [new name] part does not always come at the end.

You should look at it this way: if [some declaration] declares a variable, typedef [same declaration] would define a type.

看我标黑的这句话, 总结一下就是: 任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型不管这个声明中的标识符号出现在中间还是最后.

2. 隐藏技能

typedef 定义的新类型, 使用时可以省略括号.

什么意思?

typedef int NUM;
NUM a = ; //也可以写成NUM(a) = 10;

3. 举例
先从初级的开始:

整形

typedef int NUM;

结构体

typedef struct{int a;} STRTCT;

指针

typedef int *p;//定义了一个名为p的指针类型, 它指向int (中文描述指针好累)

  

接下来是高级的(注意标识符不一定在最后):
数组

typedef int A[]; // 定义一个名为A的ints数组的类型

函数

typedef int f(); // 定义一个名为f, 参数为空, 返回值为int的函数类型

typedef int g(int); // 定义一个名为g, 含一个int参数, 返回值为int行的函数类型

现在回过头看:

typedef int P();

static P(Q); 
 

应该就比较好理解了, P是一个新定义的function类型, 它返回值为int, 无参数
根据我的第2点说明, P(Q); 实际上等价于P Q, 声明Q是一个返回值为int, 无参数的函数.

这玩意有什么用呢?
我们都知道C++语言里, 函数都是先声明后使用的(除非在使用之前定义), 看以下例子

 
#include <iostream>
#include <stdio.h>
#include <string> typedef int P(); // 简单的
typedef void Q(int *p, const std::string& s1, const std::string& s2, size_t size, bool is_true); // 复杂的
class X {
public:
P(eat_shit); // 等价于声明`int eat_shit();`
Q(bullshit); // 等价于声明`void bullshit(int *p, const string& s1, const string& s2, size_t size, bool is_true);`
}; int main() {
X *xx;
printf("shit ret: %d\n", xx->eat_shit());
int a[] = {, , , , };
xx->bullshit(a, "foo", "bar", sizeof(a)/sizeof(int), true);
} int X::eat_shit() {
return ;
} void X::bullshit(int *p, const std::string& s1, const std::string& s2, size_t size, bool is_true) {
std::cout << "s1: " << s1 << ", s2: " << s2 << ", size: " << size << std::endl;
printf("elems:\n");
for(int i = ; i < size; i++) {
printf("%d %s", *p++, (i == size-) ? "" : ",");
}
printf("\n");
}

理解了上面的再看下面这段:

理解复杂的定义和声明:

在阅读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[];
int (*pArr) [];
pArr = &arr;

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

int (*pfunc) (int);

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

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

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

int (*pfunc) (int);
int (*arr[]) (int);
arr[] = 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[]) (int, char*); // 在之前定义的前面加入typedef,然后将变量名a替换成类型名A

最新文章

  1. 【BZOJ】3997: [TJOI2015]组合数学
  2. java.net.UnknownHostException: Unable to resolve host &quot;api102.meishi.cc&quot;: No address associated with hostname
  3. Java中的List操作
  4. 作业七:团队项目——Alpha版本冲刺阶段-13
  5. mybatis中的mapper.xml
  6. bzoj 2746: [HEOI2012]旅行问题 AC自动机fail树
  7. 安装Discuz!论坛时提示“mysqli_connect() 不支持 advice_mysqli_connect”
  8. 2014-7 Andrew Ng 自动化所报告听后感
  9. Django HTTP处理流程(自我总结)
  10. iphone与安卓的兼容性问题汇总
  11. Spring Dubbo 开发笔记
  12. jQuery插件ImgAreaSelect 实例讲解二
  13. Ecshop商品描述上传中文名图片无法显示解决方法
  14. python numpy模块使用笔记(更新)
  15. 解决修改css或js文件后,浏览器缓存未更新问题
  16. nginx配置文件结构,语法,配置命令解释
  17. Teamwork(The sixth day of the team)
  18. unity 查看prefab层次
  19. GitHub原来也可以用SVN客户端的.
  20. 静态路由解决双外卡,PC做路由器的实现

热门文章

  1. LoadRunner(5)
  2. python打包工具distutils、setuptools的使用
  3. Linux的基础使用命令
  4. java线程基础巩固---如何捕获线程运行期间的异常
  5. Python3+Appium学习笔记02-环境配置(下)
  6. axios 用 params/data 发送参数给 springboot controller,如何才能正确获取
  7. python+Appium自动化:元素等待时间
  8. runloop 小记
  9. LNMP原理
  10. C++之带有默认参数值的构造函数