Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

基础语法与编译模型 / Language & Compilation

1. C++ 语言基础完整学习手册 / Complete C++ Language Basics Guide

2. C++ 语言基础与程序运行 / C++ Language Basics and Program Execution

3. C++ 程序是怎样做出来的:从源码到可执行文件 / From Source to Executable

4. 值、类型与对象:C++ 程序中的第一层现实 / Values, Types, and Objects

5. 表达式、运算符与控制流 / Expressions, Operators, and Control Flow

6. 函数、作用域与重载 / Functions, Scope, and Overloading

7. 数组、指针与字符串:从地址到借用范围 / Arrays, Pointers, and Strings

8. 引用、const 与类型推导:从复制到借用 / References, const, and Type Deduction

9. 让数据说清楚自己:枚举、结构体、联合体与类型别名 / Enums, Structs, Unions, and Aliases

10. 一个头文件怎样影响整个程序:预处理、头文件与命名空间 / Preprocessor, Headers, and Namespaces

11. 基础 I/O、错误与调试:让程序会观察自己的失败 / Basic I/O, Errors, and Debugging

本页目录

C++ 语言基础完整学习手册 / Complete C++ Language Basics Guide ​

1. 这本手册怎么使用 ​

这不是一张“看完就算学会”的打卡表。

每个阶段都要完成四件事:

text
理解一个问题
  |
  v
写一个最小程序
  |
  v
制造或观察一个现象
  |
  v
用自己的话解释现象
1
2
3
4
5
6
7
8
9
10

如果只看文章不运行代码,你可能记住了词,却没有建立模型。

如果只运行代码不解释现象,你可能得到了输出,却不知道条件改变后会发生什么。

2. 阶段 0:程序如何被翻译 ​

阅读:

text
01-compile-link-odr.md
1

目标:

  • 区分源码、目标文件和可执行文件。
  • 区分编译和链接。
  • 理解声明和定义的分工。
  • 看懂最基本的 undefined reference。

最小实验:

cpp
// math.cpp
int add(int a, int b) {
    return a + b;
}
1
2
3
4
cpp
// main.cpp
#include <iostream>
int add(int, int);

int main() {
    std::cout << add(20, 22) << '\n';
}
1
2
3
4
5
6
7
bash
c++ -std=c++17 -Wall -Wextra -c math.cpp -o math.o
c++ -std=c++17 -Wall -Wextra -c main.cpp -o main.o
c++ main.o -o app
1
2
3

第一次看到这种命令时,不要求你立刻背下来。

先把它当作一个有结构的句子:

text
c++
  +-- 使用 C++ 编译器

-std=c++17
  +-- 选择 C++17 规则

-Wall -Wextra
  +-- 打开常见警告

-c
  +-- 只生成目标文件,不生成最终程序

math.cpp / main.cpp
  +-- 要处理的源码文件

-o math.o
  +-- 指定输出文件名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

第三条命令应该暴露链接问题。

命令中的 main.o 和 math.o 不是两个“可以直接运行的程序”,而是两个目标文件。

把它们组合成最终程序时,才会进入链接阶段。

然后执行:

bash
c++ main.o math.o -o app
./app
1
2

这里:

text
-o app
  +-- 把最终输出命名为 app

./app
  +-- 运行当前目录中的 app
1
2
3
4
5

复习:

text
为什么两个 .cpp 都能编译?
为什么只有 main.o 不能生成程序?
add 的声明和定义分别解决什么问题?
1
2
3

3. 阶段 1:值、类型和对象 ​

阅读:

text
02-language-fundamentals.md
1

目标:

  • 区分名字、对象、类型和存储。
  • 区分初始化和赋值。
  • 理解 int、float、double 的表示和限制。
  • 理解 const、转换、范围和未初始化。

从这段程序开始:

cpp
#include <iostream>

int main() {
    int count{3};
    const int limit{10};
    float ratio{0.1f};

    std::cout << count << ' '
              << limit << ' '
              << ratio << '\n';
}
1
2
3
4
5
6
7
8
9
10
11

观察:

text
count 是什么?
int 决定了什么?
ratio 为什么不一定精确表示 0.1?
limit 为什么不能被赋值?
1
2
3
4

