Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(NekoBytes-TheMissing): 优化文档 #318

Merged
merged 2 commits into from
Oct 15, 2024
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
@@ -1,4 +1,4 @@
# C 语言基础语法
# C语言基础语法

在了解完C语言环境的搭建以后,让我们开始进行编程吧,本节课将涉及到抽象思维,C语言基础操作,以及指针的入门。

Expand All @@ -8,7 +8,7 @@

我们为什么需要抽象?

举个简单的例子
举个简单的例子:

你看得懂这是什么吗?

Expand Down Expand Up @@ -58,13 +58,13 @@ int main(void) {

```c
/**
* @var 游戏板: board[board_size][board_size]
* @func 初始化: initialize_board
* @func 随机生成: generate_number
* @func 打印显示: print_board
* @func 读取移动指令: choose_direction
* @func 合并并移动: move_and_merge
* @func 判断游戏终止: is_finished
* @param 游戏板: board[board_size][board_size]
* @param 初始化: initialize_board
* @param 随机生成: generate_number
* @param 打印显示: print_board
* @param 读取移动指令: choose_direction
* @param 合并并移动: move_and_merge
* @param 判断游戏终止: is_finished
*/
void run_game() {
initialize_board();
Expand All @@ -85,7 +85,6 @@ void run_game() {
```

> 抽象的设计使得我们将复杂的逻辑分解为若干简单的函数模块,各个模块之间相互独立并可以重复使用,这样可以使代码结构更加清晰、易于维护和扩展。
>
> 虽然我还没有实现贪吃蛇的功能,但显然我已经把贪吃蛇的思路设计好了,有了思路,我们就能进行进一步的编程,逐个实现各个函数,最后拼在一起就是完整的程序了。这就是抽象,理解了吗。

## C语言基础操作
Expand All @@ -109,13 +108,13 @@ const char c = 'c';

> [!warning]
> 常量一经定义,无法更改!
>
> 但凡编译器发现你在程序中更改了这个变量,那会直接报编译失败

> [!tip]
> 常量有什么用呢
>
> 当你和你的好友一起写代码时,你想让你的好友写一段播放音频的代码,并且为你需要为你的好友提供一个程序接口,你一定不想让你的好友更改你传入的音频文件,因为这会导致一些不可预知的bug,这时候,你就能考虑使用`const`来保证你的朋友不会更改你的音频信息`void play_wav(const int* audio,size_t length);`
>
> 确保自己以后不小心修改了逻辑上不可修改的变量时编译器可以及时报错提醒,而不是等到程序运行一段时间后发生错误。

### 控制流

Expand Down Expand Up @@ -157,7 +156,7 @@ const char c = 'c';
break; // 千万别忘加
default:
printf("you don\'t choose any answer!\n");
break; // 千万别忘加
break; // 可忽略
}

```
Expand Down Expand Up @@ -262,23 +261,23 @@ int main(){
> [!tip]
>
> 请注意数组是内存中的一块连续的空间,加入你定义了一个大小为3的数组`int tmp[3]`,千万不要访问第四个元素tmp[3],因为这会导致ub(未定义行为),会导致你的程序出现莫名其妙的bug
>
> 并且,你只能在初始化的时候这么豪爽地将好几个值赋值到数组中,当完成初始化后,就不能使用这种方法了,需要一个一个进行修改

### 字符串

字符串是一类特殊的数组,它以`\0`结尾:
字符串是一类特殊的数组,它以`\0`结尾

> [!CAUTIONS]
> [!CAUTION]
> 定义字符串的时候需要注意一点,字符串的大小一定要比数据大至少一个单位,我们需要用这一个单位的空间存储`\0`
> 就比如说

```c
char mystr[5] = "abcc";
```

> 当然,字符串也是只能在初始化的时候这么畅快,之后倘若要修改的话,不调用标准库,就只能一个一个改,别忘了,改的时候需要在字符串的末尾添加`\0`,不然就不是一个完整的字符串
> 当然,字符串也是只能在复制的时候这么畅快,之后倘若要修改的话,不调用标准库,就只能一个一个改,别忘了,改的时候需要在字符串的末尾添加`\0`,不然就不是一个完整的字符串
>
>使用标准库`<string.h>`中的`strcpy`函数就能实现快速赋值
> 使用标准库`<string.h>`中的`strcpy`函数就能实现快速赋值

```c
#include <string.h>
Expand Down Expand Up @@ -340,7 +339,7 @@ char c = 'c';

> [!note]
>
> 每一个地址存储一个字节(8位),在典型的64位机中,`int`类型占用4个字节,`float`类型也占用4个字节,`char`类型占用1个字节
> 每一个地址存储一个字节(8位),在64位机中,`int`类型占用4个字节,`float`类型也占用4个字节,`char`类型占用1个字节

