From 8d687f7510e5a1c573b6d07fb857f002789aac58 Mon Sep 17 00:00:00 2001
From: Charliechen114514 <725610365@qq.com>
Date: Tue, 4 Aug 2026 23:20:03 +0800
Subject: [PATCH] =?UTF-8?q?feat(c-tutorials):=20=E7=BB=83=E4=B9=A0?=
=?UTF-8?q?=E5=8F=82=E8=80=83=E7=AD=94=E6=A1=88=E6=8A=98=E5=8F=A0=E5=8C=96?=
=?UTF-8?q?(02A=E2=80=9308B=E6=94=AF=E6=8C=81)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
details:
按照 @owollz4 大佬的请求,添加了已经拥有习题答案的相关文章使用 vite
提供的 details 能力进行了折叠,默认默认收起、点击展开,共 24 处。
答案正文一字未动;
机制用 VitePress 内置 ::: details 容器,零新代码、零新依赖。
build 验证:24 个 参考答案 全部渲染,旧标题 0 残留。
btw:
01(练习无答案)跳过。
---
.../c_tutorials/02A-data-types-basics.md | 8 ++++++--
.../c_tutorials/02B-float-char-const-cast.md | 12 +++++++++---
.../c_tutorials/03A-operators-basics.md | 8 ++++++--
.../c_tutorials/03B-bitwise-and-evaluation.md | 12 +++++++++---
.../vol1-fundamentals/c_tutorials/04-control-flow.md | 4 +++-
.../c_tutorials/05-function-basics.md | 12 +++++++++---
.../c_tutorials/06-scope-and-storage.md | 12 +++++++++---
.../c_tutorials/07A-pointer-essentials.md | 8 ++++++--
.../c_tutorials/07B-pointers-arrays-const.md | 12 +++++++++---
.../c_tutorials/08A-multi-level-pointers.md | 4 +++-
.../c_tutorials/08B-restrict-incomplete-types.md | 4 +++-
11 files changed, 72 insertions(+), 24 deletions(-)
diff --git a/documents/vol1-fundamentals/c_tutorials/02A-data-types-basics.md b/documents/vol1-fundamentals/c_tutorials/02A-data-types-basics.md
index a0e3229ef..ac9691a0f 100644
--- a/documents/vol1-fundamentals/c_tutorials/02A-data-types-basics.md
+++ b/documents/vol1-fundamentals/c_tutorials/02A-data-types-basics.md
@@ -284,7 +284,7 @@ uint8_t z{1000}; // C++ 编译错误!1000 超出 uint8_t 范围
提示:可以用一个宏来减少重复代码。
-### 练习 1 参考答案
+::: details 参考答案
```c
#include
@@ -330,6 +330,8 @@ sizeof(size_t) = 8 bytes
注意 `sizeof(long)` 这里是 4,但 `sizeof(size_t)` 已经是 8 了,说明这份输出来自 LLP64 环境(比如 64 位 Windows):这种模型下指针 8 字节,`long` 却只有 4。换到 64 位的 Linux 或 macOS(LP64),`long` 就是 8 字节。你在自己机器上看到 `sizeof(long) = 8`,程序没写错,是数据模型的差别。
+:::
+
### 练习 2:溢出观察
分别对有符号 `int` 和无符号 `unsigned int` 做溢出实验:
@@ -352,7 +354,7 @@ int main(void)
编译运行,观察两者的行为差异。然后加上 `-fsanitize=undefined` 选项重新编译,看看有什么变化。
-### 练习 2 参考答案
+::: details 参考答案
假设该文件名为overflow.c
@@ -375,6 +377,8 @@ UINT_MAX = 4294967295, UINT_MAX + 1 = 0
实际上 C 标准其实并没有对带有符号的整数的溢出进行定义,也就是说,对INT_MAX进行+1这个操作严格意义上是一个未定义行为。
(只不过溢出很好用,也是大部分编译器都默认支持溢出的。)
+:::
+
## 参考资源
- [cppreference: C 语言整型](https://en.cppreference.com/w/c/language/integer_constant)
diff --git a/documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md b/documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md
index 4cbd5195d..31d1e5bbc 100644
--- a/documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md
+++ b/documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md
@@ -353,7 +353,7 @@ g_float = 0.69999998807907104492 //0.7f
修改代码使用 epsilon 比较来得到正确的结果。
-### 练习 1 参考答案
+::: details 参考答案
把 `==` 换成「差的绝对值小于一个很小的阈值(epsilon)」来判断:
@@ -374,6 +374,8 @@ int float_equal(float a, float b) {
替换之后,`0.1 + 0.2` 与 `0.3` 的差约 `5.5e-17`,小于 `DBL_EPSILON`(约 `2.2e-16`),`double_equal(0.1 + 0.2, 0.3)` 就会返回真。要留意,绝对 epsilon 比较在数值数量级很大时会失效,工程里更稳妥的是相对误差比较,这里先用入门写法。
+:::
+
### 练习 2:隐式转换陷阱
下面这段代码有一个隐藏的 bug,找出它并解释原因:
@@ -391,7 +393,7 @@ if (target < sizeof(values) / sizeof(values[0])) {
提示:`sizeof` 返回的是什么类型?
-### 练习 2 参考答案
+::: details 参考答案
```c
int values[] = {1, 2, 3, 4, 5};
@@ -415,6 +417,8 @@ if (target < (int)(sizeof(values) / sizeof(values[0]))) {
}
```
+:::
+
### 练习 3:const 实战
写一个函数,接收一个字符串,统计其中某个字符出现的次数。函数签名中正确使用 `const`:
@@ -427,7 +431,7 @@ if (target < (int)(sizeof(values) / sizeof(values[0]))) {
size_t count_char(const char* str, char ch);
```
-### 练习 3 参考答案
+::: details 参考答案
```c
size_t count_char(const char* str, char ch) {
@@ -444,6 +448,8 @@ size_t count_char(const char* str, char ch) {
}
```
+:::
+
## 参考资源
- [cppreference: C 语言隐式转换](https://en.cppreference.com/w/c/language/conversion)
diff --git a/documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md b/documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md
index 806204358..b8eb3a244 100644
--- a/documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md
+++ b/documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md
@@ -266,7 +266,7 @@ printf("%d\n", 7 % 2);
printf("%d\n", -7 % 2);
```
-### 练习 1 参考答案
+::: details 参考答案
```text
3 // 7 / 2 = 3.5,整数除法向零取整,砍掉小数部分 → 3
@@ -278,6 +278,8 @@ printf("%d\n", -7 % 2);
关键点:C99 起 `/` 和 `%` 都向零取整(truncation toward zero),正负数规则一致,直接丢弃小数部分。所以 `-7 / 2` 得 `-3` 而不是 `-4`,`-7 % 2` 的符号随被除数取负。
+:::
+
### 练习 2:短路求值实战
写一个函数,安全地从数组中找到第一个大于指定值的元素。利用短路求值确保不越界:
@@ -291,7 +293,7 @@ printf("%d\n", -7 % 2);
int find_first_above(const int* arr, size_t len, int threshold);
```
-### 练习 2 参考答案
+::: details 参考答案
```c
#include
@@ -316,6 +318,8 @@ int find_first_above(const int* arr, size_t len, int threshold) {
核心是 `while (i < len && arr[i] <= threshold)` 这一行:`&&` 短路求值,`i < len` 为假时根本不会去读 `arr[i]`,越界访问就被挡住了。
+:::
+
## 参考资源
- [cppreference: C 语言运算符优先级](https://en.cppreference.com/w/c/language/operator_precedence)
diff --git a/documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md b/documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md
index 12acaf225..6f9bfe702 100644
--- a/documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md
+++ b/documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md
@@ -312,7 +312,7 @@ uint32_t bit_toggle(uint32_t value, int n);
uint32_t bit_extract(uint32_t value, int high, int low);
```
-### 练习 1 参考答案
+::: details 参考答案
```c
/// @brief 将 value 的第 n 位置为 1
@@ -341,6 +341,8 @@ uint32_t bit_extract(uint32_t value, int high, int low) {
`bit_extract` 里 mask 用 `1ULL` 是为了避开 `width == 32` 时 `1U << 32` 的移位溢出,这种细节在位域操作里很常见。
+:::
+
### 练习 2:安全的移位
写一个函数,安全地执行左移操作,处理所有边界情况:
@@ -354,7 +356,7 @@ uint32_t bit_extract(uint32_t value, int high, int low) {
uint32_t safe_shift_left(uint32_t val, int n, int bits);
```
-### 练习 2 参考答案
+::: details 参考答案
```c
#include
@@ -370,6 +372,8 @@ uint32_t safe_shift_left(uint32_t val, int n, int bits) {
只要保证 `n < bits` 且 `bits <= 32`,`val << n` 就不会触发移位溢出这类 UB。
+:::
+
### 练习 3:表达式分析
分析以下表达式的求值行为(不实际运行),标出每个是"明确定义"、"未指定行为"还是"未定义行为":
@@ -382,7 +386,7 @@ int r3 = (a > b) ? a-- : b--; // ?
printf("%d %d\n", a++, a++); // ?
```
-### 练习 3 参考答案
+::: details 参考答案
```c
int a = 5, b = 3;
@@ -394,6 +398,8 @@ printf("%d %d\n", a++, a++); // 未定义行为:函数实参之间没有序
容易踩的是 `r3`:看起来两边都在自减,但三目运算符只会对成立的那个分支求值,而且 `?:` 的第一个操作数之后有序列点,所以是安全的。`printf` 那行虽然写了逗号,但函数实参之间的逗号不是逗号运算符,没有序列点,两个 `a++` 之间无序列点 → UB。
+:::
+
## 参考资源
- [cppreference: C 运算符优先级](https://en.cppreference.com/w/c/language/operator_precedence)
diff --git a/documents/vol1-fundamentals/c_tutorials/04-control-flow.md b/documents/vol1-fundamentals/c_tutorials/04-control-flow.md
index 8e191a146..bae088883 100644
--- a/documents/vol1-fundamentals/c_tutorials/04-control-flow.md
+++ b/documents/vol1-fundamentals/c_tutorials/04-control-flow.md
@@ -469,7 +469,7 @@ C++17 引入了 `if constexpr`,它在编译期评估条件,直接把不满
用 `switch` 实现一个函数,根据月份和是否闰年返回该月的天数。要求利用穿透特性合并同天数的月份。
-### 练习 1 参考答案
+::: details 参考答案
```c
bool is_leap_year(int year) {
@@ -490,6 +490,8 @@ int month_day(int year, int month) {
}
```
+:::
+
### 练习 2:安全的矩阵搜索
在二维矩阵中查找目标值。找到后用两种方式跳出多层循环:一种用标志变量,一种用 `goto`。
diff --git a/documents/vol1-fundamentals/c_tutorials/05-function-basics.md b/documents/vol1-fundamentals/c_tutorials/05-function-basics.md
index f22130846..4bdc4a47f 100644
--- a/documents/vol1-fundamentals/c_tutorials/05-function-basics.md
+++ b/documents/vol1-fundamentals/c_tutorials/05-function-basics.md
@@ -325,7 +325,7 @@ typedef enum { LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR } LogLevel;
void log_message(LogLevel level, const char* format, ...);
```
-### 练习 1 参考答案
+::: details 参考答案
```c
void log_message(LogLevel level, const char* format, ...) {
@@ -359,6 +359,8 @@ void log_message(LogLevel level, const char* format, ...) {
}
```
+:::
+
### 练习 2:递归与迭代——二分查找
分别用递归和迭代实现二分查找,比较两者的性能和可读性:
@@ -368,7 +370,7 @@ int binary_search_recursive(const int* arr, size_t len, int target);
int binary_search_iterative(const int* arr, size_t len, int target);
```
-### 练习 2 参考答案
+::: details 参考答案
```c
int binary_search_recursive(const int* arr, size_t len, int target) {
@@ -405,6 +407,8 @@ int binary_search_iterative(const int* arr, size_t len, int target) {
}
```
+:::
+
### 练习 3:多返回值实战
实现一个函数,同时计算数组的最大值和最小值:
@@ -418,7 +422,7 @@ int binary_search_iterative(const int* arr, size_t len, int target) {
void find_min_max(const int* data, size_t len, int* min_out, int* max_out);
```
-### 练习 3 参考答案
+::: details 参考答案
```c
void find_min_max(const int* data, size_t len, int* min_out, int* max_out) {
@@ -437,6 +441,8 @@ void find_min_max(const int* data, size_t len, int* min_out, int* max_out) {
}
```
+:::
+
## 参考资源
- [cppreference: 函数声明](https://en.cppreference.com/w/c/language/function_declaration)
diff --git a/documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md b/documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md
index 57ec50994..607954697 100644
--- a/documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md
+++ b/documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md
@@ -456,7 +456,7 @@ void counter_reset(void);
请自行实现 `counter.c`。
-### 练习 1 参考答案
+::: details 参考答案
main.c
@@ -508,6 +508,8 @@ int counter_get(void) {
}
```
+:::
+
### 练习 2:多文件符号可见性
创建三个文件 `a.c`、`b.c`、`main.c`。要求:
@@ -524,7 +526,7 @@ int counter_get(void) {
// 各 .c 文件的实现留给你
```
-### 练习 2 参考答案
+::: details 参考答案
main.c
@@ -601,6 +603,8 @@ void set_kSharedValue(int value) {
}
```
+:::
+
### 练习 3:延迟初始化
用 `static` 局部变量实现一个 `get_config` 函数:第一次调用时执行初始化(打印 "Initializing..." 并设置默认值),后续调用直接返回已初始化的值,不再重新初始化。
@@ -617,7 +621,7 @@ const Config* get_config(void);
> 提示:`static` 局部变量只在第一次进入函数时被初始化——正好可以用来实现"只初始化一次"的语义。
-### 练习 3 参考答案
+::: details 参考答案
```c
#include
@@ -651,6 +655,8 @@ const Config* get_config(void) {
}
```
+:::
+
## 参考资源
- [存储类别说明符 - cppreference](https://en.cppreference.com/w/c/language/storage_duration)
diff --git a/documents/vol1-fundamentals/c_tutorials/07A-pointer-essentials.md b/documents/vol1-fundamentals/c_tutorials/07A-pointer-essentials.md
index 2ee6b314c..9eb735ef3 100644
--- a/documents/vol1-fundamentals/c_tutorials/07A-pointer-essentials.md
+++ b/documents/vol1-fundamentals/c_tutorials/07A-pointer-essentials.md
@@ -269,7 +269,7 @@ C++ 在指针的基础上做了两个关键的改进。第一个是**引用**(
写一个程序,声明三个不同类型的变量(`int`、`double`、`char`),打印它们的值、地址和 `sizeof` 结果。观察地址之间的间隔是否符合各类型的大小。
-### 练习 1 参考答案
+::: details 参考答案
```c
#include
@@ -301,6 +301,8 @@ int main(void) {
- `char` 的 `value` 显示成 `48`,因为 `'0'` 的 ASCII 码就是 48。C 语言里 `char` 本质上是一个小整数,用 `%d` 打印看到的就是它的整数值(想直接看到字符 `'0'`,把格式符换成 `%c` 即可)。
- 三个地址的间隔并不等于各自的类型大小。按声明顺序是 `int`(4) → `double`(8) → `char`(1),但实际地址排列成了 `char` → `int` → `double`,相邻差值也不是 4、8、1。原因有两个:编译器会为了内存对齐给局部变量重排位置、插入填充字节;而且栈布局根本不保证按声明顺序排列变量。所以"地址间隔正好等于类型大小"这个直觉,在真实编译器里通常不成立——这正是这道题想让你亲眼看到的。
+:::
+
### 练习 2:指针遍历数组
用指针算术遍历一个 `int` 数组并打印所有元素。要求不使用 `[]` 运算符,只用指针加减和解引用:
@@ -312,7 +314,7 @@ int main(void) {
void print_int_array(const int* data, size_t count);
```
-### 练习 2 参考答案
+::: details 参考答案
```c
#include
@@ -344,6 +346,8 @@ data[3] = 4
data[4] = 5
```
+:::
+
## 参考资源
- [cppreference: 指针声明](https://en.cppreference.com/w/c/language/pointer)
diff --git a/documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md b/documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md
index b679d634d..d6d6c925b 100644
--- a/documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md
+++ b/documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md
@@ -265,7 +265,7 @@ std::unique_ptr p = std::make_unique(42);
const int* linear_search(const int* data, size_t count, int target);
```
-### 练习 1 参考答案
+::: details 参考答案
```c
#include
@@ -312,6 +312,8 @@ const int* linear_search(const int* data, size_t count, int target) {
地址本身不用记,关键是后面那个「第 3 个元素」——`result - arr` 算出来就是 `3`,这个结论是稳的。顺带一提:要是终端里中文显示成乱码,那是终端编码不是 UTF-8 的问题,跟代码无关,换 WSL2 或现代终端就好。
+:::
+
### 练习 2:指针版数组反转
实现一个原地反转数组的函数,只使用指针算术(两个指针从两端向中间靠拢),不使用数组下标:
@@ -323,7 +325,7 @@ const int* linear_search(const int* data, size_t count, int target) {
void reverse_array(int* data, size_t count);
```
-### 练习 2 参考答案
+::: details 参考答案
```c
#include
@@ -399,6 +401,8 @@ after reverse_array:
10
```
+:::
+
### 练习 3:const 练习
判断以下每个声明中,哪些操作是合法的,哪些会编译错误:
@@ -415,7 +419,7 @@ const int* const p3 = &value;
// px = &other; // 修改指针指向
```
-### 练习 3 参考答案
+::: details 参考答案
```c
int value = 42, other = 100;
@@ -436,6 +440,8 @@ const int* const p3 = &value;
```
+:::
+
## 参考资源
- [cppreference: 指针声明](https://en.cppreference.com/w/c/language/pointer)
diff --git a/documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md b/documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md
index 7bbeba8c2..cbd6ab9a9 100644
--- a/documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md
+++ b/documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md
@@ -303,7 +303,7 @@ void free_matrix(int** matrix, int rows);
void fill_matrix(int** matrix, int rows, int cols, int value);
```
-### 参考答案
+::: details 参考答案
```c
#include
@@ -381,6 +381,8 @@ matrix 的类型是 int**,要解两次引用才摸到具体的 int:
提示:分配时先分配一个指针数组(`int**` 指向的那一维),然后对每一行分别 `malloc`。释放时顺序反过来——先释放每一行,再释放指针数组本身。
+:::
+
## 参考资源
- [C 声明语法 - cppreference](https://en.cppreference.com/w/c/language/declarations)
diff --git a/documents/vol1-fundamentals/c_tutorials/08B-restrict-incomplete-types.md b/documents/vol1-fundamentals/c_tutorials/08B-restrict-incomplete-types.md
index 7f1ce8011..efeae0e90 100644
--- a/documents/vol1-fundamentals/c_tutorials/08B-restrict-incomplete-types.md
+++ b/documents/vol1-fundamentals/c_tutorials/08B-restrict-incomplete-types.md
@@ -325,7 +325,7 @@ int stack_pop(Stack* s, int* out); // 成功返回 0,空栈返回 -1
int stack_size(const Stack* s);
```
-### 参考答案
+::: details 参考答案
```c
// stack.h — 只暴露接口,不暴露内部结构
@@ -415,6 +415,8 @@ int stack_size(const Stack* s){
提示:在 `.c` 文件里定义 `struct Stack` 的完整结构(可以用数组+栈顶索引实现),`.h` 文件只放前向声明和函数声明。
+:::
+
## 参考资源
- [restrict 限定符 - cppreference](https://en.cppreference.com/w/c/language/restrict)