Skip to content

Commit 01a0686

Browse files
对 C 语言系统教程第十章和第十一章添加了参考答案 (#125)
* 对C_tutorials 第六章的状态机示例代码添加了注释 * 对C_tutorials 第六章的状态机示例代码添加了注释(修正版) * docs(vol1): refine state machine comments 追加到 PR #104 的 review 修正,纯注释、零逻辑改动: - payload_len / index: 把「几个负载」改成「几个字节」, 代码实际含义如此,负载更像是完整的Payload,跟代码还是具备实际差距 - kStateHeader: 修正为「帧头已收完,接下来等长度字节」, - 顺手修了结构体注释的对齐(payload[64]) * 对C_tutorials 第二章的练习题添加了参考答案 Added example solutions for exercises on data types and overflow in C. * 对C_tutorials 第三章的练习题添加了参考答案 * fix: lint issue * 对 C 语言系统教程第四章第五章提供了参考答案 * C 语言系统教程第三章练习题答案 bug 修复 搞忘把“### 练习 1 参考答案”打上去了awa 其他.md格式修正是 VS Code 插件自动修改的格式 * Update 02B-float-char-const-cast.md * 对 C 语言系统教程的第六章和第七章添加了参考答案 第六章的第三题难度有点高,这个答案我就不写了,交给你来写。 第二题也交给你了。 awa * fix: submitted answers get possible sigfault and with implicit declarations * Update 06-scope-and-storage.md * Update 07A-pointer-essentials.md * Update 07A-pointer-essentials.md * docs(vol1): fix -Wall -Wextra warnings in ch06/07A exercise answers 练习答案代码质量修正,纯格式/调用/注释调整,零逻辑改动。 07A-pointer-essentials.md: - 练习1: sizeof/size_t 改用 %zu(原 %d 触发 -Wformat 警告) - 练习1: %p 参数加 (void*) 强转(标准要求 void*,消除 UB) - 练习1: 输出示例换真实地址格式,补 char=48 与栈地址间隔两点说明 - 练习2: size_t 索引改用 %zu;补 #include <stddef.h> 06-scope-and-storage.md: - 练习2: 让两个同名 static helper_a 各被模块公共函数调用(消除 unused-function 警告,顺便把"同名 static 互不干扰"演示得更透彻) - 练习3: config 改用 static 初始化器呼应题目提示,flag 只负责打印 全部 5 段经 -Wall -Wextra -std=c17 编译零警告、运行输出正确。 * 对 C 语言系统教程的第十章和第十一章添加了参考答案。 * review: fix compile errors & polish vol1 c_tutorials 07B/08B answers --------- Co-authored-by: Charliechen114514 <725610365@qq.com>
1 parent 4c1026d commit 01a0686

2 files changed

Lines changed: 237 additions & 15 deletions

File tree

documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md

Lines changed: 152 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
---
22
chapter: 1
33
cpp_standard:
4-
- 11
4+
- 11
55
description: 深入理解数组名退化为指针的机制、const 与指针的四种组合、NULL 指针和野指针的防范,为学习 C++ 引用和智能指针打下基础
66
difficulty: beginner
77
order: 10
88
platform: host
99
prerequisites:
10-
- 指针入门:地址的世界
10+
- 指针入门:地址的世界
1111
reading_time_minutes: 11
1212
tags:
13-
- host
14-
- cpp-modern
15-
- beginner
16-
- 入门
13+
- host
14+
- cpp-modern
15+
- beginner
16+
- 入门
1717
title: 指针与数组、const 和空指针
1818
---
19+
1920
# 指针与数组、const 和空指针
2021

2122
上一篇里我们掌握了指针的基本操作——声明、初始化、取地址、解引用、指针运算。现在我们来啃指针里几个比较绕但非常重要的应用:数组和指针之间到底是什么关系,`const` 和指针组合在一起有多少种意思,以及 NULL 指针和野指针为什么这么危险。
@@ -33,7 +34,7 @@ title: 指针与数组、const 和空指针
3334

3435
我们接下来的所有实验都在这个环境下进行:
3536

36-
- 平台:Linux x86\_64(WSL2 也可以)
37+
- 平台:Linux x86_64(WSL2 也可以)
3738
- 编译器:GCC 13+ 或 Clang 17+
3839
- 编译选项:`-Wall -Wextra -std=c17`
3940

@@ -264,6 +265,53 @@ std::unique_ptr<int> p = std::make_unique<int>(42);
264265
const int* linear_search(const int* data, size_t count, int target);
265266
```
266267
268+
### 练习 1 参考答案
269+
270+
```c
271+
#include <stdio.h>
272+
const int* linear_search(const int* data, size_t count, int target);
273+
274+
int main(void) {
275+
const int arr[] = {10, 21, 32, 43, 54, 65, 76, 87, 98, 9};
276+
int target = 43;
277+
278+
// 使用 sizeof 自动计算数组大小
279+
size_t count = sizeof(arr) / sizeof(arr[0]);
280+
281+
const int* result = linear_search(arr, count, target);
282+
283+
if (result != NULL) {
284+
printf("找到了目标 %d,位于地址 %p,是数组的第 %llu 个元素。\n",
285+
target, (void*)result, (unsigned long long)(result - arr));
286+
} else {
287+
printf("未找到目标 %d。\n", target);
288+
}
289+
290+
return 0;
291+
}
292+
293+
const int* linear_search(const int* data, size_t count, int target) {
294+
// 防御性编程:如果指针为空,或者数量为0,直接返回 NULL
295+
if (data == NULL || count == 0) {
296+
return NULL;
297+
}
298+
for (size_t i = 0; i < count; i++) {
299+
if (*(data + i) == target) {
300+
return data + i;
301+
}
302+
}
303+
return NULL;
304+
}
305+
```
306+
307+
运行后你会看到类似这样的输出(地址那串数字每次运行都不一样,这是系统的地址随机化 ASLR 在起作用,属正常现象):
308+
309+
```text
310+
找到了目标 43,位于地址 0x7ffd02b50bac,是数组的第 3 个元素。
311+
```
312+
313+
地址本身不用记,关键是后面那个「第 3 个元素」——`result - arr` 算出来就是 `3`,这个结论是稳的。顺带一提:要是终端里中文显示成乱码,那是终端编码不是 UTF-8 的问题,跟代码无关,换 WSL2 或现代终端就好。
314+
267315
### 练习 2:指针版数组反转
268316

269317
实现一个原地反转数组的函数,只使用指针算术(两个指针从两端向中间靠拢),不使用数组下标:
@@ -275,6 +323,82 @@ const int* linear_search(const int* data, size_t count, int target);
275323
void reverse_array(int* data, size_t count);
276324
```
277325
326+
### 练习 2 参考答案
327+
328+
```c
329+
#include <stdio.h>
330+
331+
void reverse_array(int* data, size_t count);
332+
333+
int main(void) {
334+
int arr[] = {10, 21, 32, 43, 54, 65, 76, 87, 98, 9};
335+
int arr_size = sizeof(arr) / sizeof(arr[0]);
336+
337+
printf("before reverse_array:\n");
338+
for (int i = 0; i < arr_size; i++) {
339+
printf("%d\n", arr[i]);
340+
}
341+
342+
reverse_array(arr, arr_size);
343+
344+
printf("after reverse_array:\n");
345+
for (int i = 0; i < arr_size; i++) {
346+
printf("%d\n", arr[i]);
347+
}
348+
349+
return 0;
350+
}
351+
352+
void reverse_array(int* data, size_t count) {
353+
if (data == NULL) {
354+
printf("data is null\n");
355+
return;
356+
}
357+
if (count == 0) {
358+
printf("data is empty\n");
359+
return;
360+
}
361+
362+
int* start = data;
363+
int* end = data + count - 1;
364+
int temp = 0;
365+
while (start < end) {
366+
temp = *start;
367+
*start = *end;
368+
*end = temp;
369+
start++;
370+
end--;
371+
}
372+
}
373+
```
374+
375+
终端应当输出:
376+
377+
```text
378+
before reverse_array:
379+
10
380+
21
381+
32
382+
43
383+
54
384+
65
385+
76
386+
87
387+
98
388+
9
389+
after reverse_array:
390+
9
391+
98
392+
87
393+
76
394+
65
395+
54
396+
43
397+
32
398+
21
399+
10
400+
```
401+
278402
### 练习 3:const 练习
279403

280404
判断以下每个声明中,哪些操作是合法的,哪些会编译错误:
@@ -291,6 +415,27 @@ const int* const p3 = &value;
291415
// px = &other; // 修改指针指向
292416
```
293417

418+
### 练习 3 参考答案
419+
420+
```c
421+
int value = 42, other = 100;
422+
423+
const int* p1 = &value;
424+
int* const p2 = &value;
425+
const int* const p3 = &value;
426+
427+
// 下面六行用来判断合法性,逐行对照上面的声明看(都写成注释,
428+
// 因为不合法的那几行真写出来会编译报错):
429+
// *p1 = other; // 不合法:p1 指向 const int,无法通过它改值
430+
// *p2 = other; // 合法:p2 是 const 指针(指向固定),但它指向的是普通 int,值能改
431+
// *p3 = other; // 不合法:p3 既指向 const int,自身也是 const,改值改向都不行
432+
//
433+
// p1 = &other; // 合法:p1 本身不是 const 指针,可以改指向
434+
// p2 = &other; // 不合法:p2 是 const 指针,指向不能改
435+
// p3 = &other; // 不合法:p3 同样是 const 指针,指向不能改
436+
437+
```
438+
294439
## 参考资源
295440

296441
- [cppreference: 指针声明](https://en.cppreference.com/w/c/language/pointer)

documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
---
22
chapter: 1
33
cpp_standard:
4-
- 11
4+
- 11
55
description: 深入理解多级指针的内存模型和实际使用场景,区分指针数组与数组指针,掌握 cdecl 声明读法和多级 const 指针的组合
66
difficulty: beginner
77
order: 11
88
platform: host
99
prerequisites:
10-
- 指针与数组、const 和空指针
10+
- 指针与数组、const 和空指针
1111
reading_time_minutes: 9
1212
tags:
13-
- host
14-
- cpp-modern
15-
- beginner
16-
- 入门
17-
- 基础
13+
- host
14+
- cpp-modern
15+
- beginner
16+
- 入门
17+
- 基础
1818
title: 多级指针与声明读法
1919
---
20+
2021
# 多级指针与声明读法
2122

2223
上一篇我们把指针和数组、`const`、NULL 的关系理清楚了。现在来啃指针里更绕的部分——多级指针(指向指针的指针)、指针数组和数组指针那对"混淆双胞胎",以及看到 `const int* const *` 这种声明时不至于大脑宕机的方法。
@@ -36,7 +37,7 @@ title: 多级指针与声明读法
3637

3738
我们接下来的所有实验都在这个环境下进行:
3839

39-
- 平台:Linux x86\_64(WSL2 也可以)
40+
- 平台:Linux x86_64(WSL2 也可以)
4041
- 编译器:GCC 13+ 或 Clang 17+
4142
- 编译选项:`-Wall -Wextra -std=c17`
4243

@@ -302,6 +303,82 @@ void free_matrix(int** matrix, int rows);
302303
void fill_matrix(int** matrix, int rows, int cols, int value);
303304
```
304305
306+
### 参考答案
307+
308+
```c
309+
#include <stdio.h>
310+
#include <stdlib.h>
311+
312+
int** allocate_matrix(int rows, int cols) {
313+
if (rows < 1 || cols < 1) {
314+
return NULL;
315+
}
316+
317+
int** matrix = malloc(sizeof(int*) * rows);
318+
if (matrix == NULL) {
319+
return NULL; //防止内存分配失败,但这不太可能吧...以防万一,还是写上吧,如果程序在1978年的硬件上跑呢(笑)
320+
}
321+
322+
for (int i = 0; i < rows; i++) {
323+
matrix[i] = malloc(sizeof(int) * cols);
324+
if (matrix[i] == NULL) { //内存不够,取消分配,并释放内存。
325+
for (int j = 0; j < i; j++) {
326+
free(matrix[j]);
327+
}
328+
free(matrix);
329+
return NULL;
330+
}
331+
}
332+
return matrix;
333+
}
334+
335+
void free_matrix(int** matrix, int rows) {
336+
if (matrix == NULL) {
337+
return;
338+
}
339+
340+
for (int i = 0; i < rows; i++) {
341+
free(matrix[i]);
342+
}
343+
free(matrix);
344+
}
345+
346+
void fill_matrix(int** matrix, int rows, int cols, int value) {
347+
if (rows < 1 || cols < 1 || matrix == NULL) {
348+
return;
349+
}
350+
351+
for (int i = 0; i < rows; i++) {
352+
for (int j = 0; j < cols; j++) {
353+
matrix[i][j] = value;
354+
}
355+
}
356+
}
357+
358+
```
359+
360+
```text
361+
兴致大发,来画个图示 awa
362+
363+
matrix 的类型是 int**,要解两次引用才摸到具体的 int:
364+
365+
matrix (int**)
366+
367+
│ matrix[0] matrix[1] matrix[2] ... matrix[n] ← 第 1 次解引用,每个是 int*
368+
│ │ │ │ │
369+
│ ▼ ▼ ▼ ▼
370+
└─▶ ┌─┬─┬─┬─┐ ┌─┬─┬─┬─┐ ┌─┬─┬─┬─┐ ┌─┬─┐
371+
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ... │ │ │ ← 第 2 次解引用,每个是 int
372+
└─┴─┴─┴─┘ └─┴─┴─┴─┘ └─┴─┴─┴─┘ └─┴─┘
373+
374+
matrix[0][1] 就是这一格(第 0 行、第 1 列的那个 int)
375+
376+
- matrix[i] :int*,指向第 i 行的开头(那块 cols 个 int)
377+
- matrix[i][j] :int,第 i 行第 j 列的具体值
378+
379+
这下应该清楚了吧 OwO?
380+
```
381+
305382
提示:分配时先分配一个指针数组(`int**` 指向的那一维),然后对每一行分别 `malloc`。释放时顺序反过来——先释放每一行,再释放指针数组本身。
306383

307384
## 参考资源

0 commit comments

Comments
 (0)