C++ 的早期被视作对 C 语言的一种扩展,仅仅引入了一种全新的编程范式 —— 面向对象编程(OOP),从而承诺解决了因代码库日益庞大而带来的诸多问题。然而,这个初始版本的 C++ 相当严苛:作为开发者,必须深入理解内存分配与释放机制、指针运算的工作原理,并时刻警惕无数细节之处 —— 这些往往容易忽略,却常常导致令人困惑的错误。
当时开发社区的文化氛围也无助于这门语言的学习:一个“真正的”程序员必须精通 CPU 架构、内存管理、各种汇编语言、操作系统原理,以及编译器的运作机制。此外,在几十年的时间里,标准化委员会几乎未采取任何措施来降低出错的可能性。时至今日,C++ 仍背负着近40年前形成的复杂且难以驾驭的名声,我自己的学习经历也印证了掌握它有多么困难。
我在 90 年代读专科时第一次接触到 C++,那段学习经历既让我感到好奇,又充满困惑。我意识到了这门语言的强大之处,但同时也觉得它似乎总是在和我“作对” —— 至少当时是这么认为的。为了写出能正常运行的代码,必须付出极大的努力。那时我还不太熟悉 STL,因为它尚未成为标准中广为人知的一部分,因此我最初的许多 C++ 程序都涉及大量的指针操作。
在当时的 C++ 考试和面试中,一个常见的问题是区分“指针数组”和“数组指针”。我只能想象这门语言的复杂性在出题方面是多么“有用”!顺便提一下,下面展示了“指针指向数组”和“数组包含指针”之间的区别,这是 C++ 的一个经典问题:
int(*pointerToArrayOf10Numbers)[10];
int *arrayOfTenPointers[10]
那时互联网尚未普及,知识不像今天这样触手可及,我只能通过实践和当时能找到的书籍来学习 C++。然而,我对这门语言真正深入的理解,是在 2000 年左右参与的一个项目中逐渐形成的。
这个项目的负责人是一位非常技术导向的比利时人。他为我们设定了明确的编码规范和开发流程,我们必须严格遵守这些规则,才能写出尽可能高质量的 C++ 代码。这种对卓越的追求并非出于他的个人偏好,而是源自于该数据库引擎项目本身的高要求(我们正在开发一个多年后,才正式命名为“NoSQL”)。
为了完成这个项目,我深入研读两本 C++ 的经典著作:Scott Meyers 所著的《Effective C++》和《More Effective C++》。这两本书总共总结了 90 条面向 C++ 开发者的编程指南,涵盖了资源管理、性能优化、继承机制、异常处理等多个关键主题。正是从这个时候起,我开始广泛使用 STL —— 而当时的 C++ 标准库,远不如今天这般丰富。
这些新掌握的知识,显著提升了我的代码质量与开发效率。而另一个重要的推动力是,我们结合书中理念所采用的开发流程:编写单元测试,进行设计评审和代码审查,并谨慎地对待每一行代码的编写。我们知道每一行代码在提交到代码库之前,都将接受同事的仔细检视。这套流程使我们的代码几乎无 bug,并在合理的时间内实现了高性能的复杂功能。
然而,语言本身仍在“考验”着我们。我们清楚如何写出优秀的 C++ 代码,但这需要极高的专注力和细致入微的观察,不可避免地拖慢了开发节奏。仅仅掌握 C++ 是不够的;语言本身也需要回馈开发者一些便利。
完成这个项目后,我暂时远离了 C++ 的世界,转而学习了 C# 、托管 C++、Java、PHP、Python、Haskell、JavaScript 和 Groovy —— 当然,仅限于那些我用于专业开发的语言。虽然每一种语言相比 C++ 都提供了更高的抽象层次,也带来了更少的底层烦恼,但我始终怀念那段与 C++ 共同成长的经历。了解 C++ 及其复杂的内存管理机制,让我能够更深刻地理解其他语言的底层运行原理,从而更好地发挥其潜力。例如,Haskell 对我来说格外亲切,它与我从 Andrei Alexandrescu 的经典著作《Modern C++ Design》中学到的元编程技术,有着异曲同工之妙。C++ 一直萦绕我的脑海中,它不仅是我职业生涯中的第一门编程语言,也成为我此后学习所有语言的基础。
大约在 2010 年,传来了令我欣喜的消息:C++ 标准化委员会终于开始频繁更新语言标准。此前多年,最新的 C++ 标准仍是 C++98;而现在,每隔三年便有新的版本发布。这一持续演进为语言引入了函数式编程范式、ranges(范围)、新的并行与异步编程原语,以及移动语义等重要特性。对于如今想学习 C++ 的人而言,最大的变化在于内存管理的简化与 auto 类型的引入。这些改进带来的最大突破是,即使是 Java 或 C# 开发者,也能轻松理解现代 C++ 程序。
今天的 C++ 已经比 90 年代要容易得多,曾经常见的考试问题:“指针数组和数组指针之间的区别”已经完全失去了实际意义。裸数组如今可以轻松地用 std::vector<> 或 std::list<> 替代,而指针则可以用更安全的 std::shared_ptr<> 或 std::unique_ptr<> 取代。这大大减少了手动管理内存所带来的负担,既美化了代码结构,又降低了产生那些令人费解的错误信息的可能性 —— 这些错误曾是 C++98 的标志性特征。
然而,我们也还无法断言现在的 C++ 已经和其他主流语言一样易于学习。