Skip to content

教学教程 —— 从零学会写汉语 C++ ​

⬇ Chinese++ 教学教程 —— 下载 Word 版(.docx) 适合打印、发邮件、或发给不习惯看网页的人。内容与本页相同。

这本教程教你三件事:汉语关键字怎么用(每个都给出对应的英文)、 原本那套符号怎么用({} () ; :: -> … 一个没换)、 基础标准库怎么用(#包含 <向量>、标准::向量<整数> …)。

它只讲汉语化带来的差异与够用的基础,不替代 C++ 教材。

本文件是生成物,不要手改。 由 tools\make_tutorial_cpp.ps1 把 教程\例子\*.cpp 的真源码与「期望输出」切进 教程\教程模板.md 生成。 tools\check_tutorial_cpp.ps1 会真编译真运行每个例子并核对输出, 同时核对本文件与例子是否逐字一致 —— 所以教程里的代码必然能跑。


怎么用这本教程 ​

  1. 先读「第 0 课」把编译命令跑通。
  2. 每课都有一份能直接编译运行的例子,放在 教程\例子\。
  3. 例子开头的注释里写了它自己的期望输出 —— 你跑出来的应当一模一样。
  4. 汉语写法和英文写法可以混着写,同一行里混都行。学会汉语不等于扔掉英文。

第 0 课 准备:编、跑 ​

编译器:build\Release\bin\chinese++.exe(安装包用户直接写 chinese++)。

powershell
chinese++ -std=c++20 -O0 -o 你好.exe 你好.cpp
.\你好.exe

一个最小程序长这样(第 1 课细讲):

cpp
#包含 <输入输出流>

整数 主函数() {
    标准::标准输出流 << "你好,世界" << 标准::行尾流冲刷;
    返回 0;
}

第 1 课 程序骨架 ​

本课关键字 ​

汉语英文干什么
整数int整数类型;整数 主函数() 里的 主函数 是 main 的汉语名
主函数main程序入口。和 main 是同一个标识符,写哪个都行,但不能两个都写
空型void「什么也不返回」
返回return从函数返回
#包含#include把另一个文件的内容引进来

本课符号 ​

符号名称用法
{ }花括号包住一段代码(函数体、类体、分支体)。成对出现
( )圆括号包住函数参数表;调用函数时也是它
;分号每条语句末尾必须有(函数体、类体、# 行末尾不写)
::双冒号名域/类型限定:标准::标准输出流 = std 里的 cout
" "双引号字符串字面量
' '单引号单个字符字面量(第 2 课用)
#井号预处理指令的开头,必须在一行最前面(可以缩进)
//行注释从这里到行尾都不算代码

例子的真源码 ​

cpp
// 第 1 课:程序骨架 —— 从「你好,世界」看最小的汉语 C++ 程序
//
// 这一课的关键字(括号里是对应的英文):
//   整数(主函数写法之一)、返回(return)、空型(void)、#包含(#include)
// 符号:花括号 {}、圆括号 ()、分号 ;、双冒号 ::、双引号 ""、井号 #
//
// 期望输出:
// 你好,世界
// 汉语编程

#包含 <输入输出流>                 // #包含 = #include

空型 打招呼() {                    // 空型 = void:不返回任何东西的函数
    标准::标准输出流 << "汉语编程" << 标准::行尾流冲刷;
}                                  // 花括号包住函数体;行尾流冲刷 = std::endl

整数 主函数() {                    // 主函数 = main(写 整数 main() 也完全一样)
    标准::标准输出流 << "你好,世界" << 标准::行尾流冲刷;
    打招呼();                      // 圆括号:调用函数;分号:语句结束
    返回 0;                        // 返回 = return;0 表示正常结束
}

跑出来的结果(与例子里的「期望输出」一致):

text
你好,世界
汉语编程

讲解 ​

  • 标准::标准输出流 就是 std::cout,标准::行尾流冲刷 就是 std::endl。 << 是「往流里塞东西」,可以连着塞。
  • 空型 打招呼() 定义了一个不返回值的函数;调用它写 打招呼();。
  • 三个地方最容易忘:语句末尾的 ;、函数体的 { }、#包含 的 #。

练习 ​

  1. 把 主函数 改成 main,重新编译 —— 结果应当完全一样。
  2. 加一个 空型 告别() 打印一行「再见」,并在 主函数 里调用它。

第 2 课 变量与类型 ​

本课关键字 ​

汉语英文干什么
整数int整数
双精度double双精度浮点
单精度float单精度浮点
字符char单个字符
布尔bool真/假
真值 / 假值true / false布尔字面量
自动auto类型由右边的值推出来
常量const之后不许改
长型 / 短型long / short更宽/更窄的整数
无符号 / 有符号unsigned / signed有没有符号位
空指针nullptr空指针值(不是类型)
大小sizeof某个类型/值占多少字节

本课符号 ​

符号用法
=赋值:整数 年龄 = 18;
,一行里声明多个:整数 甲 = 1, 乙 = 2;
( )大小(整数) —— sizeof 后面要跟括号包住类型
后缀 f / L / u60.5f(单精度)、1000000000L(长整型)、10u(无符号)

例子的真源码 ​

cpp
// 第 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;
}

跑出来的结果:

text
年龄=18
身高=1.75
体重=60.5
首字母=A
已成年=1
推断=42
圆周率=3.14159
整数占4字节
空指针是1
大数=1000000000 计数=10

讲解 ​

  • 自动 推断 = 42; 里 推断 就是 int,因为右边是整数。
  • 常量 双精度 圆周率 = 3.14159; —— 顺序是「常量 → 类型 → 名字」。
  • 空指针 是个值:自动 空 = 空指针;。别写 空指针 空;(那不是类型)。
  • 布尔值往流里塞会打印成 1 / 0。

练习 ​

  1. 声明一个 常量 整数 天数 = 7;,再试着给它赋值 —— 看编译器怎么拦你。
  2. 用 大小(双精度) 打印 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
自增自减++ --前置后置都照写
逻辑&& || !也可以写 并且 或者 并非
位运算& | ^ ~ << >>也可以写 位与 位或 异或 取反
三元? :条件 ? 真时取值 : 假时取值

例子的真源码 ​

cpp
// 第 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;
}

