第一章 C++语言概述
语言是怎么走到今天这样的,C++ 的特点和版本怎么选,一个程序由哪几块拼成,第一个程序从写下来到跑起来中间发生了什么。底子打牢,后面看到报错才知道往哪查。
┌────────────────────────────────────────────────────────┐
│ 这一章要讲的事,一张图看完 │
├────────────────────────────────────────────────────────┤
│ 第一章 C++ 语言概述 │
│ │ │
│ ├── 1. 计算机程序设计语言简介 三代语言怎么走到人这边 │
│ ├── 2. C++ 语言的特点和发展 强在哪,版本怎么选 │
│ ├── 3. C++ 程序的基本结构 一个程序由哪几块拼成 │
│ ├── 4. 编写第一个 C++ 程序 从源码到跑起来的四步 │
│ └── 5. 小结 一张表收口,加练习 │
└────────────────────────────────────────────────────────┘
这一章没有难的东西,全是认门。认完门,后面每一章都是往里走。例子别光看,每个都自己敲一遍,敲过的代码才记得住。
一、计算机程序设计语言简介
1.1 程序是一份指令清单
计算机这东西,本质上是个只会算数的傻子。你让它显示一个字,它内部要转过好几道弯。它听得懂的只有 0 和 1,别的都不会说。
程序,就是我们写给它的指令清单。问题在于没人想用 01 写清单,看一行都费劲,改一晚上能要命。于是有了程序设计语言,充当人和机器之间的中间人。我们用接近人的写法描述意图,再让翻译程序把它变成 01。
┌──────────────────────────────────────────────────────────┐
│ 程序设计语言的三级阶梯 │
├──────────────────────────────────────────────────────────┤
│ 语言一代一代改,方向只有一个,离人越来越近 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 第一代 机器语言 01001000 00100001,全是 0 和 1 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 第二代 汇编语言 MOV AX, 1,给 01 起了名字 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 第三代 高级语言 cout << "Hi";,写着像句子 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 三代今天都还在,越靠下跑得越快,越靠上写起来越省事 │
└──────────────────────────────────────────────────────────┘
三代语言今天都还在。汇编在驱动和引导代码里活着,高性能计算里也有人用。高级语言这一档,C++ 算离机器近的,这也是它快的原因之一。
| 代际 | 长相 | 离谁近 | 长处 | 代价 |
|---|---|---|---|---|
| 机器语言 | 01001000 | 机器 | 跑得最快 | 人读不了,改不动 |
| 汇编语言 | MOV AX, 1 | 机器 | 贴着硬件,性能可控 | 要记机器的脾气,换个平台重写 |
| 高级语言 | cout << "Hi"; | 人 | 写得快,读得懂 | 要经过翻译,损失一点性能 |
1.2 编译型和解释型,一条分水岭
高级语言机器看不懂,得有人翻译。翻译方式分两种,编译和解释,这一条决定了语言的很多脾气。
┌─────────────────────────────────────────────────────────┐
│ 翻译的两种方式 │
├─────────────────────────────────────────────────────────┤
│ 高级语言机器看不懂,得有人翻译,方式有两种 │
│ │
│ 编译型,C++ 走这条 解释型,Python 走这条 │
│ │
│ 源码 hello.cpp 源码 hello.py │
│ │ │ │
│ v v │
│ 编译器一次翻完 解释器一行行翻 │
│ │ │ │
│ v v │
│ 可执行文件 没有中间文件 │
│ │ │ │
│ v v │
│ 跑起来快,翻一次就够 改一行跑一行,方便 │
└─────────────────────────────────────────────────────────┘
| 编译型 | 解释型 | |
|---|---|---|
| 代表语言 | C、C++、Go、Rust | Python、JavaScript、Ruby |
| 怎么跑 | 开工前整体翻成可执行文件 | 跑的时候一行行翻 |
| 快慢 | 快 | 慢一点,换来的是灵活 |
| 改代码 | 改完要重编 | 改完直接跑 |
| 排错 | 编译期就拦下一大批错 | 跑到哪错到哪 |
C++ 是编译型。这句话的分量在后面会越来越明显。写之前要先编过,改完要重编,编译期能拦下一大批错,运行速度也有保障。
编译慢的那几秒,换来运行时的快和写代码时的安全感,这笔账我觉得划算。当然这是我个人的看法,跑小脚本的时候我也用 Python,谁跟自己的时间过不去呢。
二、C++ 语言的特点和发展
2.1 出身,贝尔实验室的一间办公室
1979 年,贝尔实验室,一个叫 Bjarne Stroustrup 的丹麦人想给 C 语言加个 class,让大型程序好写一点。项目先叫 C with classes,意思是带 class 的 C。他的名字国内教程常写成 本贾尼·斯特劳斯特卢普,太长了,后面我就用 本贾尼 这个简称。
1983 年改名 C++。这个名字是同事提的,++ 来自 C 的自增运算符,读作 C plus plus,意思是比 C 进一步。这个自带吹嘘味的命名,后来成了行业经典案例。
┌─────────────────────────────────────────────────────────────────────┐
│ C++ 的四十年 │
├─────────────────────────────────────────────────────────────────────┤
│ 从 1979 到现在,一条线看完 │
│ │
│ 1979 贝尔实验室,Bjarne Stroustrup 动手,项目叫 C with classes │
│ │ │
│ 1983 改名 C++,++ 取自 C 的自增运算符,意思是比 C 进一步 │
│ │ │
│ 1985 第一份商业发行,同名的书也在这一年出来 │
│ │ │
│ 1998 第一版国际标准,C++98,模板和 STL 进场 │
│ │ │
│ 2003 小修小补,C++03,没有新语法 │
│ │ │
│ 2011 大改,auto、lambda、智能指针都来了 │
│ │ │
│ 2014 小步前进,C++14 │
│ │ │
│ 2017 继续补强,C++17 │
│ │ │
│ 2020 大版本,concepts、ranges、协程 │
│ │ │
│ 2023 目前最新的标准,工具链支持还在铺开 │
└─────────────────────────────────────────────────────────────────────┘
2.2 标准版本,认准 C++11 之后的
| 版本 | 年份 | 看点 |
|---|---|---|
| C++98 | 1998 | 第一版国际标准,模板和 STL 进场 |
| C++03 | 2003 | 修 bug,没有新语法 |
| C++11 | 2011 | auto、lambda、智能指针、范围 for,公认的分水岭 |
| C++14 | 2014 | 泛型 lambda 之类的小补丁 |
| C++17 | 2017 | 结构化绑定、optional、文件系统库 |
| C++20 | 2020 | concepts、ranges、协程 |
| C++23 | 2023 | 目前最新,工具链支持还在铺开 |
2011 是分水岭。2011 之前的老写法,和新写法放在一起,读起来像两门语言。业界把 2011 之后的叫 modern C++,本书全程按这个来。
版本选择不用纠结。写新代码,标准直接上 C++17 或 C++20,g++ 编译时加 -std=c++17 就行。语法主体十几年没大变,把精力放在编程思想上,别耗在版本对比上。
2.3 它强在哪
| 特点 | 说明 | 新手能感受到什么 |
|---|---|---|
| 快 | 编译成机器码,零开销抽象是设计目标 | 同样的算法,它常常跑在第一档 |
| 面向过程也能面向对象 | class、继承、多态都有 | 先当 C 用,慢慢再体会对象 |
| 泛型 | 模板让一套代码通吃多种类型 | STL 的容器和算法都靠它 |
| 标准库全 | 容器、算法、线程、文件都有现成的 | 不用什么都自己造 |
| 兼容 C | 绝大多数 C 代码能直接编 | 老库拿过来就能用 |
| 跨平台 | 一份源码,各平台各编各的 | 换系统要重编,不用重写 |
拿它干活的领域,都是性能敏感或者离硬件近的地方。
| 领域 | 拿它干什么 | 眼熟的项目 |
|---|---|---|
| 游戏 | 引擎与玩法逻辑 | Unreal Engine |
| 浏览器与运行时 | 渲染引擎、JS 引擎 | Chrome 的 Blink 和 V8 |
| 数据库 | 存储引擎 | MySQL、MongoDB |
| 嵌入式与汽车 | 固件、控制软件 | 车载系统、机器人 |
| 金融 | 交易系统 | 高频交易对延迟敏感 |
| 大型桌面软件 | 性能吃紧的客户端 | 修图软件、办公套件的部分模块 |
丑话说在前面。它不好学。语法细节多,历史包袱重,报错信息一长串,指针那一关能卡住一批人。学会的回报也摆在那儿,性能和就业面都真实存在。想清楚再上车,上车就认真开。
2.4 和 C 的关系
大体上,C++ 是 C 的超集,绝大多数 C 代码能直接当 C++ 编。少数地方行为不同,比如类型转换的规则,混写时要留意。
学 C++ 之前不用先学 C,直接上。遇到看不懂的老式写法,多半是 C 时代留下来的,查一下就知道。
三、C++ 程序的基本结构
3.1 最小程序,五样东西
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
这七行是能跑起来的最小单位,拆开看,统共五样东西。
┌──────────────────────────────────────────────────────────┐
│ 最小程序的骨架 │
├──────────────────────────────────────────────────────────┤
│ 七行代码,统共五样东西 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ #include <iostream> 预处理指令,请工具库进来 │ │
│ │ using namespace std; 标准库的名字可以直接写 │ │
│ │ int main() { 程序入口,系统只从这里开工│ │
│ │ cout << "Hi"; 语句,真正干活的 │ │
│ │ return 0; 告诉系统,正常收工 │ │
│ │ } 函数体到此为止 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 漏哪一样,编译器都会拦你,报错信息就是线索 │
└──────────────────────────────────────────────────────────┘
| 成分 | 长什么样 | 干什么 |
|---|---|---|
| 预处理指令 | #include | 把工具库的声明请进来 |
| 命名空间声明 | using namespace std; | 标准库的名字可以直接写 |
| 函数 | int main() { ... } | 干活的单元,程序入口也叫 main |
| 语句 | cout << "Hello, World!" << endl; | 一条命令,分号收尾 |
| 返回值 | return 0; | 告诉操作系统干得怎么样 |
逐个说。
include 那行是预处理指令。iostream 是标准库的输入输出组件,cout、endl 都在里面住着。不请它,后面全抓瞎。
using namespace std; 是命名空间声明。std 是标准库住的大院,不声明的话,写 cout 要写全名 std::cout。新手阶段先写上,能省一半打字量。等代码有规模了,再讨论要不要省。
int main() 是程序入口。系统启动程序,只认 main 这个名字,从第一行花括号开始执行。一个程序只能有一个 main,写两个链接会报错。
花括号划出函数体的范围,里面的语句按顺序一条条跑。分号是语句的句号,漏一个,编译器能给你报一串错,而且报错位置经常偏一行,这是 C++ 的老传统。
return 0 告诉操作系统,活干完了,没出事,0 是正常收工的暗号。现在 main 里不写 return 也可以,编译器会默认加,写上是老规矩,图个清楚。
3.2 注释,写给自己看的
注释有两种写法,编译器看见就当没看见。
// 单行注释,两个斜线,到此为止
/*
块注释
斜杠星开头,星斜杠结尾,中间随便写
*/
代码是写给机器的,注释是写给人的。三个月后回来看自己的代码,你会感谢当时写注释的那个人。
四、编写第一个 C++ 程序
4.1 挑一个工具
| 工具 | 适合谁 | 备注 |
|---|---|---|
| g++ | 想敲命令学的 | MinGW-w64,或 Linux、macOS 的包管理器 |
| Visual Studio | Windows 上做项目 | 社区版免费,安装时勾 C++ 工作负载 |
| VS Code + C/C++ 扩展 | 轻量编辑器党 | 装扩展,再配上 g++ |
| CLion | 要强力补全和调试 | 收费,学生免费 |
| 在线编译器 | 只想跑一段代码 | wandbox、godbolt 之类 |
本章例子用 g++,命令行三行跑完,不挑编辑器。装好之后先验货。
C:\> g++ --version
g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
Windows 上装完 MinGW,要把安装目录下的 bin 加进 PATH,命令行才认 g++。认的话万事大吉,不认的话,把路径加进去,重启命令行再试。
4.2 从源码到跑起来,四步
新建一个文件,取名 hello.cpp。后缀必须是 .cpp,这是 C++ 源码的招牌,.c 是给 C 的,编译器靠后缀决定按哪套规则来。
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
存好,编译,一条命令。
g++ hello.cpp -o hello
-o hello 告诉编译器,产物叫 hello。不写的话,Windows 下默认叫 a.exe,Linux 和 macOS 下默认叫 a.out,都能跑,名字不好记。
这一条命令底下,流水线跑了四步。
┌───────────────────────────────────────────────────────────┐
│ 编译链接走四步 │
├───────────────────────────────────────────────────────────┤
│ 一条 g++ 命令,底下跑了四步 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 第一步 预处理 展开 include,把头文件拷进来 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 第二步 编译 翻成汇编代码 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 第三步 汇编 翻成机器码,装进目标文件 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 第四步 链接 和标准库拼成可执行文件 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 四步的产物依次是 hello.i、hello.s、hello.o、hello.exe │
└───────────────────────────────────────────────────────────┘
| 阶段 | 干什么 | 产物 |
|---|---|---|
| 预处理 | 展开 include,把头文件拷进来 | hello.i |
| 编译 | 翻成汇编代码 | hello.s |
| 汇编 | 翻成机器码,装进目标文件 | hello.o |
| 链接 | 和标准库拼成可执行文件 | hello / hello.exe |
想看中间产物,加参数单独跑,每一步都能停下来看。
g++ -E hello.cpp -o hello.i # 只做预处理
g++ -S hello.cpp -o hello.s # 做到汇编
g++ -c hello.cpp -o hello.o # 做到目标文件
排查离奇问题的时候,这几条能救命。模板那章的报错,一半靠它们看懂。
4.3 跑起来
┌──────────────────────────────────────────────────────────┐
│ 第一个程序从写到跑 │
├──────────────────────────────────────────────────────────┤
│ 第一个程序,四步走完 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 写源码 新建 hello.cpp,把代码敲进去 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 编译 g++ hello.cpp -o hello,一次跑完 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 运行 Windows 输 hello,其他系统输 ./hello │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 看结果 屏幕上出现 Hello, World! │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
Windows 下在命令行输入 hello,Linux 和 macOS 下输入 ./hello,屏幕上出现 Hello, World! 这一行,第一个程序就算成了。给自己鼓个掌,后面还有一百个程序要写。
逐行拆一遍。
| 代码 | 作用 |
|---|---|
| #include | 把输入输出工具库请进来 |
| using namespace std; | 标准库的名字可以直接写 |
| int main() { | 程序入口,系统从这里开工 |
| cout << "Hello, World!" << endl; | 往屏幕上写一句话,endl 是换行加刷新 |
| return 0; | 告诉系统正常收工 |
| } | 函数体结束 |
cout 读作 see out,是标准库的输出对象,可以把它当成屏幕的化身。<< 是往它里面送东西的通道,叫插入运算符。送字符串就显示字符串,送数字就显示数字,送 endl 就换个行。
再写一个带输入的,试试 cin。
#include <iostream>
using namespace std;
int main() {
int a = 0, b = 0;
cout << "请输入两个整数,用空格分开: ";
cin >> a >> b;
cout << a << " + " << b << " = " << a + b << endl;
return 0;
}
跑起来是这样。
请输入两个整数,用空格分开: 3 5
3 + 5 = 8
cin 是输入通道,>> 把键盘敲的东西取出来,按变量的类型存好。a 和 b 声明成 int,敲 3 和 5 进去,加起来,原样送出来。
这里有个细节,int 存不了小数,也存不下太大的数,超了会溢出。这是下一章的内容,先留个印象。
4.4 输出中文乱码,Windows 专属坑
程序里写中文,屏幕上出现一串问号或者怪符号,这个坑 Windows 用户躲不掉。
┌──────────────────────────────────────────────────────────┐
│ 中文乱码是怎么发生的 │
├──────────────────────────────────────────────────────────┤
│ 两边编码对不上,字就变了样 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 源码存成 UTF-8,字符串按 UTF-8 的字节存 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 程序把字节原样送到屏幕 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 命令行按 GBK 解码,代码页 936 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 对不上,屏幕出现乱码 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 解法三条,让两边用同一种编码 │
└──────────────────────────────────────────────────────────┘
源码存成 UTF-8,字符串按 UTF-8 的字节存。程序运行时把字节原样送到屏幕。Windows 简体中文的命令行默认按 GBK 解码,代码页 936。两边对不上,乱码就来了。
三条解法,挑一条。
- 命令行切 UTF-8,执行 chcp 65001,再跑程序。切完最好重开窗口。
- 源文件存成 GBK。VS Code 右下角点编码,Save with Encoding,选 GB2312 或 GBK。
- 换 Windows Terminal,默认 UTF-8,一劳永逸,微软商店免费下。
Linux 和 macOS 默认 UTF-8,没这个烦恼。
4.5 新手常踩的坑
| 现象 | 原因 | 解法 |
|---|---|---|
| syntax error 指到分号 | 分号打成全角 | 换回半角分号 |
| error: 'cout' was not declared | 漏了 include 或 using | 补上 |
| undefined reference to std::cout | 用 gcc 编 .cpp | 换 g++ |
| 程序能编,中文乱码 | 源码 UTF-8,命令行 GBK | 见 4.4 节 |
| error: 'mian' was not declared | main 拼错 | 改回 main |
| 提示不是内部或外部命令 | g++ 没进 PATH | 把 bin 目录加进 PATH |
报错看不懂的时候,把整段错误信息复制去搜。搜索引擎见过的新手坑比你想的多,这个习惯不高级,但是管用。
五、小结
5.1 本章知识收口表
| 模块 | 你现在应该能做对的事 | 自测 |
|---|---|---|
| 语言三代 | 说清机器、汇编、高级的差别 | □ 能解释为什么高级语言要翻译 |
| 编译与解释 | 知道 C++ 是编译型以及它带来什么 | □ 能说出编译四阶段的名字 |
| C++ 版本 | 知道 C++11 是分水岭 | □ 会加 -std=c++17 |
| 程序结构 | 认出五样成分 | □ 不看书能默写最小程序 |
| 第一个程序 | 独立写完编译跑通 | □ 屏幕上出现 Hello, World! |
| 排错 | 常见坑知道往哪查 | □ 乱码和分号错能自己修 |
┌─────────────────────────────────────────────────────┐
│ 一章走完该带走的东西 │
├─────────────────────────────────────────────────────┤
│ 学完这一章,兜里该有这五样 │
│ │
│ +--------------+--------------------------------+ │
│ |模块 |带走什么 | │
│ +--------------+--------------------------------+ │
│ |语言三代 |机器、汇编、高级,一代比一代省事| │
│ |编译与解释 |C++ 是编译型,翻一次跑很久 | │
│ |版本 |认准 C++11 之后,加 -std=c++17 | │
│ |程序结构 |include、main、return 五样东西 | │
│ |第一个程序 |写、编、跑加排错 | │
│ +--------------+--------------------------------+ │
│ │
│ 哪一块心里没底,回去找对应小节,别急着往下翻 │
└─────────────────────────────────────────────────────┘
5.2 五句话记住本章
- 程序是写给机器的指令清单,语言是中间人,C++ 站在离机器近的那一档。
- C++ 是编译型,翻一次跑很久,改代码要重编。
- 版本认准 C++11 之后,命令加 -std=c++17。
- 最小程序五样东西,include、命名空间、main、语句、return。
- 编译报错先看第一条,后面那一串常常是它引出来的。
5.3 课后练习
- 默写最小程序,不看书,编译跑通,打印 Hello, World!。
- 改 4.3 节的程序,输入两个整数,输出它们的差、积、商,商保留两位小数。
- 打印一份自我介绍,三行,姓名、城市、一句爱好,用 cout 分开写。
- 故意制造三个错,全角分号、漏 include、main 拼错,把报错信息抄下来,对着 4.5 节的表找原因。
- 执行 chcp 65001,跑一次带中文输出的程序,观察乱码消失的过程。
附录|命令速查卡
| 想干什么 | 命令 |
|---|---|
| 看编译器版本 | g++ --version |
| 编译链接一条龙 | g++ hello.cpp -o hello |
| 只做预处理 | g++ -E hello.cpp -o hello.i |
| 只做到汇编 | g++ -S hello.cpp -o hello.s |
| 只做到目标文件 | g++ -c hello.cpp -o hello.o |
| 指定 C++ 标准 | g++ -std=c++17 hello.cpp -o hello |
| 跑起来 | Windows 输 hello,Linux 和 macOS 输 ./hello |
| 命令行切 UTF-8 | chcp 65001 |