这一章在干嘛?
写代码之前,先搞清楚”C++ 到底是个什么东西”。这一章不讲语法细节,而是讲 C++ 的身世:它继承了 C 语言的高效,又嫁接了面向对象编程(OOP)和泛型编程两套新思想。最后它会告诉你一个程序是怎么从文本文件变成能在电脑上跑的可执行文件的——写好 → 编译 → 链接,三步走。
1.1 学习 C++:即将面对什么
C++ 一口气横跨了三种编程风格:
| 风格 | 代表 | 一句话概括 |
|---|---|---|
| 过程性语言 | C | 强调”做什么步骤”,像菜谱一样列出一串操作 |
| 面向对象语言(OOP) | C++ 的类 | 强调”数据长什么样”,让语言去贴合问题 |
| 泛型编程 | C++ 模板 | 强调”独立于数据类型”,写一次代码通用于多种类型 |
这三重血统既是财富也是负担:它让 C++ 非常强大,但也意味着要学的东西很多。学 C++ 大致分两条路径——如果你会 C,要适应 OOP 的思维方式(甚至要改掉一些 C 的老习惯);如果不会 C,那就得把 C 的基础、OOP 和泛型三块都啃下来。本书的做法是:先打牢 C 的地基(基本类型、运算符、控制结构、语法规则),再往上盖 OOP 和模板这两层楼。
1.2 C++ 的起源与 C 语言的哲学
20 世纪 70 年代初,贝尔实验室的 Dennis Ritchie 为了开发 Unix 操作系统创造了 C 语言。他当时的需求很明确:既要像汇编语言那样贴近硬件、高效紧凑,又要像高级语言那样可以跨平台移植。汇编语言的问题在于它与特定 CPU 绑定——换一台机器就得重写,就像”每次买辆新车,设计师都把油门和刹车挪了位置,你还得重新学开车”。而编译器(compiler)解决了移植问题:同一段高级语言代码,每个平台配一个自己的编译器即可。
C 语言强调的是算法一侧。程序 = 数据 + 算法:

▲ 图 1.1 数据 + 算法 = 程序
早期的 FORTRAN、BASIC 程序一长大,跳转语句就会乱成一锅粥——代码像意大利面条一样缠绕(所谓”面条式编程”),读不懂也不敢改。于是诞生了结构化编程:把跳转限制在少数几种行为良好的结构里(for、while、do while、if else),并配合自顶向下设计——把大程序拆成小任务,小任务再拆,直到每个模块都能轻松编写。这些模块在 C 里就叫函数(function)。
1.3 C++ 的转向:面向对象编程
结构化编程让程序更清晰可靠,但面对超大规模项目依然吃力。OOP 换了个思路:过程性编程强调算法,OOP 强调数据——不是把问题硬塞进语言的框框,而是设计出与问题本质特征对应的数据形式。
两个核心概念:
- 类(class):描述一种新数据形式的规格说明书。比如一个”矩形”类,数据部分包括角落坐标、宽高、边线颜色、填充图案;操作部分包括移动、缩放、旋转、改色等方法。
- 对象(object):按类这份图纸造出来的具体实体。画两个矩形,就有两个对象。
OOP 的设计过程是先设计类,再用对象搭程序——从低层的类走向高层的程序设计,这叫自底向上编程,与过程式的自顶向下正好相反。OOP 还带来几件法宝:
- 信息隐藏:保护数据不被不当访问;
- 多态:为运算符和函数创建多个定义,由上下文决定用哪个;
- 继承:从旧类派生出新类,代码复用省时省力。
一个设计良好的类库还能直接拿来复用——C++ 的一大好处就是能轻松复用经过充分测试的现成代码。
1.4 C++ 与泛型编程
泛型编程和 OOP 目标一致(代码复用),但着力点不同:OOP 强调数据这一侧,泛型编程强调独立于特定数据类型。举个直观的例子:要对整数、浮点数、字符串分别排序,按老办法得写三个排序函数;泛型编程让你只用写一次”通用类型”的排序函数,各种实际类型都能用。C++ 的模板(template)就是实现这套思想的机制。OOP 常用于管理大型项目,泛型编程则提供排序、合并这类通用任务的工具。
1.5 C++ 的诞生与双重身份
C++ 同样诞生于贝尔实验室:Bjarne Stroustrup 在 20 世纪 80 年代初开发了它。用他自己的话说,设计 C++ 主要是”为了我和我的朋友们不必再用汇编、C 或各种现代高级语言编程”,让写好程序这件事变得更容易、更愉快。
Stroustrup 的实用主义贯穿了 C++ 的设计:以 C 为基础(简洁、适合系统编程、与 Unix 关系紧密),借鉴仿真语言 Simula 67 的 OOP 思想,在不大幅改动 C 的前提下嫁接 OOP 和泛型支持。因此 C++ 是 C 的超集——任何合法的 C 程序几乎也是合法的 C++ 程序,现成的 C 代码库都能继续用,这大大推动了 C++ 的普及。名字里的 ++ 正来自 C 的自增运算符,暗示”增强版的 C”。
C++ 的双重身份使它既能用 OOP 概念贴合现实问题,又能靠 C 的部分贴近硬件:

▲ 图 1.2 C++ 的双重性
有意思的是,模板是 C++ 成功之后才补充进去的,后来人们才发现它的重要性或许不亚于 OOP。C++ 同时容纳过程式、OOP 和泛型三种范式,体现的是”实用高于意识形态”,这正是它成功的原因之一。
1.6 可移植性与标准
假设你在公司的旧电脑上写了个 C++ 程序,换了新机器新系统还能跑吗?只要重新用目标平台的编译器编译、代码一行不改就能正常运行,我们就说这个程序是可移植的。可移植性有两大障碍:
- 硬件:直接操控某块显卡的程序,在别的机器上就是一堆乱码。对策是把硬件相关的代码局部化在少数模块里。
- 语言的方言分化:就像约克郡英语到了布鲁克林未必听得懂,各家编译器如果不按同一份标准实现,C++ 也会四分五裂。
于是 ANSI 在 1990 年成立委员会制定 C++ 标准,ISO 随后加入,共同催生了国际标准:
| 标准 | 年份 | 要点 |
|---|---|---|
| C++98(ISO/IEC 14882:1998) | 1998 | 首个国际标准,加入异常、RTTI、模板、STL |
| C++03 | 2003 | 技术修订版,修错别字、减少歧义,不加新特性(常与 98 合称) |
| C++11 | 2011 | 新增大量特性,目标是消除不一致、让 C++ 更易学易用 |
标准不断膨胀:最初 C++ 的事实标准只有 65 页参考手册,C++98 接近 800 页,C++11 已超过 1350 页。C++ 还要尽量当 C 的超集,所以标准也参照了 ANSI C 标准(其标准库被称为标准 C 库,C++ 程序同样可以使用)。
1.7 创建程序的完整流程
写好的 C++ 程序是怎么跑起来的?三步(见图 1.3):
- 编辑:用文本编辑器写程序并保存为文件,这就是源代码(source code);
- 编译:运行编译器,把源代码翻译成计算机内部语言(机器语言),得到目标代码(object code);
- 链接:把你的目标代码与 C++ 库中用到的函数目标代码、标准启动代码合并,得到最终的可执行代码(executable code)。

▲ 图 1.3 编程步骤
创建源文件时还有个细节:文件名必须带正确的**后缀(扩展名)**来表明它是 C++ 源码——这既是提醒你,也是提醒编译器:

