Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ uint8_t z{1000}; // C++ 编译错误!1000 超出 uint8_t 范围

提示:可以用一个宏来减少重复代码。

### 练习 1 参考答案
::: details 参考答案

```c
#include <stdio.h>
Expand Down Expand Up @@ -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` 做溢出实验:
Expand All @@ -352,7 +354,7 @@ int main(void)

编译运行,观察两者的行为差异。然后加上 `-fsanitize=undefined` 选项重新编译,看看有什么变化。

### 练习 2 参考答案
::: details 参考答案

假设该文件名为overflow.c

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ g_float = 0.69999998807907104492 //0.7f

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

### 练习 1 参考答案
::: details 参考答案

把 `==` 换成「差的绝对值小于一个很小的阈值(epsilon)」来判断:

Expand All @@ -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,找出它并解释原因:
Expand All @@ -391,7 +393,7 @@ if (target < sizeof(values) / sizeof(values[0])) {
提示:`sizeof` 返回的是什么类型?


### 练习 2 参考答案
::: details 参考答案

```c
int values[] = {1, 2, 3, 4, 5};
Expand All @@ -415,6 +417,8 @@ if (target < (int)(sizeof(values) / sizeof(values[0]))) {
}
```

:::

### 练习 3:const 实战

写一个函数,接收一个字符串,统计其中某个字符出现的次数。函数签名中正确使用 `const`:
Expand All @@ -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) {
Expand All @@ -444,6 +448,8 @@ size_t count_char(const char* str, char ch) {
}
```

:::

## 参考资源

- [cppreference: C 语言隐式转换](https://en.cppreference.com/w/c/language/conversion)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ printf("%d\n", 7 % 2);
printf("%d\n", -7 % 2);
```

### 练习 1 参考答案
::: details 参考答案

```text
3 // 7 / 2 = 3.5,整数除法向零取整,砍掉小数部分 → 3
Expand All @@ -278,6 +278,8 @@ printf("%d\n", -7 % 2);

关键点:C99 起 `/` 和 `%` 都向零取整(truncation toward zero),正负数规则一致,直接丢弃小数部分。所以 `-7 / 2` 得 `-3` 而不是 `-4`,`-7 % 2` 的符号随被除数取负。

:::

### 练习 2:短路求值实战

写一个函数,安全地从数组中找到第一个大于指定值的元素。利用短路求值确保不越界:
Expand All @@ -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 <stddef.h>
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -341,6 +341,8 @@ uint32_t bit_extract(uint32_t value, int high, int low) {

`bit_extract` 里 mask 用 `1ULL` 是为了避开 `width == 32` 时 `1U << 32` 的移位溢出,这种细节在位域操作里很常见。

:::

### 练习 2:安全的移位

写一个函数,安全地执行左移操作,处理所有边界情况:
Expand All @@ -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 <stdint.h>
Expand All @@ -370,6 +372,8 @@ uint32_t safe_shift_left(uint32_t val, int n, int bits) {

只要保证 `n < bits` 且 `bits <= 32`,`val << n` 就不会触发移位溢出这类 UB。

:::

### 练习 3:表达式分析

分析以下表达式的求值行为(不实际运行),标出每个是"明确定义"、"未指定行为"还是"未定义行为":
Expand All @@ -382,7 +386,7 @@ int r3 = (a > b) ? a-- : b--; // ?
printf("%d %d\n", a++, a++); // ?
```

### 练习 3 参考答案
::: details 参考答案

```c
int a = 5, b = 3;
Expand All @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion documents/vol1-fundamentals/c_tutorials/04-control-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ C++17 引入了 `if constexpr`,它在编译期评估条件,直接把不满

用 `switch` 实现一个函数,根据月份和是否闰年返回该月的天数。要求利用穿透特性合并同天数的月份。

### 练习 1 参考答案
::: details 参考答案

```c
bool is_leap_year(int year) {
Expand All @@ -490,6 +490,8 @@ int month_day(int year, int month) {
}
```

:::

### 练习 2:安全的矩阵搜索

在二维矩阵中查找目标值。找到后用两种方式跳出多层循环:一种用标志变量,一种用 `goto`。
Expand Down
12 changes: 9 additions & 3 deletions documents/vol1-fundamentals/c_tutorials/05-function-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...) {
Expand Down Expand Up @@ -359,6 +359,8 @@ void log_message(LogLevel level, const char* format, ...) {
}
```

:::

### 练习 2:递归与迭代——二分查找

分别用递归和迭代实现二分查找,比较两者的性能和可读性:
Expand All @@ -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) {
Expand Down Expand Up @@ -405,6 +407,8 @@ int binary_search_iterative(const int* arr, size_t len, int target) {
}
```

:::

### 练习 3:多返回值实战

实现一个函数,同时计算数组的最大值和最小值:
Expand All @@ -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) {
Expand All @@ -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)
Expand Down
12 changes: 9 additions & 3 deletions documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ void counter_reset(void);

请自行实现 `counter.c`。

### 练习 1 参考答案
::: details 参考答案

main.c

Expand Down Expand Up @@ -508,6 +508,8 @@ int counter_get(void) {
}
```

:::

### 练习 2:多文件符号可见性

创建三个文件 `a.c`、`b.c`、`main.c`。要求:
Expand All @@ -524,7 +526,7 @@ int counter_get(void) {
// 各 .c 文件的实现留给你
```

### 练习 2 参考答案
::: details 参考答案

main.c

Expand Down Expand Up @@ -601,6 +603,8 @@ void set_kSharedValue(int value) {
}
```

:::

### 练习 3:延迟初始化

用 `static` 局部变量实现一个 `get_config` 函数:第一次调用时执行初始化(打印 "Initializing..." 并设置默认值),后续调用直接返回已初始化的值,不再重新初始化。
Expand All @@ -617,7 +621,7 @@ const Config* get_config(void);

> 提示:`static` 局部变量只在第一次进入函数时被初始化——正好可以用来实现"只初始化一次"的语义。

### 练习 3 参考答案
::: details 参考答案

```c
#include <stdio.h>
Expand Down Expand Up @@ -651,6 +655,8 @@ const Config* get_config(void) {
}
```

:::

## 参考资源

- [存储类别说明符 - cppreference](https://en.cppreference.com/w/c/language/storage_duration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ C++ 在指针的基础上做了两个关键的改进。第一个是**引用**(

写一个程序,声明三个不同类型的变量(`int`、`double`、`char`),打印它们的值、地址和 `sizeof` 结果。观察地址之间的间隔是否符合各类型的大小。

### 练习 1 参考答案
::: details 参考答案

```c
#include <stdio.h>
Expand Down Expand Up @@ -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` 数组并打印所有元素。要求不使用 `[]` 运算符,只用指针加减和解引用:
Expand All @@ -312,7 +314,7 @@ int main(void) {
void print_int_array(const int* data, size_t count);
```

### 练习 2 参考答案
::: details 参考答案

```c
#include <stdio.h>
Expand Down Expand Up @@ -344,6 +346,8 @@ data[3] = 4
data[4] = 5
```

:::

## 参考资源

- [cppreference: 指针声明](https://en.cppreference.com/w/c/language/pointer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ std::unique_ptr<int> p = std::make_unique<int>(42);
const int* linear_search(const int* data, size_t count, int target);
```

### 练习 1 参考答案
::: details 参考答案

```c
#include <stdio.h>
Expand Down Expand Up @@ -312,6 +312,8 @@ const int* linear_search(const int* data, size_t count, int target) {

地址本身不用记,关键是后面那个「第 3 个元素」——`result - arr` 算出来就是 `3`,这个结论是稳的。顺带一提:要是终端里中文显示成乱码,那是终端编码不是 UTF-8 的问题,跟代码无关,换 WSL2 或现代终端就好。

:::

### 练习 2:指针版数组反转

实现一个原地反转数组的函数,只使用指针算术(两个指针从两端向中间靠拢),不使用数组下标:
Expand All @@ -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 <stdio.h>
Expand Down Expand Up @@ -399,6 +401,8 @@ after reverse_array:
10
```

:::

### 练习 3:const 练习

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

### 练习 3 参考答案
::: details 参考答案

```c
int value = 42, other = 100;
Expand All @@ -436,6 +440,8 @@ const int* const p3 = &value;

```

:::

## 参考资源

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