第一章 C++语言概述

0 阅读16分钟

第一章 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、RustPython、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++981998第一版国际标准,模板和 STL 进场
C++032003修 bug,没有新语法
C++112011auto、lambda、智能指针、范围 for,公认的分水岭
C++142014泛型 lambda 之类的小补丁
C++172017结构化绑定、optional、文件系统库
C++202020concepts、ranges、协程
C++232023目前最新,工具链支持还在铺开

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 StudioWindows 上做项目社区版免费,安装时勾 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。两边对不上,乱码就来了。

三条解法,挑一条。

  1. 命令行切 UTF-8,执行 chcp 65001,再跑程序。切完最好重开窗口。
  2. 源文件存成 GBK。VS Code 右下角点编码,Save with Encoding,选 GB2312 或 GBK。
  3. 换 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 declaredmain 拼错改回 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 五句话记住本章

  1. 程序是写给机器的指令清单,语言是中间人,C++ 站在离机器近的那一档。
  2. C++ 是编译型,翻一次跑很久,改代码要重编。
  3. 版本认准 C++11 之后,命令加 -std=c++17。
  4. 最小程序五样东西,include、命名空间、main、语句、return。
  5. 编译报错先看第一条,后面那一串常常是它引出来的。

5.3 课后练习

  1. 默写最小程序,不看书,编译跑通,打印 Hello, World!。
  2. 改 4.3 节的程序,输入两个整数,输出它们的差、积、商,商保留两位小数。
  3. 打印一份自我介绍,三行,姓名、城市、一句爱好,用 cout 分开写。
  4. 故意制造三个错,全角分号、漏 include、main 拼错,把报错信息抄下来,对着 4.5 节的表找原因。
  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-8chcp 65001