在学习C系列语言的过程之中,理解C/C++的复杂声明一直是初学者很困扰的问题。笔者初学之时也深受困扰,对很多规则死记硬背。后续在阅读《C专家编程》之后,尝试在编译器的角度来理解C/C++的声明解析,并且编写代码将这部分逻辑串联起来,之后再看到许多看似复杂的声明,也能够很好的理解和消化了。

1.复杂的声明

在编写C/C++代码时偶尔能看到如下的复杂声明: float(*(*e[10])(int*))[5]。我想你的第一反应一定是:MMP。虽然我们在实际工作之中是很少出现这种极其复杂的声明逻辑,同时也不提倡使用这样的声明。但是学会理解和解析这类复杂的声明逻辑,可以更好的理解C/C++之中诸个关键词是如何进行组织,来表达逻辑的,也能更好的理解各个关键词的使用方式。

比如之前笔者写的一篇文章之中整理了C/C++之中const关键词的用法 《C++雾中风景3:const用法的小结》的之中通过口诀的方式记忆const关键字在声明之中的先后顺序来厘清不同的逻辑。这种方式不仅效率低下,而且并没有理解到为什么不同的先后顺序会对声明逻辑产生影响。在本篇文章之中,笔者尝试带大家忘记这些口诀,从编译器的角度去理解编译器是如何处理这些声明的逻辑,知其然而知其所以然。

2.优先级规则

C/C++的声明模型是及其晦涩的,笔者简单统计了涉及声明模型的关键字如const,volatile等大概有十个左右。更为复杂的是在C/C++之中这些关键字的先后顺序与括号可以任意组合并且发生看起来很奇妙的"化学反应"

万变而不离其中,总结出规律之后,再复杂的模型也可以简化成我们可以理解的单元来处理。所以我们先来看看C/C++声明的优先级规则

  • 声明是由标识符,也就是它的名字开始解析的。
  • 获取了声明之后,接下来安装如下优先级别来依次处理声明:

    1. 优先处理括号部分的声明逻辑。

    2. 优先处理后缀操作符,如[],()

    3. 处理前缀操作符,如*,const
  • 后续可以依次从右往左处理之前的声明了。

掌握了上述的优先级规则之后,我们回到本文一开始举的一个小栗子

float(*(*e[10])(int*))[5],我们依照上文的逻辑来解析这个声明。

1.找到声明e,e将作为声明的名字。

2.处理后缀操作符,也就是e代表的是一个容量为10的数组。

3.回到前缀操作符,该数组存储的内容为指针。

4.跳出括号,开始新的一轮的优先级规则,处理后缀操作符(),我们

发现这个指针指向的是一个参数为int*的函数。

5.接着再次回到前缀操作符,所以这个函数返回值依然是一个指针。

6.跳出括号,继续前文的逻辑,我们发现该指针指向了一个内容为float,容量为5的数组。

通过上述栗子我们不难发现,对于声明的处理本质上是一个有限自动机的状态变化过程,所以编译器同样也是按照上述的规律来理解并处理程序的复杂声明的。了解了优先级规则,我们也就不难去实现一个简单的小程序cdecl来处理声明逻辑了。

3.简单的代码实现

通过上述流程的说明,我们很容易想到可以用来保存声明标识符左边的内容,而名字右边的内容则依照优先级规则依次处理。(优先处理数组与函数)。

  • 先分类将要处理声明的种类,并且声明token类型来进行处理
enum type_tag {IDENTIFIER,QUALIFIER,TYPE,POINTER,LPAREN,\
LBRACKET,RPAREN,RBRACKET}; struct token {
type_tag type;
string content;
};
  • 不断读取token,并且压入栈中,直到读取到声明标识符
void read_to_first_identifer() {
gettoken();
while (this_t.type != IDENTIFIER) {
token_stack.push(this_t);
gettoken();
} cout << this_t.content + " is ";
gettoken();
}
  • 按照优先级法则处理逻辑,先右后左,遇到括号弹出之后继续上述逻辑
void deal_with_declarator(){
switch (this_t.type) {
case LBRACKET:deal_with_arrays();break;
case LPAREN:deal_with_function_args();
} deal_with_pointers(); while(!token_stack.empty()) {
if(token_stack.top().type == LPAREN) {
token_stack.pop();
gettoken();
deal_with_declarator();
} else {
cout << token_stack.top().content + " ";
token_stack.pop();
}
} }
  • 处理数组类型的函数
void deal_with_arrays() {
while (this_t.type == LBRACKET) {
cout << "array ";
gettoken();
if(isdigit(this_t.content[0])) {
printf("0....%d of ",atoi(this_t.content.c_str()) - 1);
gettoken();
} gettoken();
}
}
  • 处理函数类型的函数
void deal_with_function_args() {
while(this_t.type != RPAREN) {
gettoken();
}
gettoken(); cout << "function returning ";
}

所以通过上述的代码串联起来,我们就可以简单的完成一个解析C/C++声明的小程序。尝试这个小程序解析笔者在本文提出的示例:



上述实现代码的完整版,笔者放在了自己的github之上,需要的可以自取。《C专家编程》之中也有对应C语言版本,需要的也可以用作参考。

4.小结

