You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
13 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 01 | 重新认识C++:生命周期和编程范式
你好我是Chrono。
今天是专栏的第一节正式课。我想既然你选择了这个专栏你就应该已经对C++有所了解了,而且有过一些开发经验,甚至还掌握了一两种其他的语言。
苏轼有诗云“不识庐山真面目只缘身在此山中。”学习C++很容易被纷繁复杂的语法细节所吸引、迷惑所以我决定从“生命周期”和“编程范式”这两个不太常见的角度来“剖析”一下C++站在一个更高的层次上审视这门“历久弥新”的编程语言帮你认清楚C++最本质的东西。
这样,今后在写程序的时候,你也会有全局观或者说是大局观,更能从整体上把握程序架构,而不会迷失在那些琐碎的细枝末节里。
现在我们先来了解下C++的生命周期。
## C++程序的生命周期
如果你学过一点软件工程的知识,就一定知道“瀑布模型”,它定义了软件或者是项目的生命周期——从需求分析开始,经过设计、开发、测试等阶段,直到最终交付给用户。
“瀑布模型”把软件的生命周期分成了多个阶段,每个阶段之间分工明确,相互独立,而且有严格的先后次序,是一个经典的开发模型。虽然它已经不再适合瞬息万变的互联网产业了,但仍然有许多值得借鉴和参考的地方。
那么说了半天“瀑布模型”跟C++程序有什么关系呢?
其实从软件工程的视角来看一个C++程序的生命周期也是“瀑布”形态的也可以划分出几个明确的阶段阶段之间顺序衔接使用类似的方法就可以更好地理解C++程序的运行机制,帮助我们写出更好的代码。
不过因为C++程序本身就已经处在“开发”阶段了所以不会有“需求分析”“设计”这样的写文档过程。所以一个C++程序从“诞生”到“消亡”,要经历这么几个阶段:**编码Coding、预处理Pre-processing、编译Compiling和运行Running**。
![](https://static001.geekbang.org/resource/image/b6/4c/b6696db53248d122cd57ddd9a8e52a4c.jpg "C++程序的四个阶段")
## C++程序的四个阶段
**编码**应该是你很熟悉的一个阶段了,这也是我们“明面”上的开发任务最集中的地方。
在这个阶段,我们的主要工作就是在编辑器里“敲代码”:定义变量,写语句,实现各种数据结构、函数和类。
**编码阶段是C++程序生命周期的起点也是最重要的阶段是后续阶段的基础直接决定了C++程序的“生存质量”**。
显然,在编码阶段,我们必须要依据一些规范,不能“胡写一气”,**最基本的要求是遵循语言规范和设计文档,再高级一点的话,还有代码规范、注释规范、设计模式、编程惯用法,等等**。现在市面上绝大部分的资料都是在教授这个阶段的知识,在专栏后面,我也会重点讲一讲我在这方面的一些经验积累。
那么,编码阶段之后是什么呢?
可能对你来说稍微有点陌生,这个阶段叫**预处理**。
所谓的预处理,其实是相对于下一个阶段“编译”而言的,在编译之前,预先处理一下源代码,既有点像是编码,又有点像是编译,是一个中间阶段。
预处理是C/C++程序独有的阶段其他编程语言都没有这也算是C/C++语言的一个特色了。
在这个阶段发挥作用的是预处理器Pre-processor。它的输入是编码阶段产生的源码文件输出是经过“预处理”的源码文件。“预处理”的目的是文字替换用到的就是我们熟悉的各种预处理指令比如#include、#define、#if等实现“**预处理编程**”。这部分内容,我后面还会展开讲。
不过,你要注意的是,它们都以符号“#”开头虽然是C++程序的一部分但严格来说不属于C++语言的范畴,因为它走的是预处理器。
在预处理之后C++程序就进入了**编译**阶段更准确地说应该是“编译”和“链接Linking”。简单起见我统一称之为“编译”。
在编译阶段C++程序——也就是经过预处理的源码——要经过编译器和链接器的“锤炼”生成可以在计算机上运行的二进制机器码。这里面的讲究是最多的也是最复杂的C++编译器要分词、语法解析、生成目标码,并尽可能地去优化。
在编译的过程中编译器还会根据C++语言规则检查程序的语法、语义是否正确发现错误就会产生“编译失败”。这就是最基本的C++“静态检查”。
在处理源码时由于编译器是依据C++语法检查各种类型、函数的定义,所以,在这个阶段,我们就能够以编译器为目标进行编程,有意识地控制编译器的行为。这里有个新名词,叫“**模板元编程**”。不过,“模板元编程”比较复杂,不太好理解,属于比较高级的用法,稍后我会再简单讲一下。
编译阶段之后有了可执行文件C++程序就可以跑起来了,进入**运行**阶段。这个时候“静态的程序”被载入内存由CPU逐条语句执行就形成了“动态的进程”。
运行阶段也是我们最熟悉的了。在这个阶段我们常做的是GDB调试、日志追踪、性能分析等然后收集动态的数据、调整设计思路再返回编码阶段重走这个“瀑布模型”实现“螺旋上升式”的开发。
![](https://static001.geekbang.org/resource/image/9c/df/9cb2036ae3dbda30a00d58bdd4834ddf.jpg)
好了梳理清楚了C++程序生命周期的四个阶段,你可以看到,这和软件工程里的“瀑布模型”很相似,这些阶段也是职责明确的,前一个阶段的输出作为后一个阶段的输入,而且每个阶段都有自己的工作特点,我们可以有针对性地去做编程开发。
还有别忘了软件工程里的“蝴蝶效应”“混沌理论”大概意思是一个Bug在越早的阶段发现并解决它的价值就越高一个Bug在越晚的阶段发现并解决它的成本就越高。
所以,依据这个生命周期模型,**我们应该在“编码”“预处理”“编译”这前面三个阶段多下功夫消灭Bug优化代码尽量不要让Bug在“运行”阶段才暴露出来**,也就是所谓的“把问题扼杀在萌芽期”。
## C++语言的编程范式
说完了C++程序的生命周期再来看看C++的编程范式Paradigm
什么是编程范式呢?
关于这个概念,没有特别权威的定义,我给一个比较通俗的解释:**“编程范式”是一种“方法论”,就是指导你编写代码的一些思路、规则、习惯、定式和常用语**。
编程范式和编程语言不同,有的范式只能用于少数特定的语言,有的范式却适用于大多数语言;有的语言可能只支持一种范式,有的语言却可能支持多种范式。
那么你一定知道或者听说过C++是一种**多范式**的编程语言。具体来说现代C++11/14以后支持“面向过程”“面向对象”“泛型”“模板元”“函数式”这五种主要的编程范式。
其中,**“面向过程”“面向对象”是基础,支撑着后三种范式**。我画了一个“五环图”,圆环重叠表示有的语言特性会同时应用于多种范式,可以帮你理解它们的关系。
![](https://static001.geekbang.org/resource/image/6e/87/6ef13308109b2d1795e43c5206c32687.jpg "C++编程范式的“五环图”")
接下来,我就和你详细说说这五种编程范式。
## C++语言的五种范式
**面向过程**是C++里最基本的一种编程范式。它的核心思想是“命令”,通常就是顺序执行的语句、子程序(函数),把任务分解成若干个步骤去执行,最终达成目标。
面向过程体现在C++中就是源自它的前身——C语言的那部分比如变量声明、表达式、分支/循环/跳转语句,等等。
**面向对象**是C++里另一个基本的编程范式。**它的核心思想是“抽象”和“封装”**,倡导的是把任务分解成一些高内聚低耦合的对象,这些对象互相通信协作来完成任务。它强调对象之间的关系和接口,而不是完成任务的具体步骤。
在C++里面向对象范式包括class、public、private、virtual、this等类相关的关键字还有构造函数、析构函数、友元函数等概念。
**泛型编程**是自STL标准模板库纳入到C++标准以后才逐渐流行起来的新范式,核心思想是“一切皆为类型”,或者说是“参数化类型”“类型擦除”,使用模板而不是继承的方式来复用代码,所以运行效率更高,代码也更简洁。
在C++里泛型的基础就是template关键字然后是庞大而复杂的标准库里面有各种泛型容器和算法比如vector、map、sort等等。
与“泛型编程”很类似的是**模板元编程**这个词听起来好像很新其实也有十多年的历史了不过相对于前三个范式来说确实“资历浅”。它的核心思想是“类型运算”操作的数据是编译时可见的“类型”所以也比较特殊代码只能由编译器执行而不能被运行时的CPU执行。
在讲编译阶段的时候我也说了模板元编程是一种高级、复杂的技术C++语言对它的支持也比较少更多的是以库的方式来使用比如type\_traits、enable\_if等。
最后一个**函数式**它几乎和“面向过程”一样古老但却直到近些年才走入主流编程界的视野。所谓的“函数式”并不是C++里写成函数的子程序,而是数学意义上、无副作用的函数,**核心思想是“一切皆可调用”,通过一系列连续或者嵌套的函数调用实现对数据的处理**。
函数式早在C++98时就有少量的尝试bind1st/bind2nd等函数对象但直到C++11引入了Lambda表达式它才真正获得了可与其他范式并驾齐驱的地位。
好了介绍完了这五种编程范式你可以看到它们基本覆盖了C++语言和标准库的各个成分,彼此之间虽然有重叠,但在理念、关键字、实现机制、运行阶段等方面的差异还是非常大的。
这就好像是五种秉性不同的“真气”在C++语言里必须要有相当“浑厚”的内力才能把它们压制、收服、炼化,否则的话,一旦运用不当,就很容易“精神分裂”“走火入魔”。
**说得具体一点,就是要认识、理解这些范式的优势和劣势,在程序里适当混用,取长补短才是“王道”**。
说到这儿,你肯定很关心,该选择哪种编程范式呢?
拿我自己来说,我的出发点是“**尽量让周围的人都能看懂代码**”,所以常用的范式是“过程+对象+泛型”,再加上少量的“函数式”,慎用“模板元”。
对于你来说,我建议根据自己的实际工作需求来决定。
我个人觉得,**面向过程和面向对象是最基本的范式是C++的基础,无论如何都是必须要掌握的**,而后三种范式的学习难度就大一些。
如果是开发直接面对用户的普通应用Application那么你可以再研究一下“泛型”和“函数式”就基本可以解决90%的开发问题了如果是开发面向程序员的库Library那么你就有必要深入了解“泛型”和“模板元”优化库的接口和运行效率。
当然,还有一种情况:如果你愿意挑战“最强大脑”,那么,“模板元编程”就绝对是你的不二选择(笑)。
## 小结
今天是开篇第一课我带你从“生命周期”和“编程范式”这两个特别的角度深度“透视”了一下C++,做个简单小结:
1. C++程序的生命周期包括编码、预处理、编译、运行四个阶段,它们都有各自的特点;
2. 虽然我们只写了一个C++程序但里面的代码可能会运行在不同的阶段分别由预处理器、编译器和CPU执行
3. C++支持面向过程、面向对象、泛型、模板元、函数式共五种主要的编程范式;
4. 在C++里可以“无缝”混用多范式编程,但因为范式的差异比较大,必须小心谨慎,避免导致混乱。
## 课下作业
最后是课下作业时间,给你留两个思考题:
1. 你是怎么理解C++程序的生命周期和编程范式的?
2. 试着从程序的生命周期和编程范式的角度把C++和其他语言例如Java、Python做个比较说说C++的优点和缺点分别是什么。
欢迎你在留言区写下你的思考和答案,如果觉得对你有所帮助,也欢迎把今天的内容分享给你的朋友,我们下节课见。
![](https://static001.geekbang.org/resource/image/4a/a4/4a40e5b8c618ab38945c1346ab3878a4.jpg)