实验:

  • 比较 int、long、float 和 double 的 sizeof。
  • 比较 int 和 unsigned 的混合比较。
  • 用高精度输出观察 0.1f。
  • 比较 int x = 3.9 和 int x{3.9}。
  • 打开 -Wall -Wextra 检查未初始化。

4. 阶段 2:表达式和控制流 ​

阅读:

text
03-expressions-operators-control-flow.md
1

目标:

  • 解释表达式的类型和值。
  • 识别副作用。
  • 理解短路求值。
  • 解释 if、for、while 和 return 如何改变控制流。

实验:

cpp
int calls = 0;

bool ready() {
    ++calls;
    return true;
}

if (false && ready()) {
}
1
2
3
4
5
6
7
8
9

运行后观察 calls,再把 false 改成 true。

复习:

text
右侧为什么有时不会执行?
表达式除了返回值,还可能改变什么?
隐式类型转换在哪里发生?
1
2
3

5. 阶段 3:函数、作用域和调用栈 ​

阅读:

text
04-functions-scope-overloading.md
1

目标:

  • 区分函数声明、定义和调用。
  • 理解按值、指针和引用传参。
  • 区分作用域与生命周期。
  • 画出简单函数调用的栈帧。

观察这个危险例子:

cpp
int* bad_pointer() {
    int local{42};
    return &local;
}
1
2
3
4

不要把“某次运行还能打印 42”当作它正确。

回答:

text
local 什么时候存在?
返回的地址保存了什么?
函数返回后哪个条件失效了?
1
2
3

6. 阶段 4:数组、指针、字符串和引用 ​

阅读:

text
05-arrays-pointers-strings.md
06-references-const-type-deduction.md
1
2

目标:

  • 画出连续数组的地址布局。
  • 解释数组退化和指针加法。
  • 区分指针对象与被指向对象。
  • 识别越界、悬空和失效。
  • 理解引用、const 和 auto 的推导结果。
cpp
int values[3]{10, 20, 30};

for (int* current = values; current != values + 3; ++current) {
    std::cout << *current << '\n';
}
1
2
3
4
5

复习:

text
values 为什么能参与指针运算?
current + 1 为什么按元素移动?
values + 3 为什么是尾后位置?
1
2
3

7. 阶段 5:结构化数据和多文件程序 ​

阅读:

text
07-enums-structs-unions-aliases.md
08-preprocessor-headers-namespaces.md
1
2

目标:

  • 理解结构体字段和 padding。
  • 区分枚举的语义与底层表示。
  • 理解 #include 是怎样影响翻译单元的。
  • 能把声明、定义和命名空间组织起来。

实验:

cpp
#include <cstddef>
#include <iostream>

struct Record {
    char tag;
    int value;
};

int main() {
    std::cout << sizeof(Record) << '\n';
    std::cout << offsetof(Record, value) << '\n';
}
1
2
3
4
5
6
7
8
9
10
11
12

解释:

text
为什么结构体大小可能大于字段大小之和?
padding 对齐解决了什么问题?
1
2

8. 阶段 6:输入、错误和调试 ​

阅读:

text
09-basic-io-errors-debugging.md
1

目标:

  • 理解输入流的状态机。
  • 区分 EOF、格式错误和设备错误。
  • 区分编译、链接和运行时诊断。
  • 学会使用最小复现、调试器和 Sanitizer。
cpp
#include <iostream>

int main() {
    int value{};
    if (!(std::cin >> value)) {
        std::cerr << "input is not an integer\n";
        return 1;
    }
    std::cout << value << '\n';
}
1
2
3
4
5
6
7
8
9
10

分别输入:

text
42
hello
42abc
1
2
3

解释每种输入对流状态的影响。

9. 每个阶段的实验记录格式 ​

text
问题:
  我想知道什么?

假设:
  我认为程序会怎样?

最小代码:
  哪些内容可以删掉?

命令:
  使用什么编译器和参数?

观察:
  实际输出是什么?

解释:
  哪条语言规则或实现行为解释了它?

边界:
  换平台、输入或优化级别后还成立吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

10. 阶段验收方式 ​

每个阶段都要完成四种输出:

text
一句话解释
  |
  v
一张 ASCII 图
  |
  v
一个最小程序
  |
  v
一个故意失败的实验
1
2
3
4
5
6
7
8
9
10

