Skip to content

Commit 558439d

Browse files
对 C 语言系统教程的第四章和第五章添加了参考答案 (#114)
* 对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 插件自动修改的格式 * fix: fix some typos --------- Co-authored-by: Charliechen114514 <725610365@qq.com>
1 parent 8fe948b commit 558439d

3 files changed

Lines changed: 115 additions & 0 deletions

File tree

documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,27 @@ g_float = 0.69999998807907104492 //0.7f
353353

354354
修改代码使用 epsilon 比较来得到正确的结果。
355355

356+
### 练习 1 参考答案
357+
358+
`==` 换成「差的绝对值小于一个很小的阈值(epsilon)」来判断:
359+
360+
```c
361+
#include <math.h>
362+
#include <float.h>
363+
364+
// double 用 DBL_EPSILON
365+
int double_equal(double a, double b) {
366+
return fabs(a - b) < DBL_EPSILON;
367+
}
368+
369+
// float 用 FLT_EPSILON
370+
int float_equal(float a, float b) {
371+
return fabsf(a - b) < FLT_EPSILON;
372+
}
373+
```
374+
375+
替换之后,`0.1 + 0.2` 与 `0.3` 的差约 `5.5e-17`,小于 `DBL_EPSILON`(约 `2.2e-16`),`double_equal(0.1 + 0.2, 0.3)` 就会返回真。要留意,绝对 epsilon 比较在数值数量级很大时会失效,工程里更稳妥的是相对误差比较,这里先用入门写法。
376+
356377
### 练习 2:隐式转换陷阱
357378
358379
下面这段代码有一个隐藏的 bug,找出它并解释原因:

documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ printf("%d\n", 7 % 2);
266266
printf("%d\n", -7 % 2);
267267
```
268268
269+
### 练习 1 参考答案
270+
271+
```text
272+
3 // 7 / 2 = 3.5,整数除法向零取整,砍掉小数部分 → 3
273+
-3 // -7 / 2 = -3.5,同样向零取整 → -3(不是 -4)
274+
-3 // 7 / -2 = -3.5,向零取整 → -3
275+
1 // 7 % 2 = 1:7 = 3×2 + 1
276+
-1 // -7 % 2 = -1:余数符号跟被除数走,-7 = (-3)×2 + (-1)
277+
```
278+
279+
关键点:C99 起 `/``%` 都向零取整(truncation toward zero),正负数规则一致,直接丢弃小数部分。所以 `-7 / 2``-3` 而不是 `-4``-7 % 2` 的符号随被除数取负。
280+
269281
### 练习 2:短路求值实战
270282

271283
写一个函数,安全地从数组中找到第一个大于指定值的元素。利用短路求值确保不越界:
@@ -279,6 +291,31 @@ printf("%d\n", -7 % 2);
279291
int find_first_above(const int* arr, size_t len, int threshold);
280292
```
281293
294+
### 练习 2 参考答案
295+
296+
```c
297+
#include <stddef.h>
298+
299+
int find_first_above(const int* arr, size_t len, int threshold) {
300+
// 数组为空或长度为 0,直接返回 -1
301+
if (arr == NULL || len == 0) {
302+
return -1;
303+
}
304+
305+
size_t i = 0;
306+
307+
// 利用短路求值防越界:先判 i < len,成立才访问 arr[i]
308+
while (i < len && arr[i] <= threshold) {
309+
i++;
310+
}
311+
312+
// 退出循环后,i < len 说明找到了,否则就是遍历完没找到
313+
return (i < len) ? (int)i : -1;
314+
}
315+
```
316+
317+
核心是 `while (i < len && arr[i] <= threshold)` 这一行:`&&` 短路求值,`i < len` 为假时根本不会去读 `arr[i]`,越界访问就被挡住了。
318+
282319
## 参考资源
283320

284321
- [cppreference: C 语言运算符优先级](https://en.cppreference.com/w/c/language/operator_precedence)

documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,35 @@ uint32_t bit_toggle(uint32_t value, int n);
312312
uint32_t bit_extract(uint32_t value, int high, int low);
313313
```
314314
315+
### 练习 1 参考答案
316+
317+
```c
318+
/// @brief 将 value 的第 n 位置为 1
319+
uint32_t bit_set(uint32_t value, int n) {
320+
return value | (1U << n);
321+
}
322+
323+
/// @brief 将 value 的第 n 位清零
324+
uint32_t bit_clear(uint32_t value, int n) {
325+
return value & ~(1U << n);
326+
}
327+
328+
/// @brief 翻转 value 的第 n 位
329+
uint32_t bit_toggle(uint32_t value, int n) {
330+
return value ^ (1U << n);
331+
}
332+
333+
/// @brief 提取 value 的 [high:low] 位域(包含两端)
334+
uint32_t bit_extract(uint32_t value, int high, int low) {
335+
uint32_t width = high - low + 1;
336+
value = value >> low;
337+
uint64_t mask = (1ULL << width) - 1;
338+
return value & mask;
339+
}
340+
```
341+
342+
`bit_extract` 里 mask 用 `1ULL` 是为了避开 `width == 32``1U << 32` 的移位溢出,这种细节在位域操作里很常见。
343+
315344
### 练习 2:安全的移位
316345

317346
写一个函数,安全地执行左移操作,处理所有边界情况:
@@ -325,6 +354,22 @@ uint32_t bit_extract(uint32_t value, int high, int low);
325354
uint32_t safe_shift_left(uint32_t val, int n, int bits);
326355
```
327356
357+
### 练习 2 参考答案
358+
359+
```c
360+
#include <stdint.h>
361+
362+
uint32_t safe_shift_left(uint32_t val, int n, int bits) {
363+
// bits 必须是合法位宽,n 必须落在 [0, bits) 内
364+
if (bits <= 0 || bits > 32 || n < 0 || n >= bits) {
365+
return 0;
366+
}
367+
return val << n;
368+
}
369+
```
370+
371+
只要保证 `n < bits``bits <= 32``val << n` 就不会触发移位溢出这类 UB。
372+
328373
### 练习 3:表达式分析
329374

330375
分析以下表达式的求值行为(不实际运行),标出每个是"明确定义"、"未指定行为"还是"未定义行为":
@@ -337,6 +382,18 @@ int r3 = (a > b) ? a-- : b--; // ?
337382
printf("%d %d\n", a++, a++); // ?
338383
```
339384
385+
### 练习 3 参考答案
386+
387+
```c
388+
int a = 5, b = 3;
389+
int r1 = a++ + b; // 明确定义:a 只被修改一次,b 只是读取
390+
int r2 = a++ + ++a; // 未定义行为:a 在两个序列点之间被修改两次
391+
int r3 = (a > b) ? a-- : b--; // 明确定义:?: 第一个操作数求值后有序列点,且只对一个分支求值
392+
printf("%d %d\n", a++, a++); // 未定义行为:函数实参之间没有序列点,a 被修改两次
393+
```
394+
395+
容易踩的是 `r3`:看起来两边都在自减,但三目运算符只会对成立的那个分支求值,而且 `?:` 的第一个操作数之后有序列点,所以是安全的。`printf` 那行虽然写了逗号,但函数实参之间的逗号不是逗号运算符,没有序列点,两个 `a++` 之间无序列点 → UB。
396+
340397
## 参考资源
341398

342399
- [cppreference: C 运算符优先级](https://en.cppreference.com/w/c/language/operator_precedence)

0 commit comments

Comments
 (0)