教学教程 —— 从零学会写汉语 C++
⬇ Chinese++ 教学教程 —— 下载 Word 版(.docx) 适合打印、发邮件、或发给不习惯看网页的人。内容与本页相同。
这本教程教你三件事:汉语关键字怎么用(每个都给出对应的英文)、 原本那套符号怎么用(
{}();::->… 一个没换)、 基础标准库怎么用(#包含 <向量>、标准::向量<整数>…)。它只讲汉语化带来的差异与够用的基础,不替代 C++ 教材。
本文件是生成物,不要手改。 由
tools\make_tutorial_cpp.ps1把教程\例子\*.cpp的真源码与「期望输出」切进教程\教程模板.md生成。tools\check_tutorial_cpp.ps1会真编译真运行每个例子并核对输出, 同时核对本文件与例子是否逐字一致 —— 所以教程里的代码必然能跑。
怎么用这本教程
- 先读「第 0 课」把编译命令跑通。
- 每课都有一份能直接编译运行的例子,放在
教程\例子\。 - 例子开头的注释里写了它自己的期望输出 —— 你跑出来的应当一模一样。
- 汉语写法和英文写法可以混着写,同一行里混都行。学会汉语不等于扔掉英文。
第 0 课 准备:编、跑
编译器:build\Release\bin\chinese++.exe(安装包用户直接写 chinese++)。
chinese++ -std=c++20 -O0 -o 你好.exe 你好.cpp
.\你好.exe一个最小程序长这样(第 1 课细讲):
#包含 <输入输出流>
整数 主函数() {
标准::标准输出流 << "你好,世界" << 标准::行尾流冲刷;
返回 0;
}第 1 课 程序骨架
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
整数 | int | 整数类型;整数 主函数() 里的 主函数 是 main 的汉语名 |
主函数 | main | 程序入口。和 main 是同一个标识符,写哪个都行,但不能两个都写 |
空型 | void | 「什么也不返回」 |
返回 | return | 从函数返回 |
#包含 | #include | 把另一个文件的内容引进来 |
本课符号
| 符号 | 名称 | 用法 |
|---|---|---|
{ } | 花括号 | 包住一段代码(函数体、类体、分支体)。成对出现 |
( ) | 圆括号 | 包住函数参数表;调用函数时也是它 |
; | 分号 | 每条语句末尾必须有(函数体、类体、# 行末尾不写) |
:: | 双冒号 | 名域/类型限定:标准::标准输出流 = std 里的 cout |
" " | 双引号 | 字符串字面量 |
' ' | 单引号 | 单个字符字面量(第 2 课用) |
# | 井号 | 预处理指令的开头,必须在一行最前面(可以缩进) |
// | 行注释 | 从这里到行尾都不算代码 |
例子的真源码
// 第 1 课:程序骨架 —— 从「你好,世界」看最小的汉语 C++ 程序
//
// 这一课的关键字(括号里是对应的英文):
// 整数(主函数写法之一)、返回(return)、空型(void)、#包含(#include)
// 符号:花括号 {}、圆括号 ()、分号 ;、双冒号 ::、双引号 ""、井号 #
//
// 期望输出:
// 你好,世界
// 汉语编程
#包含 <输入输出流> // #包含 = #include
空型 打招呼() { // 空型 = void:不返回任何东西的函数
标准::标准输出流 << "汉语编程" << 标准::行尾流冲刷;
} // 花括号包住函数体;行尾流冲刷 = std::endl
整数 主函数() { // 主函数 = main(写 整数 main() 也完全一样)
标准::标准输出流 << "你好,世界" << 标准::行尾流冲刷;
打招呼(); // 圆括号:调用函数;分号:语句结束
返回 0; // 返回 = return;0 表示正常结束
}跑出来的结果(与例子里的「期望输出」一致):
你好,世界
汉语编程讲解
标准::标准输出流就是std::cout,标准::行尾流冲刷就是std::endl。<<是「往流里塞东西」,可以连着塞。空型 打招呼()定义了一个不返回值的函数;调用它写打招呼();。- 三个地方最容易忘:语句末尾的
;、函数体的{ }、#包含的#。
练习
- 把
主函数改成main,重新编译 —— 结果应当完全一样。 - 加一个
空型 告别()打印一行「再见」,并在主函数里调用它。
第 2 课 变量与类型
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
整数 | int | 整数 |
双精度 | double | 双精度浮点 |
单精度 | float | 单精度浮点 |
字符 | char | 单个字符 |
布尔 | bool | 真/假 |
真值 / 假值 | true / false | 布尔字面量 |
自动 | auto | 类型由右边的值推出来 |
常量 | const | 之后不许改 |
长型 / 短型 | long / short | 更宽/更窄的整数 |
无符号 / 有符号 | unsigned / signed | 有没有符号位 |
空指针 | nullptr | 空指针值(不是类型) |
大小 | sizeof | 某个类型/值占多少字节 |
本课符号
| 符号 | 用法 |
|---|---|
= | 赋值:整数 年龄 = 18; |
, | 一行里声明多个:整数 甲 = 1, 乙 = 2; |
( ) | 大小(整数) —— sizeof 后面要跟括号包住类型 |
后缀 f / L / u | 60.5f(单精度)、1000000000L(长整型)、10u(无符号) |
例子的真源码
// 第 2 课:变量与类型
//
// 这一课的关键字(英文对照):
// 整数(int) 双精度(double) 单精度(float) 字符(char) 布尔(bool)
// 真值(true) 假值(false) 自动(auto) 常量(const) 长型(long) 短型(short)
// 无符号(unsigned) 有符号(signed) 空型(void) 大小(sizeof) 空指针(nullptr)
//
// 期望输出:
// 年龄=18
// 身高=1.75
// 体重=60.5
// 首字母=A
// 已成年=1
// 推断=42
// 圆周率=3.14159
// 整数占4字节
// 空指针是1
// 大数=1000000000 计数=10
#包含 <输入输出流>
整数 主函数() {
整数 年龄 = 18; // 整数 = int
双精度 身高 = 1.75; // 双精度 = double
单精度 体重 = 60.5f; // 单精度 = float(末尾 f 是 C++ 原本的写法)
字符 首字母 = 'A'; // 字符 = char;单引号包单个字符
布尔 已成年 = 真值; // 布尔 = bool;真值 = true
自动 推断 = 42; // 自动 = auto:类型由右边的值推出来
常量 双精度 圆周率 = 3.14159; // 常量 = const:之后不许改
长型 大数 = 1000000000L; // 长型 = long
无符号 计数 = 10u; // 无符号 = unsigned
自动 空 = 空指针; // 空指针 = nullptr(是个值,不是类型)
标准::标准输出流 << "年龄=" << 年龄 << 标准::行尾流冲刷;
标准::标准输出流 << "身高=" << 身高 << 标准::行尾流冲刷;
标准::标准输出流 << "体重=" << 体重 << 标准::行尾流冲刷;
标准::标准输出流 << "首字母=" << 首字母 << 标准::行尾流冲刷;
标准::标准输出流 << "已成年=" << 已成年 << 标准::行尾流冲刷;
标准::标准输出流 << "推断=" << 推断 << 标准::行尾流冲刷;
标准::标准输出流 << "圆周率=" << 圆周率 << 标准::行尾流冲刷;
标准::标准输出流 << "整数占" << 大小(整数) << "字节" << 标准::行尾流冲刷;
标准::标准输出流 << "空指针是" << (空 == 空指针) << 标准::行尾流冲刷;
标准::标准输出流 << "大数=" << 大数 << " 计数=" << 计数 << 标准::行尾流冲刷;
返回 0;
}跑出来的结果:
年龄=18
身高=1.75
体重=60.5
首字母=A
已成年=1
推断=42
圆周率=3.14159
整数占4字节
空指针是1
大数=1000000000 计数=10讲解
自动 推断 = 42;里推断就是int,因为右边是整数。常量 双精度 圆周率 = 3.14159;—— 顺序是「常量 → 类型 → 名字」。空指针是个值:自动 空 = 空指针;。别写空指针 空;(那不是类型)。- 布尔值往流里塞会打印成
1/0。
练习
- 声明一个
常量 整数 天数 = 7;,再试着给它赋值 —— 看编译器怎么拦你。 - 用
大小(双精度)打印double占几字节。
第 3 课 运算符与符号
本课最重要的一句话:符号一个都没换。 汉语化增加的是关键字的另一种拼写, 不是把 + - * / 换成汉字。唯一的例外是 11 个「运算符替代拼写」—— 它们本来就是英文 and / or / not 那一族的等价物。
本课关键字(运算符替代拼写)
| 汉语 | 等价英文 | 等价符号 |
|---|---|---|
并且 | and | && |
或者 | or | || |
并非 | not | ! |
不等于 | not_eq | != |
位与 | bitand | & |
位或 | bitor | | |
异或 | xor | ^ |
取反 | compl | ~ |
与赋值 | and_eq | &= |
或赋值 | or_eq | |= |
异或赋值 | xor_eq | ^= |
本课符号
| 类别 | 符号 | 说明 |
|---|---|---|
| 算术 | + - * / % | % 是取余 |
| 比较 | == != < > <= >= | 结果是 1(真)或 0(假) |
| 赋值 | = += -= *= /= %= | 甲 += 1 等价 甲 = 甲 + 1 |
| 自增自减 | ++ -- | 前置后置都照写 |
| 逻辑 | && || ! | 也可以写 并且 或者 并非 |
| 位运算 | & | ^ ~ << >> | 也可以写 位与 位或 异或 取反 |
| 三元 | ? : | 条件 ? 真时取值 : 假时取值 |
例子的真源码
// 第 3 课:运算符与符号怎么用
//
// 这一课要认清一件事:【符号照旧是那套 ASCII 符号】,
// 汉语化增加的是「关键字的另一种拼写」,没有把符号换成汉字。
// 唯一的例外是 11 个逻辑/位运算关键字(并且 或者 并非 …),
// 它们本来就是英文 and / or / not 那一族的等价拼写。
//
// 关键字(英文对照):
// 并且(and/&&) 或者(or/||) 并非(not/!) 位与(bitand/&) 位或(bitor/|)
// 取反(compl/~) 异或(xor/^) 与赋值(and_eq/&=) 或赋值(or_eq/|=) 异或赋值(xor_eq/^=) 不等于(not_eq/!=)
// 真值(true) 假值(false) 常量(const)
//
// 期望输出:
// 加=10 减=4 乘=21 除=2 取余=1
// 比较: 1 0 1 1
// 并且=0 或者=1 并非=0
// 三元取大=7
// 自增后=9 自减后=8
// 位与=2 位或=7 异或=5 取反=-7
// 不等于=1
#包含 <输入输出流>
整数 主函数() {
整数 甲 = 7;
整数 乙 = 3;
// —— 算术:+ - * / % 都是原样照写 ——
标准::标准输出流 << "加=" << (甲 + 乙)
<< " 减=" << (甲 - 乙)
<< " 乘=" << (甲 * 乙)
<< " 除=" << (甲 / 乙)
<< " 取余=" << (甲 % 乙) << 标准::行尾流冲刷;
// —— 比较:== != < > <= >= 也是原样照写;结果 1 表示真、0 表示假 ——
标准::标准输出流 << "比较: " << (甲 > 乙) << " " << (甲 < 乙)
<< " " << (甲 != 乙) << " " << (甲 >= 7) << 标准::行尾流冲刷;
// —— 逻辑:这里可以写 并且/或者/并非,也可以写 && || ! ——
布尔 真 = 真值;
布尔 假 = 假值;
标准::标准输出流 << "并且=" << (真 并且 假)
<< " 或者=" << (真 或者 假)
<< " 并非=" << (并非 真) << 标准::行尾流冲刷;
// —— 三元运算符 ?: 照写 ——
标准::标准输出流 << "三元取大=" << (甲 > 乙 ? 甲 : 乙) << 标准::行尾流冲刷;
// —— 赋值与自增自减 ——
整数 数 = 8;
数 += 1; // 等价于 数 = 数 + 1
标准::标准输出流 << "自增后=" << 数;
数 -= 1;
标准::标准输出流 << " 自减后=" << 数 << 标准::行尾流冲刷;
数++; // 后缀自增,也照写
--数; // 前缀自减,也照写
// —— 位运算:既可以写 & | ^ ~,也可以写 位与/位或/异或/取反 ——
标准::标准输出流 << "位与=" << (6 位与 3)
<< " 位或=" << (6 位或 3)
<< " 异或=" << (6 异或 3)
<< " 取反=" << (取反 6) << 标准::行尾流冲刷;
// —— 不等于也有中文拼写 ——
标准::标准输出流 << "不等于=" << (甲 不等于 乙) << 标准::行尾流冲刷;
返回 0;
}跑出来的结果:
加=10 减=4 乘=21 除=2 取余=1
比较: 1 0 1 1
并且=0 或者=1 并非=0
三元取大=7
自增后=9 自减后=8
位与=2 位或=7 异或=5 取反=-7
不等于=1讲解
并且 / 或者 / 并非只替换逻辑运算符那三个,不要拿它们当&&的别名去写位运算 —— 位运算有自己的三个词(位与 / 位或 / 异或)。!=有汉语拼写不等于;但==没有,照写==。?:里的?和:是符号,照写。
练习
- 用
位与、位或、异或各算一次,再用&、|、^算一次,确认结果一样。 - 用三元运算符打印两个数里的较小者。
第 4 课 分支与循环
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
如果 / 否则 | if / else | 分支 |
选择 | switch | 多分支 |
情形 | case | 选择里的一个分支 |
默认 | default | 选择里的兜底分支 |
对于 | for | 计次循环 / 范围遍历 |
每当 | while | 先判断再执行 |
执行 | do | 先执行一次再判断(搭配 每当 收尾) |
跳出 | break | 结束整个循环 |
跳过 | continue | 跳过本轮剩下的 |
本课符号
| 写法 | 说明 |
|---|---|
如果 (条件) { … } | 条件必须用 ( ) 包起来 |
如果 … 否则 { … } | 否则 前不写分号 |
选择 (值) { 情形 1: … 跳出; 默认: … } | 情形 后面是冒号,每个 情形 末尾要 跳出 |
对于 (初值; 条件; 步进) { … } | 三段用分号隔开 |
执行 { … } 每当 (条件); | do-while:末尾那个 每当 后面必须有 ; |
对于 (自动 项 : 容器) { … } | 范围遍历,: 读作「在…里」 |
例子的真源码
// 第 4 课:分支与循环
//
// 关键字(英文对照):
// 如果(if) 否则(else) 选择(switch) 情形(case) 默认(default)
// 对于(for) 每当(while) 执行(do) 跳出(break) 跳过(continue)
// 整数(int) 自动(auto) 真值(true) 假值(false)
// 符号:{} 包住代码块、() 包住条件、; 结束语句、: 跟在 情形/默认 后面
//
// 期望输出:
// 成绩及格
// 等级=C
// 累加=10
// 每当两遍后=2
// 执行后=2
// 跳过偶数后=1 3 5
// 遇到5就跳出=1 2 3 4
// 范围遍历=3 1 2
#包含 <输入输出流>
#包含 <向量>
整数 主函数() {
整数 分数 = 75;
// —— 如果 / 否则 ——
如果 (分数 >= 60) {
标准::标准输出流 << "成绩及格" << 标准::行尾流冲刷;
} 否则 {
标准::标准输出流 << "成绩不及格" << 标准::行尾流冲刷;
}
// —— 选择 / 情形 / 默认:每个情形末尾要写 跳出,否则会继续往下穿 ——
整数 档 = 分数 / 10;
选择 (档) {
情形 9: 标准::标准输出流 << "等级=A" << 标准::行尾流冲刷; 跳出;
情形 8: 标准::标准输出流 << "等级=B" << 标准::行尾流冲刷; 跳出;
情形 7: 标准::标准输出流 << "等级=C" << 标准::行尾流冲刷; 跳出;
默认: 标准::标准输出流 << "等级=D" << 标准::行尾流冲刷;
}
// —— 对于(有初值、条件、步进三段,用分号隔开)——
整数 累加 = 0;
对于 (整数 甲 = 1; 甲 <= 4; 甲++) {
累加 += 甲;
}
标准::标准输出流 << "累加=" << 累加 << 标准::行尾流冲刷;
// —— 每当(先判断再执行)——
整数 遍 = 0;
每当 (遍 < 2) {
遍++;
}
标准::标准输出流 << "每当两遍后=" << 遍 << 标准::行尾流冲刷;
// —— 执行…每当(先执行一次再判断)——
整数 次 = 0;
执行 {
次++;
} 每当 (次 < 2);
标准::标准输出流 << "执行后=" << 次 << 标准::行尾流冲刷;
// —— 跳过:跳过本轮剩下的,进下一轮 ——
标准::标准输出流 << "跳过偶数后=";
对于 (整数 甲 = 1; 甲 <= 6; 甲++) {
如果 (甲 % 2 == 0) 跳过;
标准::标准输出流 << 甲 << " ";
}
标准::标准输出流 << 标准::行尾流冲刷;
// —— 跳出:直接结束整个循环 ——
标准::标准输出流 << "遇到5就跳出=";
对于 (整数 甲 = 1; 甲 <= 9; 甲++) {
如果 (甲 == 5) 跳出;
标准::标准输出流 << 甲 << " ";
}
标准::标准输出流 << 标准::行尾流冲刷;
// —— 范围遍历:对于 (自动 项 : 容器) ——
标准::向量<整数> 表 = {3, 1, 2};
标准::标准输出流 << "范围遍历=";
对于 (自动 项 : 表) {
标准::标准输出流 << 项 << " ";
}
标准::标准输出流 << 标准::行尾流冲刷;
返回 0;
}跑出来的结果:
成绩及格
等级=C
累加=10
每当两遍后=2
执行后=2
跳过偶数后=1 3 5
遇到5就跳出=1 2 3 4
范围遍历=3 1 2讲解
选择里的情形 9:情形 8:… 是整数常量;忘了写跳出会一路穿下去。执行 { … } 每当 (条件);—— 注意末尾那个分号,这是do-while的固定格式。- 范围遍历
对于 (自动 项 : 表)里的项是每个元素的一份拷贝; 想改原元素得写自动& 项(第 5 课讲&)。
练习
- 用
选择把「星期几」翻译成中文(1→星期一 … 7→星期日)。 - 用
每当算 1 到 100 的和,再用对于算一遍,对照结果。
第 5 课 函数
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
空型 | void | 不返回值 |
返回 | return | 返回值 / 提前结束 |
自动 | auto | 让编译器推返回类型 |
常量 | const | 只读参数 |
本课符号(这一课的符号最关键)
| 符号 | 位置 | 含义 |
|---|---|---|
( ) | 函数名后 | 参数表;空着表示没有参数 |
, | 参数之间 | 分隔参数 |
& | 类型后:整数& 甲 | 引用:函数里改的就是外面那个变量 |
& | 变量前:&值 | 取地址:把变量的地址交出去 |
* | 类型后:整数* 指针 | 指针类型 |
* | 指针前:*指针 | 解引用:取出它指向的那个值 |
-> | 指针后 | 用指针访问成员 |
:: | 名域后 | 限定到某个名域/类型 |
一句话记住:
&和*在类型那边是「是什么」,在变量那边是「取什么」。
例子的真源码
// 第 5 课:函数 —— 定义、参数、返回、重载、引用、指针
//
// 关键字(英文对照):整数(int) 双精度(double) 空型(void) 返回(return) 如果(if)
// 符号:
// () 参数表 / 调用
// , 分隔多个参数
// & 写在类型后 = 引用(改的是原本那个变量);写在变量前 = 取地址
// * 写在类型后 = 指针;写在指针前 = 取出它指向的值
// -> 用指针访问成员
// :: 名域限定
//
// 期望输出:
// 两数之和=7
// 两数之和(小数)=3.5
// 交换后 甲=2 乙=1
// 通过指针改后=99
// 递归阶乘(5)=120
// 默认参数=10
#包含 <输入输出流>
整数 相加(整数 甲, 整数 乙) { 返回 甲 + 乙; } // 重载①:两个整数
双精度 相加(双精度 甲, 双精度 乙) { 返回 甲 + 乙; } // 重载②:两个双精度
空型 交换(整数& 甲, 整数& 乙) { // & = 引用:函数里改的就是外面那两个
整数 临时 = 甲;
甲 = 乙;
乙 = 临时;
}
空型 置为九十九(整数* 指针) { // * = 指针:*指针 是它指向的那个值
*指针 = 99;
}
整数 阶乘(整数 数) { // 函数调用自己 = 递归
如果 (数 <= 1) 返回 1;
返回 数 * 阶乘(数 - 1);
}
整数 加上(整数 甲, 整数 乙 = 3) { 返回 甲 + 乙; } // 默认参数:不传就用 3
整数 主函数() {
标准::标准输出流 << "两数之和=" << 相加(3, 4) << 标准::行尾流冲刷;
标准::标准输出流 << "两数之和(小数)=" << 相加(1.5, 2.0) << 标准::行尾流冲刷;
整数 甲 = 1, 乙 = 2;
交换(甲, 乙);
标准::标准输出流 << "交换后 甲=" << 甲 << " 乙=" << 乙 << 标准::行尾流冲刷;
整数 值 = 1;
置为九十九(&值); // &值 = 取出 值 的地址
标准::标准输出流 << "通过指针改后=" << 值 << 标准::行尾流冲刷;
标准::标准输出流 << "递归阶乘(5)=" << 阶乘(5) << 标准::行尾流冲刷;
标准::标准输出流 << "默认参数=" << 加上(7) << 标准::行尾流冲刷;
返回 0;
}跑出来的结果:
两数之和=7
两数之和(小数)=3.5
交换后 甲=2 乙=1
通过指针改后=99
递归阶乘(5)=120
默认参数=10讲解
- 同名函数只要参数不同就能共存(重载),调用时编译器自己挑。
空型 交换(整数& 甲, 整数& 乙)改的是外面那两个变量; 如果去掉&,改的只是副本,外面看不到变化 —— 这是 C++ 最常见的坑之一。加上(整数 甲, 整数 乙 = 3)的= 3是默认参数,调用时可以少传一个。
练习
- 写一个
整数 最大值(整数 甲, 整数 乙),返回较大的那个。 - 把
交换的&去掉,重新编译运行 —— 看输出怎么变(这就是坑的样子)。
第 6 课 自己定义类型
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
类型 | class | 定义类型(注意:不是「类」,汉语关键字一律不少于两个字) |
结构 | struct | 默认成员公开的类型 |
枚举 | enum | 枚举 |
公开 / 私有 / 受保护 | public / private / protected | 访问说明符 |
自身 | this | 指向当前对象 |
虚拟 | virtual | 允许派生类改写 |
重写 | override | 声明「我在改写基类的虚函数」 |
最终 | final | 到此为止,不许再被继承/改写 |
静态 | static | 属于类型本身,不属于某个对象 |
新建 / 删除 | new / delete | 动态分配 / 释放 |
使用 | using | 引进名字 / 起别名 |
类型别名 | typedef | 起类型别名(老写法) |
本课符号
| 写法 | 说明 |
|---|---|
类型 名字 { … }; | 类体末尾必须有分号 |
公开: | 访问说明符后面是冒号,不是分号 |
类型 派生 : 公开 基类 { … }; | 继承:冒号 + 访问说明符 + 基类名 |
. | 用对象访问成员:甲.面积() |
-> | 用指针访问成员:指针->说() |
:: | 类名后限定:人::数量、整数 人::数量 = 0; |
例子的真源码
// 第 6 课:自己定义类型
//
// 关键字(英文对照):
// 类型(class) 结构(struct) 枚举(enum) 公开(public) 私有(private) 受保护(protected)
// 自身(this) 虚拟(virtual) 重写(override) 静态(static) 空型(void) 返回(return)
// 使用(using) 类型别名(typedef) 常量(const)
// 符号:
// { } 包住类体
// ; 类定义末尾必须有分号
// 公开: / 私有: 访问说明符后面是冒号
// : 公开 基类 继承写法
// . 用对象访问成员
// -> 用指针访问成员
// :: 类名后跟 :: 用来限定成员(类外定义、静态成员)
//
// 期望输出:
// 面积=12
// 打招呼:小明
// 静态计数=2
// 形状说:我是形状
// 圆说:我是圆
// 颜色序号=1
// 别名变量=5
#包含 <输入输出流>
#包含 <字符串>
类型 长方形 { // 类型 = class
公开: // 公开 = public
整数 宽;
整数 高;
长方形(整数 宽值, 整数 高值) { // 构造函数:与类型同名,无返回值
自身->宽 = 宽值; // 自身 = this;箭头访问成员
自身->高 = 高值;
}
整数 面积() { 返回 宽 * 高; } // 类里面可以直接写成员名
};
类型 人 {
公开:
标准::字符串 名字;
空型 打招呼() { 标准::标准输出流 << "打招呼:" << 名字 << 标准::行尾流冲刷; }
静态 整数 数量; // 静态 = static:属于类型本身,不属于某个对象
};
整数 人::数量 = 0; // 类外定义静态成员;:: 限定到 人
类型 形状 {
公开:
虚拟 标准::字符串 说() { 返回 "我是形状"; } // 虚拟 = virtual:允许派生类改写它
};
类型 圆 : 公开 形状 { // 冒号 + 公开 基类 = 继承
公开:
标准::字符串 说() 重写 { 返回 "我是圆"; } // 重写 = override
};
枚举 颜色 { 红, 绿, 蓝 }; // 枚举 = enum,成员自动是 0、1、2
使用 整数别名 = 整数; // 使用 = using(也可写:类型别名 整数别名 整数;)
整数 主函数() {
长方形 甲(3, 4);
标准::标准输出流 << "面积=" << 甲.面积() << 标准::行尾流冲刷; // . 访问对象成员
人 小明;
小明.名字 = "小明";
小明.打招呼();
人::数量 = 2;
标准::标准输出流 << "静态计数=" << 人::数量 << 标准::行尾流冲刷;
形状 形;
圆 圈;
形状* 指针 = &圈; // 基类指针指向派生类对象
标准::标准输出流 << "形状说:" << 形.说() << 标准::行尾流冲刷;
标准::标准输出流 << "圆说:" << 指针->说() << 标准::行尾流冲刷; // -> 用指针访问成员
颜色 我的 = 绿;
标准::标准输出流 << "颜色序号=" << 我的 << 标准::行尾流冲刷;
整数别名 数 = 5;
标准::标准输出流 << "别名变量=" << 数 << 标准::行尾流冲刷;
返回 0;
}跑出来的结果:
面积=12
打招呼:小明
静态计数=2
形状说:我是形状
圆说:我是圆
颜色序号=1
别名变量=5讲解
- 构造函数与类型同名、不写返回类型:
长方形(整数 宽值, 整数 高值) { … }。 自身->宽 = 宽值;——自身就是this,这里用->。 类里面访问自己的成员也可以直接写名字(宽 * 高)。虚拟+重写才有多态:基类指针指向派生类对象时,调用的是派生类那一版。 例子里的圆说:我是圆就是这么来的;去掉虚拟就会变成「我是形状」。枚举 颜色 { 红, 绿, 蓝 };里红=0、绿=1、蓝=2。
练习
- 给
长方形加一个整数 周长() { 返回 2 * (宽 + 高); }并在主函数里打印。 - 把例子里的
虚拟去掉,重新编译运行 —— 看输出变成什么。
第 7 课 模板与名域
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
模板 | template | 声明模板 |
类型名 | typename | 模板里表示「某个类型」 |
名域 | namespace | 名域 |
使用 | using | 引进名字 |
常量表达式 | constexpr | 编译期就能算出来的常量/函数 |
本课符号
| 写法 | 说明 |
|---|---|
模板 <类型名 元素类型> | 模板参数表用 < > 包起来 |
标准::向量<整数> | 模板实参也用 < >,注意和比较运算符 < 区分 |
我的名域::值 | :: 限定进名域 |
使用 我的名域::取; | 把 取 引进当前作用域,之后直接写 取() |
例子的真源码
// 第 7 课:模板与名域
//
// 关键字(英文对照):
// 模板(template) 类型名(typename) 名域(namespace) 使用(using)
// 常量表达式(constexpr) 自动(auto) 整数(int) 返回(return)
// 符号:< > 包住模板参数;:: 名域限定;{ } ( ) ; 照旧
//
// 期望输出:
// 两数较大=9
// 双精度较大=2.5
// 常量表达式平方=25
// 我的名域里的值=42
// 简写后=42
// 全局计数器=1
#包含 <输入输出流>
模板 <类型名 元素类型> // 模板 = template;类型名 = typename
元素类型 取较大(元素类型 甲, 元素类型 乙) { // 一份代码,整数和双精度都能用
返回 甲 > 乙 ? 甲 : 乙;
}
常量表达式 整数 平方(整数 数) { 返回 数 * 数; } // 常量表达式 = constexpr:编译期就能算出来
名域 我的名域 { // 名域 = namespace
整数 值 = 42;
整数 取() { 返回 值; }
}
整数 计数器 = 0; // 全局变量(任何函数都能看到)
空型 加一() { 计数器++; }
使用 我的名域::取; // 使用 = using:把名字引进来,之后直接写 取()
整数 主函数() {
标准::标准输出流 << "两数较大=" << 取较大(3, 9) << 标准::行尾流冲刷;
标准::标准输出流 << "双精度较大=" << 取较大(1.5, 2.5) << 标准::行尾流冲刷;
标准::标准输出流 << "常量表达式平方=" << 平方(5) << 标准::行尾流冲刷;
标准::标准输出流 << "我的名域里的值=" << 我的名域::值 << 标准::行尾流冲刷; // :: 限定进名域
标准::标准输出流 << "简写后=" << 取() << 标准::行尾流冲刷;
加一();
标准::标准输出流 << "全局计数器=" << 计数器 << 标准::行尾流冲刷;
返回 0;
}跑出来的结果:
两数较大=9
双精度较大=2.5
常量表达式平方=25
我的名域里的值=42
简写后=42
全局计数器=1讲解
- 模板让一份代码同时服务于
整数和双精度:例子里的取较大被用了两次, 一次给整数、一次给浮点。 ::是「从里面找」:标准::向量=std里面的vector。 模板实参的<>和比较的<、>长得像,编译器靠上下文区分。常量表达式 整数 平方(整数 数)能在编译期求值 —— 用它写的常量不会带来运行时开销。
练习
- 写一个模板
模板 <类型名 元素类型> 空型 打印(元素类型 值),打印任意类型的值。 - 在自己的
名域里放两个函数,用::调用它们。
第 8 课 基础标准库
汉语标准库的规矩很简单:头名有汉语的,类型名与成员名也有一套汉语的, 而英文原名照旧可用。
| 东西 | 汉语写法 | 英文写法(照旧可用) |
|---|---|---|
| 头 | #包含 <向量> | #include <vector> |
| 类型 | 标准::向量<整数> | std::vector<int> |
| 成员 | 数.尾插(4) | 数.push_back(4) |
| 输出 | 标准::标准输出流 | std::cout |
| 换行 | 标准::行尾流冲刷 | std::endl |
本课名字(英文对照)
向量(vector)、字符串(string)、映射(map)、排序(sort)、 最大值(max)、最小值(min)、尺寸(size)、尾插(push_back)、 子串(substr)、开始(begin)、结束(end)。
例子的真源码
// 第 8 课:基础标准库怎么用
//
// 汉语标准库的做法:头名用汉语,类型名与成员名也有一套汉语的,
// 【英文原名照旧可用】—— 两者可以混着写。
// 头名: #包含 <向量> 等价于 #include <vector>
// 类型名: 标准::向量<整数> 等价于 std::vector<int>
// 成员名: 数.尾插(4) 等价于 数.push_back(4)
//
// 关键字/名字(英文对照):
// 向量(vector) 字符串(string) 映射(map) 排序(sort) 最大值(max) 最小值(min)
// 尺寸(size) 尾插(push_back) 子串(substr) 追加(append)
// 开始(begin) 结束(end) 标准输出流(std::cout) 行尾流冲刷(std::endl)
//
// 期望输出:
// 向量尺寸=4 首=3 末=2
// 排序后=1 2 3 9
// 尾插后尺寸=5
// 字符串=你好,世界 字节数=15
// 子串=世界
// find位置=9
// 映射里的乙=2
// 最大值=9 最小值=1
//
// 两个必须知道的事:
// ① 标准::字符串 的 尺寸() 数的是【字节】,不是汉字个数。
// "你好,世界" 每个汉字 3 字节、全角逗号也 3 字节,共 15 字节。
// ② 汉化不是整齐划一的:有些成员只有英文名(比如 find),
// 有些汉语名是合成的(rfind → 反向查找、find_first_of → 查找首次属于)。
// 拿不准时就写英文 —— 英文【永远】可用。
#包含 <输入输出流>
#包含 <向量>
#包含 <字符串>
#包含 <映射>
#包含 <算法>
整数 主函数() {
// —— 向量:可以变长的数组 ——
标准::向量<整数> 数 = {3, 1, 9, 2};
标准::标准输出流 << "向量尺寸=" << 数.尺寸()
<< " 首=" << 数[0] // [ ] 取下标,从 0 开始
<< " 末=" << 数[数.尺寸() - 1] << 标准::行尾流冲刷;
标准::排序(数.开始(), 数.结束()); // 开始()/结束() 给排序划范围
标准::标准输出流 << "排序后=";
对于 (自动 项 : 数) {
标准::标准输出流 << 项 << " ";
}
标准::标准输出流 << 标准::行尾流冲刷;
数.尾插(5); // 尾插 = push_back
标准::标准输出流 << "尾插后尺寸=" << 数.尺寸() << 标准::行尾流冲刷;
// —— 字符串 ——
标准::字符串 名 = "你好,世界";
标准::标准输出流 << "字符串=" << 名 << " 字节数=" << 名.尺寸() << 标准::行尾流冲刷;
标准::标准输出流 << "子串=" << 名.子串(9, 6) << 标准::行尾流冲刷; // 子串(起点, 长度),按字节
标准::标准输出流 << "find位置=" << 名.find("世界") << 标准::行尾流冲刷; // find 只有英文名,照写
// —— 映射:键值对 ——
标准::映射<标准::字符串, 整数> 龄;
龄["甲"] = 1; // [ ] 也可以用来插入键
龄["乙"] = 2;
标准::标准输出流 << "映射里的乙=" << 龄["乙"] << 标准::行尾流冲刷;
// —— 算法:最大值 / 最小值(注意不是「最大」「最小」)——
标准::标准输出流 << "最大值=" << 标准::最大值(9, 1)
<< " 最小值=" << 标准::最小值(9, 1) << 标准::行尾流冲刷;
返回 0;
}跑出来的结果:
向量尺寸=4 首=3 末=2
排序后=1 2 3 9
尾插后尺寸=5
字符串=你好,世界 字节数=15
子串=世界
find位置=9
映射里的乙=2
最大值=9 最小值=1讲解
标准::向量<整数> 数 = {3, 1, 9, 2};—— 花括号初始化列表。数[0]取下标(从 0 开始),数.尺寸()是元素个数。标准::排序(数.开始(), 数.结束());—— 排序要两个迭代器圈定范围。- ⚠
标准::字符串的尺寸()数的是字节,不是汉字个数。"你好,世界"共 15 字节(每个汉字 3 字节,全角逗号也 3 字节)。 - ⚠ 汉化不是整齐划一的:
find只有英文名,substr的汉语名是子串。 拿不准时写英文 —— 英文永远可用。 - 注意是
最大值/最小值,不是最大/最小。
练习
- 用
标准::排序把{5, 2, 8, 1}排好序并打印。 - 用
标准::映射<标准::字符串, 整数>存三个人的年龄,再打印其中一个人的。
第 9 课 预处理指令
本课指令(英文对照)
| 汉语 | 英文 | 干什么 |
|---|---|---|
#包含 | #include | 引入头文件 |
#定义 | #define | 定义宏 |
#取消定义 | #undef | 取消宏 |
#如果已定义 | #ifdef | 定义了就走这一段 |
#如果未定义 | #ifndef | 没定义就走这一段 |
#如果 / #否则若 | #if / #elif | 条件 |
#否则 | #else | 否则 |
#结束如果 | #endif | 结束条件块 |
#编译指示 | #pragma | 编译器指示 |
本课符号
| 符号 | 说明 |
|---|---|
# | 指令行开头,按行生效:不吃缩进规则、末尾不写分号 |
< > | #包含 <向量> 尖括号:系统头 |
" " | #包含 "我的头.h" 双引号:自己写的头 |
例子的真源码
// 第 9 课:预处理指令(以 # 开头的那些行)
//
// 指令也有汉语拼写,而且【中英可以混着写】:
// #包含(#include) #定义(#define) #取消定义(#undef)
// #如果已定义(#ifdef) #如果未定义(#ifndef) #否则(#else) #结束如果(#endif)
// #编译指示(#pragma)
//
// 一个容易踩的坑(本课就是踩出来的):
// `标准::字符串` 是【汉语头】带进来的名字;只写英文的 `#include <string>`,
// 里面是微软原版的 `std::string`,没有 `标准::字符串`。
// 所以:想要汉语名字,就包含汉语头;两边都要,就两个都包含。
//
// 期望输出:
// 版本号已定义=2
// 未定义分支走了这里
// 中英混写也认
// 英文类型也能用,尺寸=3
// 结束
#包含 <输入输出流>
#包含 <字符串>
#include <vector> // 英文指令与中文指令混着写,照样编得过
#定义 版本号 2
整数 主函数() {
// 注意:以 # 开头的行【按行生效,不吃缩进、末尾也不写分号】
#如果已定义 版本号
标准::标准输出流 << "版本号已定义=" << 版本号 << 标准::行尾流冲刷;
#否则
标准::标准输出流 << "没有版本号" << 标准::行尾流冲刷;
#结束如果
#如果未定义 不存在的宏
标准::标准输出流 << "未定义分支走了这里" << 标准::行尾流冲刷;
#结束如果
标准::字符串 文本 = "中英混写也认"; // 来自 #包含 <字符串>
标准::标准输出流 << 文本 << 标准::行尾流冲刷;
std::vector<整数> 一堆 = {1, 2, 3}; // 英文类型名 + 汉语关键字
标准::标准输出流 << "英文类型也能用,尺寸=" << 一堆.size() << 标准::行尾流冲刷;
#取消定义 版本号
标准::标准输出流 << "结束" << 标准::行尾流冲刷;
返回 0;
}跑出来的结果:
版本号已定义=2
未定义分支走了这里
中英混写也认
英文类型也能用,尺寸=3
结束讲解
- 中英可以混写:同一份文件里
#包含 <字符串>和#include <vector>并存没问题。 - ⚠ 本课踩出来的坑:
标准::字符串是汉语头带进来的名字。 只写英文的#include <string>,里面是微软原版的std::string, 没有标准::字符串。想要汉语名字就包含汉语头。 #如果已定义后面跟宏名,不跟括号。#定义 版本号 2之后,版本号在代码里就当2用。
练习
#定义 平方(x) ((x)*(x)),用它算平方(5)。- 用
#如果未定义写一个「防重复包含」的头文件。
附录 A 符号用法速查
这张表要背下来的只有一句话:符号一个都没换。 汉语化只让关键字多了汉语拼写;下面这些符号在汉语代码里照旧。
| 符号 | 名字 | 典型写法 | 别忘的事 |
|---|---|---|---|
// | 行注释 | // 说明 | 到行尾 |
/* */ | 块注释 | /* 多行 */ | 不能嵌套 |
# | 井号 | #包含 <向量> | 按行生效,末尾不写分号 |
; | 分号 | 返回 0; | 每条语句末尾 |
{ } | 花括号 | 整数 主函数() { … } | 成对;类体末尾还要分号 |
( ) | 圆括号 | 如果 (甲 > 乙)/打招呼() | 条件、参数、调用都用它 |
[ ] | 方括号 | 数[0]/整数 表[10]; | 下标从 0 开始 |
. | 点 | 甲.面积() | 用对象访问成员 |
-> | 箭头 | 指针->说() | 用指针访问成员 |
:: | 双冒号 | 标准::向量/人::数量 | 名域/类型限定 |
< > | 尖括号 | 标准::向量<整数>/模板 <类型名 T> | 模板参数 |
" " | 双引号 | "你好" | 字符串 |
' ' | 单引号 | 'A' | 单个字符 |
\ | 反斜杠 | "\n"/"引号\"里面" | 转义 |
, | 逗号 | 相加(3, 4) | 分隔参数/声明多个变量 |
= | 等号 | 整数 甲 = 1; | 赋值;比较要写 == |
== != | 相等/不等 | 甲 == 乙 | 结果是 1 或 0 |
< > <= >= | 大小比较 | 甲 >= 60 | 结果是 1 或 0 |
+ - * / % | 算术 | 甲 % 2 | % 取余 |
+= -= *= /= | 复合赋值 | 甲 += 1 | 等价 甲 = 甲 + 1 |
++ -- | 自增自减 | 甲++/--甲 | 前置后置都可 |
&& || ! | 逻辑 | 真 && 假 | 也可写 并且 或者 并非 |
& | ^ ~ << >> | 位运算 | 6 & 3 | 也可写 位与 位或 异或 取反;<< 还兼做流插入 |
& | 引用/取地址 | 整数& 甲/&值 | 在类型那边是引用,在变量那边是取地址 |
* | 指针/解引用 | 整数* 指针/*指针 | 同上 |
? : | 三元 | 甲 > 乙 ? 甲 : 乙 | 条件 ? 真值 : 假值 |
附录 B 关键字总表
下面四张表是从 packaging\中文对照表.md 原样切进来的 —— 那张表由 tools\make_cheatsheet.py 从编译器真正在用的那两张表自动生成,并有守卫防它漂移。 放进教程里是为了让你不用来回翻文件(教学文档应当自包含)。
一、关键字(88 个)
| 中文 | 英文 | 中文 | 英文 |
|---|---|---|---|
对齐 | alignas | 长型 | long |
对齐值 | alignof | 模块 | module |
汇编 | asm | 可变 | mutable |
自动 | auto | 名域 | namespace |
布尔 | bool | 新建 | new |
跳出 | break | 不抛出 | noexcept |
情形 | case | 空指针 | nullptr |
捕获 | catch | 运算符 | operator |
字符 | char | 重写 | override |
字符16位 | char16_t | 私有 | private |
字符32位 | char32_t | 受保护 | protected |
字符8位 | char8_t | 公开 | public |
类型 | class | 寄存器 | register |
协程等待 | co_await | 重释转换 | reinterpret_cast |
协程返回 | co_return | 约束 | requires |
协程产出 | co_yield | 受限 | restrict |
概念 | concept | 返回 | return |
常量 | const | 短型 | short |
常量转换 | const_cast | 有符号 | signed |
立即求值 | consteval | 大小 | sizeof |
常量表达式 | constexpr | 静态 | static |
常量初始化 | constinit | 静态断言 | static_assert |
跳过 | continue | 静态转换 | static_cast |
声明类型 | decltype | 结构 | struct |
默认 | default | 选择 | switch |
删除 | delete | 模板 | template |
执行 | do | 自身 | this |
双精度 | double | 线程局部 | thread_local |
动态转换 | dynamic_cast | 抛出 | throw |
否则 | else | 真值 | true |
枚举 | enum | 尝试 | try |
显式 | explicit | 类型别名 | typedef |
导出 | export | 类型标识 | typeid |
外部 | extern | 类型名 | typename |
假值 | false | 取类型 | typeof |
最终 | final | 取无修饰类型 | typeof_unqual |
单精度 | float | 共用体 | union |
对于 | for | 无符号 | unsigned |
友元 | friend | 使用 | using |
跳转 | goto | 虚拟 | virtual |
如果 | if | 空型 | void |
导入 | import | 易变 | volatile |
内联 | inline | 宽字符 | wchar_t |
整数 | int | 每当 | while |
其中 重写(override)与 最终(final)是上下文关键字: 它们只在特定位置上被当作关键字,在别的位置仍是普通标识符。
二、运算符替代拼写(11 个)
| 中文 | 英文 | 符号 |
|---|---|---|
并且 | and | && |
与赋值 | and_eq | &= |
位与 | bitand | & |
位或 | bitor | | |
取反 | compl | ~ |
并非 | not | ! |
不等于 | not_eq | != |
或者 | or | || |
或赋值 | or_eq | |= |
异或 | xor | ^ |
异或赋值 | xor_eq | ^= |
这些和 C++ 原本的 and / or / not 是同一类东西(运算符的替代拼写)。
三、预处理指令(22 条)
| 中文 | 英文 |
|---|---|
#断言 | #assert |
#定义 | #define |
#否则若 | #elif |
#否则若已定义 | #elifdef |
#否则若未定义 | #elifndef |
#否则 | #else |
#嵌入 | #embed |
#结束如果 | #endif |
#错误 | #error |
#标识 | #ident |
#如果 | #if |
#如果已定义 | #ifdef |
#如果未定义 | #ifndef |
#导入 | #import |
#包含 | #include |
#包含下一个 | #include_next |
#行号 | #line |
#模块 | #module |
#编译指示 | #pragma |
#取消断言 | #unassert |
#取消定义 | #undef |
#警告 | #warning |
中英可以混用,同一个文件、同一条 #if 链里混着写都行,不影响编译结果。
四、入口点
| 中文 | 英文 | 说明 |
|---|---|---|
主函数 | main | 同一个标识符:写哪个都行,但不能同时写两个(那是重定义) |
那张表还有第五节「这些仍然必须写英文」与第六节「一个完整例子」,见
packaging\中文对照表.md。本教程的附录 C 也覆盖了最常踩的坑。
附录 C 常见坑
| 坑 | 现象 | 怎么办 |
|---|---|---|
| 单字关键字不存在 | 写 类 编不过 | 汉语关键字不少于两个字:class 是 类型,不是 类(见对照表) |
最大 ≠ 最大值 | 写 标准::最大 报「没有名为 最大 的成员」 | 按用法表查真名:是 最大值 / 最小值 |
| 有些成员只有英文名 | 写 名.查找 报错 | 用英文 名.find(...);英文永远可用 |
| 汉语头 vs 英文头 | 只 #include <string> 却用 标准::字符串 | 想要汉语名字就 #包含 <字符串> |
标准::字符串 数的是字节 | "你好" 的 尺寸() 是 6 不是 2 | 按字节算;要按字符算得用宽字符或 UTF-8 库 |
引用的 & 忘了写 | 函数里改了,外面没变 | 参数写 整数& 才是引用 |
情形 忘了 跳出 | 会把后面的分支也执行了 | 每个 情形 末尾写 跳出 |
执行…每当 末尾忘分号 | 编译错 | 执行 { … } 每当 (条件); |
那,英文怎么办?
照样用,而且可以混着写。 例如下面这行完全合法:
标准::向量<std::string> 混合 = {"汉语名字", "英文名字"};汉语不是替换英文,是并行多出来的一种拼写。 这也是为什么「随便拿一个开源 C++ 项目,用我们的编译器编,照样编得过」。