例如学指针,不能只说“指针保存地址”。

还应该能够画出:

text
pointer object
  |
  +-- stores a location
  |
  v
target object
  |
  +-- has its own type, value, and lifetime
1
2
3
4
5
6
7
8

11. 模块完成验收 ​

完成这个模块后,你应该能够:

  • 解释源码到可执行程序的主要阶段。
  • 区分编译错误、链接错误和运行时错误。
  • 解释类型的用途、表示、范围和限制。
  • 区分名字、对象、存储和生命周期。
  • 解释初始化与赋值的区别。
  • 画出数组、地址和指针关系。
  • 解释函数调用和局部变量生命周期。
  • 说明引用、指针和借用视图的差异。
  • 解释结构体 padding。
  • 组织一个简单的多文件程序。
  • 处理基本输入错误。
  • 用实验验证而不是猜测。

12. 进入下一个模块之前 ​

下一个模块是对象模型和内存管理。

不要把智能指针当作本模块的替代品。

智能指针表达所有权。

要理解所有权,必须先能解释:

text
对象什么时候开始存在?
谁能访问它?
谁负责让资源失效?
地址什么时候不再有效?
1
2
3
4

如果这些问题还模糊,就回到阶段 1、阶段 3 和阶段 4 重新做实验。

13. 推荐的每周节奏 ​

如果每天只有一小时,可以采用:

text
10 分钟:回顾上一阶段的图
20 分钟:阅读新概念
20 分钟:运行最小实验
10 分钟:写下自己的解释
1
2
3
4

不要把一小时全部用来阅读。

底层知识需要观察错误和修改条件,才能变成自己的模型。

14. 编译器输出也是学习材料 ​

学习基础阶段不要只看程序的最终输出。

还要看:

text
编译器警告
链接器错误
调试器中的局部变量
调用栈
Sanitizer 报告
目标文件符号
1
2
3
4
5
6

同一段代码可以从多个观察窗口理解:

text
源码窗口       -> 你写了什么
编译器窗口     -> 类型和语法是否接受
链接器窗口     -> 跨文件实体是否能组合
调试器窗口     -> 运行时对象和控制流怎样变化
1
2
3
4

15. 遇到不会的问题先分层 ​

程序崩溃时,不要立刻归因于“指针问题”。

先问:

text
程序是否成功编译?
程序是否成功链接?
程序是否成功启动?
崩溃发生在哪个函数?
对象生命周期是否结束?
输入和索引是否经过验证?
1
2
3
4
5
6

这种分层比背更多 API 更重要。

16. 贯穿练习的版本记录 ​

建议保留:

text
text-counter/
  +-- stage-00-build/
  +-- stage-01-values/
  +-- stage-02-expressions/
  +-- stage-03-functions/
  +-- stage-04-pointers/
  +-- stage-05-headers/
  +-- stage-06-errors/
1
2
3
4
5
6
7
8

每个阶段保存:

text
源代码
编译命令
一次成功输出
一次失败输出
自己的解释
1
2
3
4
5

这样学习内存管理时,可以回到早期代码观察生命周期问题是怎样出现的。

17. 不要用“运行成功”代替正确性 ​

下面这些现象都不能单独证明程序正确:

text
程序打印了预期数字
一次运行没有崩溃
优化打开后仍然工作
换一组输入仍然工作
1
2
3
4

更可靠的验收要结合:

text
语言规则
  +-- 类型和生命周期是否成立

工具诊断
  +-- 警告、Sanitizer、调试器

边界实验
  +-- 空输入、极值、错误输入、重复调用
1
2
3
4
5
6
7
8

18. 进入内存管理之前 ​

先画出:

text
名字
  |
  v
对象
  |
  v
存储
  |
  v
生命周期
  |
  v
访问路径
1
2
3
4
5
6
7
8
9
10
11
12
13

然后再问:

text
如果存储来自 new,谁负责释放?
如果多个对象引用同一资源,谁拥有它?
如果函数保存了借用地址,地址何时失效?
1
2
3

这四个问题就是 RAII 和所有权设计的入口。

最后更新于:

Pager
上一篇← C++ 编程 / C++ Programming
下一篇2. C++ 语言基础与程序运行 / C++ Language Basics and Program Execution

持续记录,持续成长

Copyright © Tidenflow