介绍一种语言,从它的历史和发展开始是不会错的。说到 C++ 的组织和发展形式——大家也许有所耳闻——C++ 在所有编程语言中,组织是最严谨的。最初的 C++ 于上世纪 80 年代诞生于著名的美国贝尔实验室,由 Bjarne Stroustrup(比雅尼·斯特劳斯特鲁普)博士开发。
细节参见 wikipedia。
1985 年,第一版《C++程序设计语言》发布,随后 C++ 就逐渐走上了标准引领的发展模式。在 1998 年,C++ 语言标准的制定和发布工作正式被转交给国际标准化组织,也就是 ISO 负责。1998 年版本的标准文档编号为 ISO/IEC 14882:1998,也就是我们所说的 C++98 版本。此后的每个 C++ 版本都是按照 ISO 组织的议事流程提出、讨论和发布的,换句话说,开发者标注的每个 C++ 规范版本,都对应着一个 ISO 标准文档。
C++ 的第二份标准文档出版于 2003 年,即 C++03 版本。第三份文档出版于 2011 年。这一份文档对 C++ 的语法、库甚至组织形式都做了重大的调整,因此我们认为从 C++98 开始开发者使用的是“标准 C++”,而从 C++11 开始,开发者使用的是“现代 C++”。这个标准本身还确立了从此之后应该每 3 年出版一份新的标准文档,因此我们有了 C++14、17、20 和 23。许多现代语言都采用了这样的发布节奏,比如说 Rust 的 2015、2018 和 2021 大版本。
然而,组织上的严谨常常意味着结果上的不严谨。作为多方角力和妥协的结果,C++ 语法和标准库中包含各种各样的不合常理或者残缺之处。这里点名批评 std::vector<bool> 还有挣扎多年进不了标准的网络库。另外,委员会里的专家学者们未必是工程人员,因此规范文本和工程是不同步的。一个标准语法可能是在某个编译器的测试版本中先实现,然后再提交给标准审议。通过之后再由其他的编译器工程团队实现,结果就是在不同的时期、使用不同品牌和版本的编译器、开启不同的语言标准,可能遇到的行为都不同。
因此,在学习 C++ 的过程中,开发者必须选取比较灵活的占位。在原则上,应该关注标准,贴合标准。但是如果实测到与标准不同的编译器行为(尤其当你使用的不是 3 大编译器的时候),尽量学会接受现实。另外,即使是标准范围内的行为,也可能不合直觉,甚至扭曲怪异。C++ 开发者应该学会对标准进行合理的扬弃,取其精华去其糟粕。这里点名批评 C++ 的流式格式化,即 std::cout 这一套,用了都说差。但是在我们这次的训练营测试代码里,为了减少依赖,我还是都用的 std::cout 输出。但是工程上,只要有可能引入依赖都推荐大家使用 fmtlib 库做格式化输出。另外还要批评 C++ 的 exception 机制,这东西我在训练营代码里既不使用也不会讲到,有需要的同学可以自行学习。
本次基础阶段课程会围绕这一套 C++ 测试题展开,具体的形式是我会围绕题目的话题展开讲解,介绍 C++ 语法、标准库和一些惯例写法。期望在课程结束时,大部分人至少可以提交一份满分的作业,同时又学到了 C++ 这门语言。如果遇到合适的话题,我也会在 InfiniTenosr 和 RefacorGraph 项目里找一些典型的代码,方便大家了解在实际项目中会出现什么样的情况,也为下一阶段学习项目中的专业知识做一些铺垫。
另外每节课会留一小部分答疑时间,大家可以直接在线问答,这个时间除了我们习题中的问题,也欢迎大家把自己学习工作中遇到的 C++ 问题拿来探讨,可以投自己的屏幕展示,我能回答的我就回答,回答不了的大家可以一起探讨,我们希望即使在课程结束之后,我们的学习群也可以成为一个学习和交流的平台。我就不说 C++ 爱好者了,因为直白地说我本人并不是一个 C++ 爱好者,我写 C++ 纯粹是工作需要罢了。但是不管爱不爱,至少我们可以尽量通过这个社区让 C++ 更好地发挥作用,这样就足够了。
最后,课后课程的视频和我的讲稿都会发布在课程网站,供大家回看。
那么我们现在开始今天的课程。
因为不是所有人都接触过 C 和 C++,所以我需要先介绍一下 C++ 程序生成的概况。大部分现代的语言带有跨平台的生成工具,例如 Rust 的 rustup,Python 的 Python 应用程序等。但是 C++ 作为一种标准驱动的语言,在不同的平台有不同的主流构建工具。最常见的三大 C++ 编译器是 GCC、Clang 和 MSVC。说是编译器,这个编译器实际上是比较广义的编译工具的意思,包括狭义的编译器、链接器和调试器还有二进制工具等,所以更常见的称呼是工具链。
我一般使用 Windows 操作系统,原生 Windows 上,最常用的工具链是 MSVC,它有两种安装途径,一般是随 Visual Studio IDE(集成开发环境)安装,但也可以通过微软官方提供的安装器单独安装。一般如果常用原生 Windows 环境并且硬盘空间不太紧张的话,比较推荐直接安装 Visual Studio。一方面是因为 Visual Studio 社区版反正是免费的而且安装不麻烦,没必要折腾;另外还因为直接按默认安装方式安装 MSVC 是和其他软件配合起来最省事的,尤其是 CUDA 工具链。不喜欢 Visual Studio 开发体验的可以不用,就当它是工具链的挂件就行了。
如果使用 WSL 或者其他的 Linux 环境,最常用的工具链是 GCC。这个东西在不同的发行版中安装方式比较多样,需要每个人根据自己环境去找。
CLang,我习惯读成 C-Lang,但是也有人读成“克朗”的。这个编译器发展相对比较晚。它是由 LLVM 的作者开发的,一直和 LLVM 同步发展,就架构来说它是比较先进的。但是由于 GCC 和 MSVC 在各自的环境里地位实在无法撼动,所以现在 CLang 在这两个环境里不是那么常见。但是由于 InfiniLM 项目依赖 Rust 一个叫做 bindgen 的库,bindgen 又依赖 CLang 的 C 语言前端解读头文件,所以只要是跑起 InfiniLM 的学员一定安装了 clang。
但是这些工具链只是完成基本的代码编译和链接功能的工具,并不带有我们所说的构建工具应该带有的工程管理、依赖管理、缓存管理以及增量编译这样的功能,因此是无法直接使用的,必须在其上套上一层构建工具。最常见的构建工具是 make,这里我们因为用不到,就不去细讲什么是 make 了,大家只要知道这是一个限 linux 环境可用的软件就行了。make 会读一种它自己定义格式的脚本文件叫做 Makefile,可以实现脚本化指令调用和增量编译,带有基本的逻辑能力和字符串处理能力。make 算是一种有效的工具,但是它有两个重大缺陷:第一是表达能力差,对于复杂项目来说不比写普通 shell 脚本省太多事。叠个甲,不排除大神能写超帅 Makefile 干一切事,但是确实不容易;第二是不跨平台,Windows 环境里倒是又对应物 nmake,但是就是有点不一样导致不通用,很烦。
如果主要关注第二个问题,最常用的跨平台构建工具是 CMake。相比 make,CMake 是一种十分复杂的软件,它也有自己的配置文件叫做 CMakeLists.txt,注意 .txt 后缀不能省略。CMake 确实具有比较正经的跨平台能力,另外它也是一种严格意义上的生成工具,可以定义项目结构、递归调用子项目,并且针对各种语言和工具链定制了许多脚本命令来减轻写复杂项目配置的压力。但是 CMake 还有两个重大缺陷:第一是 CMake 只是软件和配置文件跨平台,但是用法并不跨平台。在 linux 上用 CMake 一般是写几个命令,CMake 会生成下一级工具的配置文件,让真正控制编译过程的软件来编译项目,包括 make、meson 和 ninja,有一堆。但是在 Windows 上,CMake 的正统用法是生成一个 Visual Studio 工程,然后通过 Visual Studio 的图形界面打开编译。其实底层逻辑上这个还蛮合理的,因为 linux 上大家直接用 make,CMake 就生成 Makefile 给 make;Windows 上大家直接用 Visual Studio,CMake 就生成 Visual Studio 工程给 Visual Studio。但是用法过于不一致,导致 CMake 用户必须同时懂 CMake 和那个平台的专用工具的用法,这就十分麻烦了。CMake 的第二个缺陷是懂 CMake 本身就十分甚至九分的难。CMake 的配置语法复杂和反直觉的程度不输 C++,而且语法特别难以理解,导致虽然市面上跨平台的 C++ 项目和库 70%-80% 都用的 CMake,但是却很难找到一个 CMake 专家。研究这种东西性价比太低了。
综上所述,我们的学习项目最终选择了新兴的 xmake 作为构建工具。xmake 作为构建工具有 3 个优点,首先是它跨平台,而且至少在 Windows 和 linux 上用法相当一致,非常节省我写文档的时间。其次是 xmake 使用通用编程语言 lua 作为配置文件,虽然 lua 并不是所有人熟悉的语言,但是它至少符合一种通用语言的基本逻辑,可以猜着写。CMake 是绝对猜不透的。这第三个优点比较中国特色,就是 xmake 是中国人做的个人项目,所以它有超完整的中文文档,issue 也有许多中文问答,而且访问它的网站十分顺畅,不需要加特技,这些优点对于初学者来说太重要了。所以最终我选择了用 xmake 来构建我们的习题项目。
顺便再重申一下项目的用法,虽然很简单而且 README 写了,但是以免有人没看懂。
- 首先确保你已经安装好了适用于你的平台的 C++ 工具链,并且安装好了 xmake。可以随便找个命令行
xmake --version试一下; - 从 github 拉取训练项目,然后进入路径直接
xmake构建,构建成功配置步骤就结束了,可以开始做题了; - 做题的时候就是直接题目,比如
exercises/00_hello_world/main.cpp,然后下面命令行敲xmake run learn+ 题号,就会编译并且测试这个题目; 这个命令是需要手工敲的,因为 xmake 没有 cargo 的 watch 功能,用 C++ 手工实现一个 watch 功能又比较麻烦,我就没做,做完题需要敲这个命令才能测试; - 然后做完题之后可以用这个 summary 功能来检查自己做完了多少,敲
xmake run summary,就会按顺序编译测试运行所有题目,显示出来对错的情况; 这样一个一个运行会比较慢,所以还有一个不会输出的的版本叫做xmake run summary --simple,这个命令会并行运行所有测试然后直接打出一个分数,可以看到这个跑起来快多了;