厌倦了复杂声明?希望有更友好的声明类型?番外篇当然是为了引出正篇,接下来笔者将会和大家一起来看看,C++为了简化声明的类型系统,做出了那些努力来更加高效的提升程序员的工作效率。A

C++雾中风景番外篇:理解C++的复杂声明与声明解析的更多相关文章

  1. C++雾中风景番外篇2:Gtest 与 Gmock,聊聊C++的单元测试

    正式工作之后,公司对于单元测试要求比较严格.(笔者之前比较懒,一般很少写完整的单测~~).作为一个合格的开发工程师,需要为所编写代码编写适量的单元测试是十分必要的,在实际进行的开发工作之中,TDD(T ...

  2. C++雾中风景番外篇3:GDB与Valgrind ,调试代码内存的工具

    写 C++的同学想必有太多和内存打交道的血泪经验了,常常被 C++的内存问题搅的焦头烂额.(写 core 的经验了)有很多同学一见到 core 就两眼一抹黑,不知所措了.笔者 入"坑&quo ...

  3. C++雾中风景番外篇4:GCC升级二三事

    最近将手头上负责的项目代码从GCC 4.8.2升级到了GCC 8.2.(终于可以使用C++17了,想想后续的开发也是很美好啊~~)不过这个过程之中也遇到了一些稀奇古怪的问题,在这里做一个简单的记录,希 ...

  4. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  5. 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV

    这次博客园的排版彻底残了..高清版请移步: https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程: 给深度学习入门者的Python快速教程 - 基础篇 给深度 ...

  6. python自动化测试应用-番外篇--接口测试1

    篇1                 book-python-auto-test-番外篇--接口测试1 --lamecho辣么丑 1.1概要 大家好! 我是lamecho(辣么丑),至今<安卓a ...

  7. python自动化测试应用-番外篇--接口测试2

    篇2                 book-python-auto-test-番外篇--接口测试2 --lamecho辣么丑 大家好! 我是lamecho(辣么丑),今天将继续上一篇python接 ...

  8. [uboot] (番外篇)uboot之fdt介绍

    http://blog.csdn.net/ooonebook/article/details/53206623 以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为 ...

  9. python之爬虫--番外篇(一)进程,线程的初步了解

    整理这番外篇的原因是希望能够让爬虫的朋友更加理解这块内容,因为爬虫爬取数据可能很简单,但是如何高效持久的爬,利用进程,线程,以及异步IO,其实很多人和我一样,故整理此系列番外篇 一.进程 程序并不能单 ...

随机推荐

  1. 阿里云服务器 ECS Linux 禁止IP 通过 SSH 登录

    这几天买的服务器老是受到黑客攻击被破解登录密码,今天修改了登录规则发现只有固定ip可以访问,其他ip即使有密码也无法登录我的服务器,但是能通过ip访问我的网站,哈哈. 限制 IP SSH 登录解决步骤 ...

  2. Mysql 插入中文错误:Incorrect string value: '\xE7\xA8\x8B\xE5\xBA\x8F...' for column 'course' at row 1

    create table my_user (    id tinyint(4) not null auto_increment,    account varchar(255) default nul ...

  3. ActiveMQ安装与入门程序 & JMS的消息结构

    1.Activemq安装 直接到官网下载:记住apache的官网是域名反过来,比如我们找activemq就是activemq.apache.org. 最新版本要求最低的JDK是8,所以最好在电脑装多个 ...

  4. Java用System读取系统相关信息、环境变量——(六)

    package Java_Test; public class System1 { public static void main(String[] args) { // TODO Auto-gene ...

  5. WPF复制异常问题(OpenClipboard 失败 (异常来自 HRESULT:0x800401D0 (CLIPBRD_E_CANT_OPEN)))

    最近在维护WPF系统的时候发现的问题,刚刚开始自己的电脑都不能重现,后面写日志跟踪才发现问题的所在.问题主要是由于:1.   在程序访问剪切板的时候,有其他程序正在占用剪切板,导致自己的程序无法访问, ...

  6. 【转】Zabbix 3.0 从入门到精通(zabbix使用详解)

    [转]Zabbix 3.0 从入门到精通(zabbix使用详解) 第1章 zabbix监控 1.1 为什么要监控 在需要的时刻,提前提醒我们服务器出问题了 当出问题之后,可以找到问题的根源   网站/ ...

  7. MCS-51单片机存储地址空间划分

    1.前言 MCS-51的存储器有片内RAM.片外RAM 和 ROM 三个空间. MCS-51单片机在物理结构上有四个存储空间 1.片内程序存储器(片内ROM)2.片外程序存储器(片外ROM)3.片内数 ...

  8. Project Euler Problem 10

    Summation of primes Problem 10 The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of ...

  9. 通过Cookie跳过登录验证码【限cookie不失效有用】

    验证码,相信每个写web自动化测试的同学来说,都是个头疼的事,怎么办呢? 方法还是有的,先说今天这种方式,通过cookie绕过登录验证码 思路: 需要你通过抓包工具抓到你登录的cookie 接下来开始 ...

  10. Android开发之深入理解Android Studio构建文件build.gradle配置

    摘要: 每周一次,深入学习Android教程,TeachCourse今天带来的一篇关于Android Studio构建文件build.gradle的相关配置,重点学习几个方面的内容:1.applica ...