> 假设我们定义一个变量`int d;`不给他传递初始值,那么,编译器会为它分配一片空间,有可能编译器不对他赋初始值,那么,我们倘若直接读取这个变量的时候,有可能读到一个数值,这个数值就是原来内存存储的数值,我们将这种数据称为**垃圾值**

Expand Down Expand Up @@ -388,7 +387,7 @@ int array[2][2] = {{1,2},{3,4}};

我们可以发现,不管是多维数组,还是一维数组,存储结构都是线性的,多维数组是按行进行展开的

> [!warning]
> [!WARNING]
>
>请先跳过这段,等到学完指针之后,再来看下面这个程序,你会发现什么呢?

Expand All @@ -415,6 +414,9 @@ matrix[0] = 0x7ffc624f4cd0

### 指针

> [!CAUTION]
> 指针是C语言中最困难的部分,我们将花大量的篇幅讲解指针,如果初学的你无法完全理解下面的内容,这是正常的。如果可以,请尝试编写程序验证自己的理解,或者保留你的疑问等待后续讲座的深入讲解。

指针也可以看作一个变量,我们对其的定义是这样的

```c
Expand Down Expand Up @@ -560,7 +562,7 @@ main.c:10:19: warning: initialization of ‘int **’ from incompatible pointer
> [!note]
> C语言对于指针比较"宽容",即使检查到你错误的使用了指针,编译器也只会报一个警告,而不是错误,所以,当出现关于指针的警告是,请将它看作报错,并想办法去解决它
>
> 或者你也可以在使用gcc进行编译时,加入`-werror`将警告变为错误处理
> 或者你也可以在使用gcc进行编译时,加入`-Werror`将警告变为错误处理

### 指针的加减法

Expand All @@ -578,30 +580,29 @@ main.c:10:19: warning: initialization of ‘int **’ from incompatible pointer
表示指针之间的偏移量
仅当原指针和结果指针都指向同一数组中的元素,或该数组的尾后一位置,行为才有定义。
> [!warning]
>
> 警惕越界行为!!!

### void*

使用void类型指针的时候无法进行解引用,也无法进行加减运算
使用void类型指针的时候无法进行解引用也无法进行加减运算

使用void*类型的指针的时候记得要对其进行强制类型转化
使用void*类型的指针的时候一定要对其进行强制类型转化

```c
int a = 1;
void *p = &a;
printf("%d\n",*((int*)p));
```

### 警惕ub(未定义行为)
### 警惕UB(未定义行为)

我们很多时候需要警惕未定义行为,就比如说
我们很多时候需要警惕[未定义行为](https://zh.cppreference.com/w/c/language/behavior),就比如说:

```c
a[i] = ++i +1;
```

i在这里多次使用且数值发生了改变,没人知道a[i]先执行还是++i先执行
`i`在这里多次使用且数值发生了改变,没人知道`a[i]`先求值还是`++i`先求值

还有就是

Expand All @@ -610,8 +611,13 @@ i在这里多次使用且数值发生了改变,没人知道a[i]先执行还是++
int a = f(i++)+f(i++)-f(i++);
```

虽然在最后的结果上编译器会将其翻译为int a = (f(i++)+f(i++))-f(i++);
但没人知道哪个f(i++)最先执行,可能是第一个,也可能是第三个
虽然在最后的结果上编译器会将其翻译为`int a = (f(i++)+f(i++))-f(i++)`;
但没人知道哪个`f(i++)`最先求值,可能是第一个,也可能是第三个。

> [!CAUTION]
> 请一定避免写出这样的代码
>
> 我们不推荐初学者了解这里的细节,但如果你想要了解,请参考关于[求值顺序](https://zh.cppreference.com/w/c/language/eval_order)的语法标准,并做好关于**运算顺序**和**求值顺序**的区分。

## 问题

Expand Down
6 changes: 5 additions & 1 deletion 2.编程模块/2.1 NekoBytes-TheMissing/2.1.2 Lab/2.Lab2.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@

- 题中数独采用二维数组,需解答填充的数字用0代替如:

```txt
0 6 0 0 0 0 0 7 1
7 0 5 4 0 3 0 0 0
0 0 0 6 7 0 3 0 0
Expand All @@ -120,6 +121,9 @@
6 0 1 0 2 0 0 0 3
0 7 9 0 0 0 5 0 2
0 0 0 0 0 4 7 0 0
提供了`printBoard(int board[N][N])`函数打印数独和`isSafe(int board[N][N], int row, int col, int num)`函数检查数字是否可以放在board[row][col]位置。
```

- 使用`printBoard(int board[N][N])`函数打印数独
- 使用`isSafe(int board[N][N], int row, int col, int num)`函数检查数字是否可以放在board[row][col]位置。
- 运行judge.sh检测程序正确性。
- 完成文件`sudoku.c`中的`solveSudoku`函数编写。
Loading