阅读复杂的C类型声明,通常采用右左法则,也就是Clockwise/Spiral Rule (顺时针/螺旋法则)。 本文将首先介绍工具(cdecl)(个人比较偏好使用工具提高学习和工作效率),然后中英文对照翻译一下著名的文章The ``Clockwise/Spiral Rule''

1. 工具(cdecl)

1.1 命令行(cdecl)

在Ubuntu上,使用如下命令安装cdecl。

$ sudo apt-get install cdecl

如何使用cdecl,举几个例子:

$ cdecl -i
Type `help' or `?' for help
cdecl> explain int *foo[]
declare foo as array of pointer to int
cdecl>
cdecl> explain int (*foo)[]
declare foo as pointer to array of int
cdecl>
cdecl> explain void (*signal(int, void (*)(int)))(int);
declare signal as function (int, pointer to function (int) returning void) returning pointer to function (int) returning void
cdecl>
cdecl> exit
$

注意: 阅读C++类型声明,请使用工具c++decl。

1.2 在线服务 (https://cdecl.org/

这个在线服务实在是太贴心了,体验截图如下:

2. 文章(The ``Clockwise/Spiral Rule"

[This was posted to comp.lang.c by its author, David Anderson, on 1994-05-06.]

The ``Clockwise/Spiral Rule'' | 顺时针/螺旋法则
By David Anderson
There is a technique known as the ``Clockwise/Spiral Rule'' which enables any C
programmer to parse in their head any C declaration!
C程序员们如果使用"顺时针/螺旋法则",就可以看懂任何C语言类型声明。
There are three simple steps to follow:
一共分为三步,描述如下:
1. Starting with the unknown element, move in a spiral/clockwise direction;
when ecountering the following elements replace them with the corresponding
english statements:
从未知元素(变量名?)开始,沿着顺时针螺旋移动。每次遇到以下几种符号,把它换成相应的句子:
[X] or []
=> Array X size of... or Array undefined size of... (type1, type2)
=> function passing type1 and type2 returning... *
=> pointer(s) to... 2. Keep doing this in a spiral/clockwise direction until all tokens have been
covered.
反复地沿着顺时针/螺旋方向做上述动作,直到所有的符号都被遍历到;
3. Always resolve anything in parenthesis first!
如果遇到括号,一定要先确定括号内的类型。
Example #1: Simple declaration | 简单声明
+-------+
| +-+ |
| ^ | |
char *str[10];
^ ^ | |
| +---+ |
+-----------+ Question we ask ourselves: What is str?
问题: str是什么? ``str is an...
``str的类型是... We move in a spiral clockwise direction starting with `str' and the first
character we see is a `[' so, that means we have an array, so...
从"str"开始,沿顺时针做螺旋移动,遇到的第一个符号是"["。所以,它是一个数组。所以...
``str is an array 10 of...
``str是一个元素类型为...的长度为10的数组 ...
Continue in a spiral clockwise direction, and the next thing we encounter
is the `*' so, that means we have pointers, so...
继续沿顺时针做螺旋移动,下一个遇到的符号是'*'。这意味着有指针。所以...
``str is an array 10 of pointers to...
``str是一个元素类型为指向...的指针的长度为10的数组 ... Continue in a spiral direction and we see the end of the line (the `;'),
so keep going and we get to the type `char', so...
继续沿顺时针做螺旋移动,这一行最后的符号是';', 继续沿顺时针做螺旋移动,遇到类型'char',所以 ... ``str is an array 10 of pointers to char''
``str是一个元素类型为指向char类型的指针的长度为10的数组 We have now ``visited'' every token; therefore we are done!
我们已经"遍历"了每一个符号。收工!
Example #2: Pointer to Function declaration | 函数指针声明

                     +--------------------+
| +---+ |
| |+-+| |
| |^ || |
char *(*fp)( int, float *);
^ ^ ^ || |
| | +--+| |
| +-----+ |
+------------------------+ Question we ask ourselves: What is fp?
问题: fp是什么类型? ``fp is a... Moving in a spiral clockwise direction, the first thing we see is a `)';
therefore, fp is inside parenthesis, so we continue the spiral inside the
parenthesis and the next character seen is the `*', so...
沿顺时针做螺旋移动,第一个遇到的是")"。我们发现,fp被放在一个()内。我们必须先留在()内,
继续沿着顺时针做螺旋移动。下一个遇到的符号是"*"。所以...
``fp is a pointer to...
``fp是一个指向...的指针 We are now out of the parenthesis and continuing in a spiral clockwise
direction, we see the `('; therefore, we have a function, so...
离开了括号,继续沿顺时针做螺旋移动。我们遇到"("。因此,这是一个函数。所以... ``fp is a pointer to a function passing an int and a pointer to
float returning...
``fp是一个指向函数的指针,函数参数为一个int型和一个float型指针,返回值为... Continuing in a spiral fashion, we then see the `*' character, so...
继续沿顺时针做螺旋移动,遇到了"*",所以... ``fp is a pointer to a function passing an int and a pointer to
float returning a pointer to...
``fp是一个指向函数的指针,函数参数为一个int型和一个float型指针,返回值为
一个指向...的指针
Continuing in a spiral fashion we see the `;', but we haven't visited all
tokens, so we continue and finally get to the type `char', so...
继续沿顺时针做螺旋移动,遇到了";",但是我们还没有遍历完所有符号,于是继续绕圈圈,
最后遇到类型"char", 所以... ``fp is a pointer to a function passing an int and a pointer to
float returning a pointer to a char''
``fp是一个指向函数的指针,函数参数为一个int型和一个float型指针,返回值为
一个指向char类型的指针"
Example #3: The ``Ultimate'' | 终极难题

                      +-----------------------------+
| +---+ |
| +---+ |+-+| |
| ^ | |^ || |
void (*signal(int, void (*fp)(int)))(int);
^ ^ | ^ ^ || |
| +------+ | +--+| |
| +--------+ |
+----------------------------------+ Question we ask ourselves: What is `signal'?
问题: signal是什么?
Notice that signal is inside parenthesis, so we must resolve this first!
注意signal在括号内,于是我们必须先搞清楚括号内的类型! Moving in a clockwise direction we see `(' so we have...
沿着顺时针移动,遇到"(",所以... ``signal is a function passing an int and a...
``signal 是一个函数,该函数的参数为一个整数和... Hmmm, we can use this same rule on `fp', so... What is fp? fp is also inside
parenthesis so continuing we see an `*', so...
嗯嗯,我们可以用同样的规则来确定fp的类型,那么...fp是什么类型? fp也在括号内。所以,继续绕圈圈,
我们遇见"*"。所以... fp is a pointer to...
fp是一个指向...的指针 Continue in a spiral clockwise direction and we get to `(', so...
继续沿顺时针方向绕圈圈,我们遇到'(',所以... ``fp is a pointer to a function passing int returning...''
``fp是一个指向一个参数为int,返回值是...的函数的指针 Now we continue out of the function parenthesis and we see void, so...
离开函数参数的括号之后,我们遇见void。所以... ``fp is a pointer to a function passing int returning nothing (void)''
``fp是一个指向参数为int,什么也不返回(返回void)的函数的指针。 We have finished with fp so let's catch up with `signal', we now have...
解析fp部分结束了。回到signal的类型。目前是... ``signal is a function passing an int and a pointer to a function
passing an int returning nothing (void) returning...
``signal是一个参数为一个int和一个指向一个参数为int,什么也不返回的函数的指针,
返回值为...的函数。 We are still inside parenthesis so the next character seen is a `*', so...
我们还在括号内。下一个符号是"*"。所以... ``signal is a function passing an int and a pointer to a function
passing an int returning nothing (void) returning a pointer to...
``signal是一个参数位一个int和一个指向一个参数为int,什么也不返回的函数的指针,
  返回值为指向...的指针的函数。 We have now resolved the items within parenthesis, so continuing clockwise,
we then see another `(', so...
现在,括号内的类型已经解决了。继续绕圈圈,遇到另一个"("。所以... ``signal is a function passing an int and a pointer to a function
passing an int returning nothing (void) returning a pointer to
a function passing an int returning...
``signal是一个参数位一个int和一个指向一个参数为int,什么也不返回的函数的指针,
  返回值为指向一个参数为int,返回值为...的函数的指针的函数。 Finally we continue and the only thing left is the word `void', so the final
complete definition for signal is:
继续绕圈圈,最后一个符号就是最左边的"void"。所以,signal的最终定义就是: ``signal is a function passing an int and a pointer to a function
passing an int returning nothing (void) returning a pointer to
a function passing an int returning nothing (void)''
``signal是一个参数位一个int和一个指向一个参数为int,什么也不返回的函数的指针,
  返回值为指向一个参数为int,什么也不返回的函数的指针的函数。" The same rule is applied for const and volatile. For Example:
同样的规则可用于const和volatile。例如: const char *chptr; Now, what is chptr??
那么现在,chptr是什么类型?? ``chptr is a pointer to a char constant''
``chptr是一个指向char型常量的指针变量。" How about this one:
下面这个呢? char * const chptr; Now, what is chptr??
现在,chptr又是什么?? ``chptr is a constant pointer to char''
``chptr是一个指向char型变量的指针常量。" Finally:
最后一道题:
volatile char * const chptr; Now, what is chptr??
现在,chptr又是什么类型??
``chptr is a constant pointer to a char volatile.''
``chptr是一个指向char volatile型变量的指针常量。"
Practice this rule with the examples found in K&R II on page 122.
请使用此规则对K&R II第122页上的例子做练习。

Copyright © 1993,1994 David Anderson
This article may be freely distributed as long as the author's name and this notice are retained.

本文可以自由地发布,唯一需要保留的就是作者的名字和这条提示。

参考文献

结束语: 此时无声胜有声

cdecl> explain void (*signal(int, void (*)(int)))(int);
declare signal as function (int, pointer to function (int) returning void) returning pointer to function (int) returning void

如何阅读复杂的C类型声明的更多相关文章

  1. 【转】如何理解c和c++的复杂类型声明

    转自:http://blog.chinaunix.net/space.php?uid=22889411&do=blog&id=59667 曾经碰到过让你迷惑不解.类似于int * (* ...

  2. Go语言规格说明书 之 类型声明(Type declarations)

    go version go1.11 windows/amd64 本文为阅读Go语言中文官网的规则说明书(https://golang.google.cn/ref/spec)而做的笔记,完整的介绍Go语 ...

  3. 【C语言】复杂类型声明

    原文地址: http://blog.csdn.net/wangweixaut061/article/details/6549768 原文不让转载,但实在是有用,就拷贝了一小部分过来.全文请点开链接. ...

  4. C/C++复杂类型声明

    曾经碰到过让你迷惑不解.类似于int * (* (*fp1) (int) ) [10];这样的变量声明吗?本文将由易到难,一步一步教会你如何理解这种复杂的C/C++声明.   我们将从每天都能碰到的较 ...

  5. 如何理解c和c++的复杂类型声明

    曾经碰到过让你迷惑不解.类似于int * (* (*fp1) (int) ) [10];这样的变量声明吗?本文将由易到难,一步一步教会你如何理解这种复杂的C/C++声明. 我们将从每天都能碰到的较简单 ...

  6. callable object与新增的function相关 C++11中万能的可调用类型声明std::function<...>

    在c++11中,一个callable object(可调用对象)可以是函数指针.lambda表达式.重载()的某类对象.bind包裹的某对象等等,有时需要统一管理一些这几类对象,新增的function ...

  7. 生成跨语言的类型声明和接口绑定的工具(Djinni )

    Djinni 是一个用来生成跨语言的类型声明和接口绑定的工具,主要用于 C++ 和 Java 以及 Objective-C 间的互通. 示例接口定义文件: # Multi-line comments ...

  8. [Effective Modern C++] Item 5. Prefer auto to explicit type declarations - 相对显式类型声明,更倾向使用auto

    条款5 相对显式类型声明,更倾向使用auto 基础知识 auto能大大方便变量的定义,可以表示仅由编译器知道的类型. template<typename It> void dwim(It ...

  9. !DOCTYPE html文档类型声明简写 HTML5 DOCTYPE缩写

    html5之!DOCTYPE html文档类型声明简写,在HTML5中DOCTYPE简写非常重要. 一.概述   -   TOP 让CSS样式表生效,DOCTYPE声明是必须的,以前TABLE布局的网 ...

随机推荐

  1. Linux Guard Service - 杀死守护进程

    杀死某个子进程 杀死守护进程的子进程后,改进程会变为僵尸进程 14087 ? Ss 0:00 ./test4-1 14088 ? S 0:00 \_ ./test4-1 14089 ? S 0:00 ...

  2. 基于Quartz.net的远程任务管理系统 二

    紧接着上一篇.上一篇讲了表设计与ADO.Net基本操作.接下来,就来说说怎么动态来添加Job任务及清理过期任务吧. 首先,先理一下思路,做事情要先把思绪理清了,然后下手就快准狠了.下面是我的思路:做一 ...

  3. leetcode 移除元素

    给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成 ...

  4. WPF透明窗体不支持缩放解决方案

    方案一 WPF中的无边框透明窗体,由于没有边并且透明,窗体无法进行缩放操作,今天来讲解如何解决这个问题. 先说一下思路,我们先手为该窗体添加4个边,4个角用于缩放操作,然后再为他们写事件,完成拖放操作 ...

  5. WCF快速上手(二)

    服务端是CS程序,客户端(调用者)是BS程序 一.代码结构: 二.服务接口Contract和实体类Domain INoticeService: using Domain; using System; ...

  6. API Test Postman接口测试之高级篇2

    API Test  Postman接口测试之高级篇2 一.继承父类的设置: 二.导出及导入: 三.分享文档: 四.发布接口文档: 五.常用脚本: 右边框选的是一些常用的脚本,postman提供的,可以 ...

  7. Android Preferences: How to load the default values when the user hasn't used the preferences-screen?

    在启动 preferences 之前,默认值并不能生效.第一次运行程序时候,默认值没生效,然后获取的 preferences 的值就是错误的. 解决办法是在程序开始时加一行代码使默认值生效. Pref ...

  8. JZOJ6096 森林

    题目传送门 Description ​我们定义对一棵树做一次变换的含义为:当以 1 号节点为根时,交换两个互相不为祖先的点的子树: ​一棵树的权值为对它进行至多一次变换能得到的最大直径长度: ​初始时 ...

  9. ASPNETPager常用属性(近来用到分页属性)

    ASPNETPager常用属性 建议去封装好,然后调用这样比较容易 <webdiyer:aspnetpager id="AspNetPager1" runat="s ...

  10. ElasticSearch安装拼音插件 elasticsearch-analysis-pinyin

    elasticsearch-analysis-pinyin 是 ElasticSearch的拼音插件.强大的功能支持拼音等的搜索 1.下载源代码 源码地址https://github.com/medc ...