重新温习 C++ 中的三驾马车

怎么说,C++ 的学习算是绕不过去的,尤其是对我这个半路出家自学的半吊子,更是如此。

凭借着已有的零零散散的积累,现在能勉强看懂 C++ 中的三驾马车究竟意味着什么了。

先来说第一驾马车:C

是的,C++ 是构建在 C 之上的,而且是 C 的一个超集。超集意味着什么呢?不仅仅是继承了 C 当中的大部分类型系统、语法和控制结构,而且继承了基于 C 编写的大量函数库。这使得 C++ 诞生之初就具备了强大的能力。

这里多说一句,C 的核心是过程性和结构化编程,可以完美展示算法的具体实现,也可以强有力的控制和组织代码结构。这是编程思维的第一次完美显现,脱离于汇编语言的冗杂细节,直接将编程思维灌注在 C 语言本身的设计规范中。

再说第二驾马车:面向对象

事实上,在 C++ 诞生之初,这里的 ++ 就只有面向对象而已。面向对象可以与面向过程做一个对比:对象是数据,过程是算法。对一个程序而言,数据和算法缺一不可,区别只是以哪个为核心。如果深入到汇编之下的机器指令当中去的话,算法其实也是数据。但是,在概念上将数据和算法区分开来,对人类的思考是极为友好的一件事情。

为什么面向对象会发展为一种新的编程思维呢?这和计算机底层统统是数据这一事实是否有关系呢?不知道。但能知道的一点是,面向对象比面向过程更自然。当我们想要解决一个问题的时候,我们其实是深陷在细节当中,这个时候,为了解剖问题,比较容易做到的是将问题本身拆解为一个个彼此独立的概念,概念之间可以彼此嵌套,组合,进而发生相互作用,用交换信息的方式进行通讯。

面向过程强调的是怎么做,目的是要完成任务。这并没有错,但难度往往挺大,但这难不倒天才。所以早期程序员只需要这类思考也能做出足够牛X的程序和操作系统。

但到了更大规模的应用程序的开发时,因为需要更多的人力,团队的平均水平必然降低,为了保证任务的完成,出现了可以自下而上进行工作的面向对象式的编程思维,或者说编程范式。面向对象提供了这样一种机制:团队大牛负责顶层设计,团队小牛负责分工实现具体的某一部分。这些具体的部分被离散化的概念实体统摄,进而可以保持相当的独立性。

在比较理想的状态下,不同部分之间只会交换信息,除此之外,别无交集。怎么讲呢?就其本质而言,不同部分之间必然不可能完全独立,但面向对象的编程范式可以帮助我们将这个耦合程度尽可能的降低。

最后再说第三驾马车:泛型编程的支持

值得注意的是,在最初的 C++ 版本中,并没有提供泛型的支持。添加泛型支持,是在面向对象得到充分使用和足够的认可之后。这个事实很有趣,体现了编程群体对于编程思维的演化所作出的反应。从面向过程到面向对象,编程这件事无疑变得更为容易,但容易本身同样招致问题:写出来的代码更冗杂了,多出了很多不必要的部分。执行效率的降低就是对此最好的证明。

这似乎有点违反直觉:面向对象的编程明明让代码更简单了呀,甚至都可以让面条代码与优雅代码和平共处。站在人类视角看去,确实如此,但如果站在机器的视角看去,代码确实更复杂了。所以,如果我们将机器最终执行的代码看作我们写出来的代码的话,C++ 写出来的代码确实会比 C 写出来的代码更复杂。

然后,泛型的支持,让这种复杂程度又上了一个台阶。是的,还是老样子,泛型的支持,让我们写出来的 C++ 代码更简单,但从机器的视角,其实更复杂了。

泛型的出现也是高阶抽象的结果:当高级类型的出现需要同时统摄多种基本类型时,泛型就出现了。但无论如何,这在机器层面都需要提前一一实现,或者届时根据上下文实时实现。

总结:编程思维的演变是朝着更有利于人类思考的方向进行,这牺牲了一定的效率,换来人类思考的便捷。所以,其中是一个平衡:在机器效率和人脑思考和执行效率之间的平衡。

但对于程序员而言:不应该将其视作可以偷懒的借口。也就是说,自己对于编程的学习不应该仅仅局限于某一种编程范式,而应该深入浅出,不断打磨自己的思考和表达,进而可以恰到好处地游走在各种编程范式之间,而且都能做到游刃有余。

总而言之,对编程本身的学习集中在两点:思维的打磨和经验的积累。这两点都要付诸于细节,付诸于时光,付诸于冥思苦想。


不知是该恭喜,还是该怎样,总之阅读到该文的,你是第 人。每一次刷新,都是不同的自己。