跑出来的结果:

text
加=10 减=4 乘=21 除=2 取余=1
比较: 1 0 1 1
并且=0 或者=1 并非=0
三元取大=7
自增后=9 自减后=8
位与=2 位或=7 异或=5 取反=-7
不等于=1

讲解 ​

  • 并且 / 或者 / 并非 只替换逻辑运算符那三个,不要拿它们当 && 的别名去写位运算 —— 位运算有自己的三个词(位与 / 位或 / 异或)。
  • != 有汉语拼写 不等于;但 == 没有,照写 ==。
  • ?: 里的 ? 和 : 是符号,照写。

练习 ​

  1. 用 位与、位或、异或 各算一次,再用 &、|、^ 算一次,确认结果一样。
  2. 用三元运算符打印两个数里的较小者。

第 4 课 分支与循环 ​

本课关键字 ​

汉语英文干什么
如果 / 否则if / else分支
选择switch多分支
情形case选择里的一个分支
默认default选择里的兜底分支
对于for计次循环 / 范围遍历
每当while先判断再执行
执行do先执行一次再判断(搭配 每当 收尾)
跳出break结束整个循环
跳过continue跳过本轮剩下的

本课符号 ​

写法说明
如果 (条件) { … }条件必须用 ( ) 包起来
如果 … 否则 { … }否则 前不写分号
选择 (值) { 情形 1: … 跳出; 默认: … }情形 后面是冒号,每个 情形 末尾要 跳出
对于 (初值; 条件; 步进) { … }三段用分号隔开
执行 { … } 每当 (条件);do-while:末尾那个 每当 后面必须有 ;
对于 (自动 项 : 容器) { … }范围遍历,: 读作「在…里」

例子的真源码 ​

cpp
// 第 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;
}

跑出来的结果:

text
成绩及格
等级=C
累加=10
每当两遍后=2
执行后=2
跳过偶数后=1 3 5
遇到5就跳出=1 2 3 4
范围遍历=3 1 2