▲ 图 1.4 源代码文件名的组成部分
不同实现惯用的后缀不同,Unix 区分大小写所以用大写 C 与 C 程序区分,DOS 不分大小写只能加字母:
| C++ 实现 | 源码后缀 |
|---|---|
| Unix | C, cc, cxx, c |
| GNU C++ | C, cc, cxx, cpp, c++ |
| Digital Mars | cpp, cxx |
| Borland C++ | cpp |
| Watcom | cpp |
| Microsoft Visual C++ | cpp, cxx, cc |
| Freestyle CodeWarrior | cpp, cp, cc, cxx, c++ |
1.8 编译与链接的三种方式
早期 Stroustrup 是用一个叫 cfront 的程序把 C++ 先翻译成 C 再编译的;后来多数实现改为直接从 C++ 生成目标代码,速度更快,也表明 C++ 是一门独立语言。
Unix 系统:用 CC 命令编译(大写以区别于 C 编译器 cc):
CC spiffy.C // 编译生成 spiffy.o,自动链接为 a.out
CC my.C precious.C // 多文件编译
CC my.C precious.o // 只重编改动的 my.C,链接旧目标文件默认可执行文件叫 a.out,直接输入 a.out 运行;想保留就先用 mv 改名,否则下次编译会被覆盖。
Linux 系统:常用 GNU 的 g++ 编译器:
g++ spiffy.cxx // 生成 a.out
g++ my.cxx precious.cxx // 多文件
g++ my.cxx precious.o // 增量重编Windows 命令行:免费的 Cygwin / MinGW 提供 g++,编译成功后生成 a.exe:
g++ great.cppWindows IDE(如 Visual C++):先建项目(project)再添加源文件。项目类型一定要选**控制台程序(Console Application)**而不是 Windows 应用,因为本书的程序都是通用的字符界面程序。IDE 菜单常见术语:Compile 只编当前文件;Build/Make 编译整个项目(增量式,只重编改过的文件);Build All 从头全编;Run/Execute 运行;Debug 单步调试;Debug 版本比 Release 版本多了调试信息,更大更慢。
编译器报错时,错误行号未必精确,有时真正的问题在标注行的前一行,有时一个错误会引发一串报错。修错误要从第一个修起。另外,如果编译器在构建不完整后给出莫名奇妙的报错,试试 Build All 从头来一遍。
常见坑:程序窗口一闪而过
很多 IDE 运行完程序会立刻关掉输出窗口,你根本来不及看结果。解决办法是在
return 0;前加上cin.get();——它会让程序等待你按回车。如果程序之前读过输入(比如敲过数字后按了回车),需要加两条cin.get();,第一条吃掉残留的回车键,第二条才是真正的暂停。
常见坑:源文件后缀用错
在 Unix 上把 C++ 源文件存成小写
.c,编译器会把它当成 C 程序来编;IDE 里选错项目类型(比如选成 Windows 应用而不是 Console),程序编译能过但代码没法按书里的方式写。文件命名和项目类型这类”环境问题”是新手第一个碰到的墙,出了怪错先检查它们。
通关标准:
你能不看书画出”编辑 → 编译 → 链接”三步流程图并说出每步的产物(源代码 → 目标代码 → 可执行代码);能说出 C++ 的三种编程范式分别强调什么(算法 / 数据 / 类型无关);知道自己用的编译器是哪个、源文件该用什么后缀、控制台程序该怎么建。
自测:C++ 为什么是 C 的"超集"有什么好处?
因为 Stroustrup 添加 OOP 和泛型支持时没有大改 C 的部分,所以任何合法的 C 程序几乎也是合法的 C++ 程序,现成的 C 代码库可以直接在 C++ 里继续使用,节省大量重复劳动,也促进了 C++ 的推广。
自测:OOP 和泛型编程都追求代码复用,它们的关键区别是什么?
OOP 强调数据这一侧——设计与问题本质对应的数据形式(类),适合管理大型项目;泛型编程强调独立于特定数据类型——用模板写一次”通用类型”的代码(如排序),通用于多种实际类型。
自测:可移植性的两大障碍是什么?
一是硬件——直接操控特定硬件的代码无法移植,对策是把硬件相关代码局部化到少数模块;二是语言方言分化——没有统一标准时各家编译器行为不一致,对策是 ANSI/ISO 标准统一语言行为。
自测:为什么说"编译器接受了你的程序"不等于"程序是合法的 C++"?
个别编译器可能接受了不符合标准的写法(方言扩展),也可能错误地拒绝合法代码。编译器的通过与否只是参考,真正的裁判是 C++ 标准。
自测:Build 和 Build All 有什么区别?
Build/Make 是增量式编译:项目里多个源文件只重编改过的那个;Build All 是从头重新编译所有源文件。编译器状态混乱、报错莫名其妙时,用 Build All 从头再来往往能解决问题。