C++雾中风景番外篇:理解C++的复杂声明与声明解析

在学习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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大数据文摘

超8千Star,火遍Github的Python反直觉案例集!

Python,是一个设计优美的解释型高级语言,它提供了很多能让程序员感到舒适的功能特性。

962
来自专栏Vamei实验室

Java基础04 封装与接口

总结之前的内容,对象(object)指代某一事物,类(class)指代象的类型。对象可以有状态和动作,即数据成员和方法。 到现在为止,数据成员和方法都是同时开放...

1907
来自专栏java学习

Java每日一练(2017/8/16)

最新通知 ●回复"每日一练"获取以前的题目! ●【新】Android视频更新了!(回复【安卓视频】获取下载链接) ●【新】Ajax知识点视频更新了!(回复【学习...

2866
来自专栏Petrichor的专栏

eager evaluation (及早求值) & lazy evaluation (惰性求值)

及早求值,也被称为贪婪求值(greedy evaluation)或严格求值,是多数传统编程语言的求值策略。

2663
来自专栏微信公众号:Java团长

Java基础04 封装与接口

总结之前的内容,对象(object)指代某一事物,类(class)指代象的类型。对象可以有状态和动作,即数据成员和方法。

1342
来自专栏take time, save time

python 爬虫 入门 commit by commit -- commit6

代码你可以在https://github.com/rogerzhu/relwarcDJ 上得到,并且带有我完整的commit记录。

600
来自专栏菜鸟致敬

Python匿名函数快速入门

匿名函数,顾明思义就是一种隐匿了名字的函数。Python中使用lambda语法定义匿名函数,只需用表达式而无需申明。

651
来自专栏北京马哥教育

鲜为人知的 Python 语法

所有人(好吧,不是所有人)都知道 python 是一门用途广泛、易读、而且容易入门的编程语言。

861
来自专栏Java学习网

Java中提高代码复用性的方法及规范总结

一个好的程序员一定是用最少的代码实现最多的功能,代码复用是程序员所追求和期望的,这也是Java语言所提倡的;根据以往开发经验整理了一些能够提高代码复用性方法和规...

3917
来自专栏企鹅号快讯

Java与C/C加加的区别

Java 是由 C++发展而来的,保留了 C++的大部分内容,其编程方式类似于 C++。但 Java 的句法更清晰、规模更小、更易学。Sun 公司对多种程序设计...

7546

扫码关注云+社区