讲解 ​

  • 选择 里的 情形 9: 情形 8: … 是整数常量;忘了写 跳出 会一路穿下去。
  • 执行 { … } 每当 (条件); —— 注意末尾那个分号,这是 do-while 的固定格式。
  • 范围遍历 对于 (自动 项 : 表) 里的 项 是每个元素的一份拷贝; 想改原元素得写 自动& 项(第 5 课讲 &)。

练习 ​

  1. 用 选择 把「星期几」翻译成中文(1→星期一 … 7→星期日)。
  2. 用 每当 算 1 到 100 的和,再用 对于 算一遍,对照结果。

第 5 课 函数 ​

本课关键字 ​

汉语英文干什么
空型void不返回值
返回return返回值 / 提前结束
自动auto让编译器推返回类型
常量const只读参数

本课符号(这一课的符号最关键) ​

符号位置含义
( )函数名后参数表;空着表示没有参数
,参数之间分隔参数
&类型后:整数& 甲引用:函数里改的就是外面那个变量
&变量前:&值取地址:把变量的地址交出去
*类型后:整数* 指针指针类型
*指针前:*指针解引用:取出它指向的那个值
->指针后用指针访问成员
::名域后限定到某个名域/类型

一句话记住:& 和 * 在类型那边是「是什么」,在变量那边是「取什么」。

例子的真源码 ​

cpp
// 第 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;
}

跑出来的结果:

text
两数之和=7
两数之和(小数)=3.5
交换后 甲=2 乙=1
通过指针改后=99
递归阶乘(5)=120
默认参数=10

讲解 ​

  • 同名函数只要参数不同就能共存(重载),调用时编译器自己挑。
  • 空型 交换(整数& 甲, 整数& 乙) 改的是外面那两个变量; 如果去掉 &,改的只是副本,外面看不到变化 —— 这是 C++ 最常见的坑之一。
  • 加上(整数 甲, 整数 乙 = 3) 的 = 3 是默认参数,调用时可以少传一个。

练习 ​

  1. 写一个 整数 最大值(整数 甲, 整数 乙),返回较大的那个。
  2. 把 交换 的 & 去掉,重新编译运行 —— 看输出怎么变(这就是坑的样子)。

第 6 课 自己定义类型 ​

本课关键字 ​

汉语英文干什么
类型class定义类型(注意:不是「类」,汉语关键字一律不少于两个字)
结构struct默认成员公开的类型
枚举enum枚举
公开 / 私有 / 受保护public / private / protected访问说明符
自身this指向当前对象
虚拟virtual允许派生类改写
重写override声明「我在改写基类的虚函数」
最终final到此为止,不许再被继承/改写
静态static属于类型本身,不属于某个对象
新建 / 删除new / delete动态分配 / 释放
使用using引进名字 / 起别名
类型别名typedef起类型别名(老写法)

本课符号 ​

写法说明
类型 名字 { … };类体末尾必须有分号
公开:访问说明符后面是冒号,不是分号
类型 派生 : 公开 基类 { … };继承:冒号 + 访问说明符 + 基类名
.用对象访问成员:甲.面积()
->用指针访问成员:指针->说()
::类名后限定:人::数量、整数 人::数量 = 0;

例子的真源码 ​

cpp
// 第 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;
}

跑出来的结果:

text
面积=12
打招呼:小明
静态计数=2
形状说:我是形状
圆说:我是圆
颜色序号=1
别名变量=5

讲解 ​

  • 构造函数与类型同名、不写返回类型:长方形(整数 宽值, 整数 高值) { … }。
  • 自身->宽 = 宽值; —— 自身 就是 this,这里用 ->。 类里面访问自己的成员也可以直接写名字(宽 * 高)。
  • 虚拟 + 重写 才有多态:基类指针指向派生类对象时,调用的是派生类那一版。 例子里的 圆说:我是圆 就是这么来的;去掉 虚拟 就会变成「我是形状」。
  • 枚举 颜色 { 红, 绿, 蓝 }; 里 红=0、绿=1、蓝=2。

练习 ​

  1. 给 长方形 加一个 整数 周长() { 返回 2 * (宽 + 高); } 并在主函数里打印。
  2. 把例子里的 虚拟 去掉,重新编译运行 —— 看输出变成什么。

第 7 课 模板与名域 ​

本课关键字 ​

汉语英文干什么
模板template声明模板
类型名typename模板里表示「某个类型」
名域namespace名域
使用using引进名字
常量表达式constexpr编译期就能算出来的常量/函数

本课符号 ​

写法说明
模板 <类型名 元素类型>模板参数表用 < > 包起来
标准::向量<整数>模板实参也用 < >,注意和比较运算符 < 区分
我的名域::值:: 限定进名域
使用 我的名域::取;把 取 引进当前作用域,之后直接写 取()

例子的真源码 ​

cpp
// 第 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;
}

跑出来的结果:

text
两数较大=9
双精度较大=2.5
常量表达式平方=25
我的名域里的值=42
简写后=42
全局计数器=1

讲解 ​

  • 模板让一份代码同时服务于 整数 和 双精度:例子里的 取较大 被用了两次, 一次给整数、一次给浮点。
  • :: 是「从里面找」:标准::向量 = std 里面的 vector。 模板实参的 <> 和比较的 <、> 长得像,编译器靠上下文区分。
  • 常量表达式 整数 平方(整数 数) 能在编译期求值 —— 用它写的常量不会带来运行时开销。

练习 ​

  1. 写一个模板 模板 <类型名 元素类型> 空型 打印(元素类型 值),打印任意类型的值。
  2. 在自己的 名域 里放两个函数,用 :: 调用它们。

第 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)。

例子的真源码 ​

cpp
// 第 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;
}

跑出来的结果:

text
向量尺寸=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 的汉语名是 子串。 拿不准时写英文 —— 英文永远可用。
  • 注意是 最大值 / 最小值,不是 最大 / 最小。

练习 ​

  1. 用 标准::排序 把 {5, 2, 8, 1} 排好序并打印。
  2. 用 标准::映射<标准::字符串, 整数> 存三个人的年龄,再打印其中一个人的。

第 9 课 预处理指令 ​

本课指令(英文对照) ​

汉语英文干什么
#包含#include引入头文件
#定义#define定义宏
#取消定义#undef取消宏
#如果已定义#ifdef定义了就走这一段
#如果未定义#ifndef没定义就走这一段
#如果 / #否则若#if / #elif条件
#否则#else否则
#结束如果#endif结束条件块
#编译指示#pragma编译器指示

本课符号 ​

符号说明
#指令行开头,按行生效:不吃缩进规则、末尾不写分号
< >#包含 <向量> 尖括号:系统头
" "#包含 "我的头.h" 双引号:自己写的头

例子的真源码 ​

cpp
// 第 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;
}

跑出来的结果:

text
版本号已定义=2
未定义分支走了这里
中英混写也认
英文类型也能用,尺寸=3
结束

讲解 ​

  • 中英可以混写:同一份文件里 #包含 <字符串> 和 #include <vector> 并存没问题。
  • ⚠ 本课踩出来的坑:标准::字符串 是汉语头带进来的名字。 只写英文的 #include <string>,里面是微软原版的 std::string, 没有 标准::字符串。想要汉语名字就包含汉语头。
  • #如果已定义 后面跟宏名,不跟括号。#定义 版本号 2 之后, 版本号 在代码里就当 2 用。

练习 ​

  1. #定义 平方(x) ((x)*(x)),用它算 平方(5)。
  2. 用 #如果未定义 写一个「防重复包含」的头文件。

附录 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 库
引用的 & 忘了写函数里改了,外面没变参数写 整数& 才是引用
情形 忘了 跳出会把后面的分支也执行了每个 情形 末尾写 跳出
执行…每当 末尾忘分号编译错执行 { … } 每当 (条件);

那,英文怎么办? ​

照样用,而且可以混着写。 例如下面这行完全合法:

cpp
标准::向量<std::string> 混合 = {"汉语名字", "英文名字"};

汉语不是替换英文,是并行多出来的一种拼写。 这也是为什么「随便拿一个开源 C++ 项目,用我们的编译器编,照样编得过」。

以 MIT / Apache-2.0 等宽松许可发布 · 联系作者 QQ:1396257961