From 96ee4eb84eb6f9bf55759105479b49bafa1727d3 Mon Sep 17 00:00:00 2001 From: lzy Date: Thu, 14 Mar 2024 17:53:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6=E4=BA=86=E7=AC=94=E8=AE=B0?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Chapter1/C1-绪论.md | 303 ++++++++++++++++ Chapter2/C2-数据类型,运算符和表达式.md | 448 ++++++++++++++++++++++++ README.md | 6 +- 4 files changed, 755 insertions(+), 3 deletions(-) create mode 100644 Chapter1/C1-绪论.md create mode 100644 Chapter2/C2-数据类型,运算符和表达式.md diff --git a/.gitignore b/.gitignore index 23975dd..d290bfe 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ # 允许跟踪所有的.c和.h文件 !**/*.c !**/*.h +!**/*.md \ No newline at end of file diff --git a/Chapter1/C1-绪论.md b/Chapter1/C1-绪论.md new file mode 100644 index 0000000..d5f5e12 --- /dev/null +++ b/Chapter1/C1-绪论.md @@ -0,0 +1,303 @@ +# 第一章 绪论 + +课程地址,[史上最强最细腻的linux嵌入式C语言学习教程【李慧芹老师】_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV18p4y167Md/?spm_id_from=333.999.0.0&vd_source=4e03f52e94cfa281cde032856b1f93a7)。 + +## 第一节 + +### C语言发展史 + +| 时间 | 发展 | +| ---- | -------------------- | +| 1960 | 原型A语言->ALGOL语言 | +| 1963 | CPL语言 | +| 1967 | BCPL | +| 1967 | B语言 | +| 1973 | C语言 | + + + +### C语言特点 + +1. 基础性语言 +2. 语法间接、紧凑、方便、灵活 +3. 运算符,数据结构丰富 +4. 结构化,模块化编程 +5. 移植性好,执行效率高 +6. 允许直接对硬件操作 + + + +### C语言学习建议 + +1. 概念的正确性 +2. 动手能力 +3. 阅读优秀的程序段 +4. 大量练习,面试题 + + + +### 课程思路 + +1. 基础概念 +2. 数据类型,运算符和表达式 +3. 输入输出专题 +4. 流程控制 +5. 数组 +6. 指针 +7. 函数 +8. 构造类型 +9. 动态内存管理 +10. 调试工具(gdb,make) +11. 常用库函数 + + + +### 课程平台 + +* 老师:64位的`redhat6`,`vim`,`gcc4.4.6(make)` +* 本人:`WSL-Ubuntu22.04`,`vscode(remote ssh)`,`gcc11.4.0(make)`,`ohmyzsh(theme:eastwood)` + +我的`gcc`版本比较新,所以很多提示比较完善,老师这个课程是很早的课,那个时候的gcc提示没有现在厉害,所以还是要结合老师的视频的思路,在当时提示不完善的时候是如何手撕代码分析出来的。 + + + +## 第二节 + +### 神一般的`Hello world!` + +```c +//hello.c +#include +#include + +#if 0 +void main(void); +void main(int argc, char **argv);// char *argv[] +int main(int argc, char **argv); +int main(void); +#endif + +int main(void) +{ + printf("Hello world!\n"); + + exit(0); +} +``` + +用那种`main`得看编译器环境,目前在`linux`的`gcc`下,`int main(void);`常用,需要传参用`int main(int argc, char **argv);`。 + + + +### 源文件到可执行文件 + +#### gcc + +C源文件=>预处理=>编译=>汇编=>链接=>可执行文件 + +> 所有`#`后面的代码都在预处理解决 + +```bash +# 单步执行 +gcc -E hello.c > hello.i # 预处理生成hello.i +gcc -S hello.i # 编译生成hello.s +gcc -c hello.s # 汇编生成hello.o +gcc hello.o -o hello # 链接生成hello +./hello # 运行 +Hello world! + +# 一次到位 +gcc hello.c # 得到a.out可执行文件 +# 指定名字 +gcc hello.c -o myhello # 得到myhello可执行文件 +``` + + + +#### make + +```bash +$ make hello +cc hello.c -o hello +# cc就是make选择的编译器,这里是gcc +$ ls +hello hello.c +``` + + + +### vim + +老师对于常用的`.vimrc`做了一些介绍,笔者建议`vscode`的`remote-ssh`插件,可以在win的vscode编辑器享受linux的环境。 + + + +## 第三节 + +### 基本概念 + +#### 以`Hello world`为例对写程序的思路提出如下要求 + +##### 1. 头文件正确包含的重要性 + +```c +#include + +int main(void) +{ + int *p = NULL; + int i; + p = malloc(sizeof(int)); + if (p == NULL) + return -1; + printf("Hello world!\n"); + return 0; +} +``` + +这里我的gcc版本比较新,所以和老师的不一致,总之老师的意思就是 + +> 不要忽略gcc的警告!!! + +```bash +# 通过 -Wall 参数让gcc显示全部警告 +# 注意'W'要大写 +$ gcc hello.c -Wall +hello.c: In function ‘main’: +hello.c:7:9: warning: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration] + 7 | p = malloc(sizeof(int)); + | ^~~~~~ +hello.c:2:1: note: include ‘’ or provide a declaration of ‘malloc’ + 1 | #include + +++ |+#include + 2 | +hello.c:7:9: warning: incompatible implicit declaration of built-in function ‘malloc’ [-Wbuiltin-declaration-mismatch] + 7 | p = malloc(sizeof(int)); + | ^~~~~~ +hello.c:7:9: note: include ‘’ or provide a declaration of ‘malloc’ +hello.c:6:9: warning: unused variable ‘i’ [-Wunused-variable] + 6 | int i; + | ^ + +``` + +* 例如说这个变量`i`定义了但是没用上,就是你清楚来龙去脉可以忽略的警告。 +* 而`malloc`那个警告就是在告诉你`malloc`所属的`stdlib.h`库未引入。 +* C语言程序往往如果能把警告都消除,错误也能解决。例如: + +```c +#include +#include +#include + +int main() +{ + FILE *fp; + + fp = fopen("tmp", "r"); + if (fp == NULL) + { + fprintf(stderr, "fopen():%s\n", strerror(errno)); + exit(1); + } + puts("ok!"); + exit(0); +} +``` + +这个程序运行报段错误,这个时候,查看一下警告就是问题所在。 + +```bash +$ ./a +[1] 1960 segmentation fault ./a + +$ gcc a.c -Wall +a.c: In function ‘main’: +a.c:12:41: warning: implicit declaration of function ‘strerror’; did you mean ‘perror’? [-Wimplicit-function-declaration] + 12 | fprintf(stderr, "fopen():%s\n", strerror(errno)); + | ^~~~~~~~ + | perror +a.c:12:35: warning: format ‘%s’ expects argument of type ‘char *’, but argument 3 has type ‘int’ [-Wformat=] + 12 | fprintf(stderr, "fopen():%s\n", strerror(errno)); + | ~^ ~~~~~~~~~~~~~~~ + | | | + | | int + | char * + | %d +``` + +用`%s`需要的类型是`char *`,但是`strerror(errno)`的类型是`int`,它是所以是`int`,是因为C语言对没有找到原型的函数返回值都是`int`,所以根源在于`strerror`函数没有原型,没有引入需要的`string.h`库。加入这个库重新运行。 + +```bash +[main][~/LinuxC/Chapter1/Section3]$ make a +cc a.c -o a +[main][~/LinuxC/Chapter1/Section3]$ ./a +fopen():No such file or directory +``` + + + +##### 2. 以函数为单位来进行程序编写 + +main`是特殊的定义,其实就是一个正在运行的线程。其实对于内核而言,是只有进程的概念,且与我们理解的进程、线程不一样。 + + + +##### 3. 声明部分+实现部分 + +早期的编译器要求变量先定义后使用。 + +##### 4. `return 0/exit(0)` + +结束当前进程,是给父进程看的。 + +对于带有`return 0`的程序: + +```bash +[main][~/LinuxC/Chapter1/Section3]$ ./hello +Hello world! +[main][~/LinuxC/Chapter1/Section3]$ echo $? +0 +``` + +如果不带`return 0`: + +老师演示的返回值是13,是`printf`的返回的`Hello world!\n`的长度。 + +在我本地无法复现,新版本可能改了。 + +##### 5. 多用空格空行 + +##### 6. 添加注释 + +```c +//单行注释 + +/* + * 多行注释 + * 多行注释 + * 多行注释 + */ + +//大段的注释 +#if 0 +func(){ + +} +#endif +``` + +#### 算法 + +解决问题的方法。(流程图,NS图,FSM有限状态机) + +#### 程序 + +用某种语言实现算法 + +#### 进程 + +#### 防止写越界,防止内存泄漏,谁打开谁关闭,谁申请谁释放 + + + diff --git a/Chapter2/C2-数据类型,运算符和表达式.md b/Chapter2/C2-数据类型,运算符和表达式.md new file mode 100644 index 0000000..8475012 --- /dev/null +++ b/Chapter2/C2-数据类型,运算符和表达式.md @@ -0,0 +1,448 @@ +# 第二章 数据类型,运算符和表达式 + +## 数据类型(基本数据类型) + +> 数据类型所占字节数随机器硬件不同而不同,以`int`为基准,`char`比它小,`float`和`double`比它大。 + +1. 所占字节数 + +2. 存储区别 + +3. 不同类型的数据间转换(隐式和**显式**=>强制类型转换) + +4. 特殊性 + + 1. 布尔型(`bool`): + + `false`:`0`,`true`:非零值。 + + 2. `float`类型,不够精确,其实是一个小范围。 + + ```c + int func(float f) + { + if (f < 0) + return -1; + // else if (f == 0) + else if (fabs(f - 0) <= 1e-6) + return 0; + else + return 1; + } + ``` + + 3. `char`型是否有符号,未定义 + + 4. 不同形式的零值 + + `0`,`'0'`,`"0"`,`'\0'`,`NULL` + + 5. 数据类型得与后续代码的输入输入相匹配(自相矛盾) + + + +## 常量与变量 + +### 常量 + +#### 定义 + +在程序执行过程中值不会发生变化的量。 + +#### 分类 + +* 整型常量:1,790,76 + +* 实型常量:3.14,5.26 +* 字符常量:由单引号引起来的单个字符或转义字符,如`'a'`,`'X'`,`'\n'`,`'\ddd'`(d可以是`0-7`,三位八进制,例如`'\015'`),`'\xhh'`(h可以是`0-F`,二位十六进制,`'\x7f'`) +* 字符串常量:由双引号引起来的一个或多个字符组成的序列(特殊情况,空串),如:`""`,`"a"`,`"abXYZ"`,`"abc\n\021\018"`(注意这个字符串,`\n`,`\021`都是算单个字符,`\018`又不算) + +> 我问gpt,"\018"在C语言几个字符。 +> 在C语言中,字符串是以字符数组的形式表示,而每个字符串的末尾都有一个特殊的字符`'\0'`(空字符),用于标识字符串的结束。当你提到的`"\018"`在C语言中出现时,它表示一个包含特殊字符的字符串字面量。 +> +> 具体来说,`\0`在C语言中是一个转义字符,用于表示字符串的结束符(null terminator)。该符号用于标记字符串的结束,确保字符串处理函数如`printf`、`strcpy`等知道在哪里停止处理。 +> +> 在`"\018"`中,`\0`表示字符串的结束,后面跟的`18`并不会作为字符串的一部分来处理。因此,如果你在C语言中使用`"\018"`,它实际上会被视为只包含一个空字符(`\0`)的字符串。这意味着,尽管看起来字符串中有更多的字符,但由于`\0`的存在,它实际上在C语言处理中表示一个空字符串。 +> +> 总结来说,在C语言中`"\018"`实际上会被看作一个包含单一结束字符(null terminator)的字符串,而不是包含多个字符。这种处理方式是因为C语言使用空字符来标记字符串的结束。 + +* 标识常量:`#define` + + 特点:完整的用宏体替换宏名,**不做任何检查**。 + + ```c + #include + #include + + #define PI 3.14 + #define ADD 2 + 3 + #define ADD1 (2 + 3) + + #define MAX(a, b) (a > b ? a : b) + #define MAX1(a, b) ((a) > (b) ? (a) : (b)) + + int main() + { + // printf("%d\n", ADD * ADD); + // 相当于 2+3*2+3 + // 故输出11而不是25 + // printf("%d\n", ADD1 * ADD1); + // 相当于(2+3)*(2+3) + // 正常输出25 + + int i = 5, j = 3; + // printf("%d\n", MAX(i, j)); + // 输出5 + + // printf("%d\n", MAX(i, j * 2)); + // 输出6 + + printf("i=%d\tj=%d\n", i, j); + printf("%d\n", MAX1(i++, j++)); + printf("i=%d\tj=%d\n", i, j); + // 输出: + // i = 5 j = 3 + // 6 + // i = 7 j = 4 + // 为什么i自增了两次? + // 预处理结果 + // printf("%d\n", ((i++) + (j++) ? (i++) : (j++))); + + exit(0); + } + ``` + + 解决办法: + + 1. 使用函数:函数与宏的区别在于,一个占用编译时间,一个占用运行时间。在`linux`内核中多用宏。 + 2. 在宏中进行变量保存,这种写法超出标准C,属于`GNU C`的扩展部分,只能在支持的编译器(如`gcc`)中使用,在`linux`内核中非常常用。 + + ```c + #define MAX2(a, b) \ + ({ \ + int A = a, B = b; \ + ((A) > (B) ? (A) : (B)); \ + }) + + #define MAX3(a, b) \ + ({ \ + typeof(a) A = a, B = b; \ + ((A) > (B) ? (A) : (B)); \ + }) + + ``` + + + + + + + +### 变量 + +用来保存一些特定内容,并且在程序执行过程中值**随时会发生变化**的量。 + +#### 定义 + +| [存储类型] | 数据类型 | 标识符 | = | 值 | +| :--------: | :------: | :----: | :--: | :---: | +| | TYPE | NAME | = | VALUE | + +* 标识符:由字母,数字,下划线组成且不能以数字开头的一个标识序列。拟定时尽量做到见名知义。 +* 数据类型:基本数据类型、构造类型 +* 值:注意匹配 +* 存储类型:`auto`,`static`,`register`,`extern`(说明型) + * `auto`:默认,自动分配空间,自动回收空间。 + * `register`:(建议型,编译器不一定采用)寄存器类型,只能定义局部变量,不能定义全局变量;大小有定义,只能定义32位大小的数据类型,如`double`就不可以;集尘器没有地址,所以一个寄存器类型的变量无法打印出地址查看或使用。 + * `static`:静态型,自动初始化为0值或空值,并且变量的值有继承性。另外,常用来修饰一个变量和函数,防止其对外扩散。 + * `extern`:说明型,意味着不能改变被说明的变量的值或类型。 + + + +```c +#include +#include + +#if 0 +void func(void) +{ + int x = 0; + x = x + 1; + printf("%p->%d\n", &x, x); +} + +void func1(void) +{ + static int x = 0; + x = x + 1; + printf("%p->%d\n", &x, x); +} + +int main() +{ + + // auto int i; + // printf("i=%d\n", i); + // out:i=21915,每次都不一样 + + // static int i; + // printf("i=%d\n", i); + // out:i=1 + + // func(); + // func(); + // func(); + // out:: + // 0x7ffc3c4ca4f4->1 + // 0x7ffc3c4ca4f4->1 + // 0x7ffc3c4ca4f4->1 + // 三次地址看着一样 + // 但是是每次函数开始取用,结束销毁的 + // 只是gcc刚好都取的栈上同一块地址 + + func1(); + func1(); + func1(); + // out:: + // 0x55fd83c96014->1 + // 0x55fd83c96014->2 + // 0x55fd83c96014->3 + + exit(0); +} +#endif + +#if 0 +int i = 100; + +void func(int i) +{ + printf("i=%d\n", i); +} + +int main() +{ + + int i = 3; + + // printf("i=%d\n", i); + // out: + // i=3 + + // { + // printf("i=%d\n", i); + // } + // out: + // i=3 + + // { + // i = 5; + // printf("i=%d\n", i); + // } + // out: + // i=5 + + // func(i); + + exit(0); +} +#endif + +int i = 0; + +void print_star(void) +{ + for (i = 0; i < 5; i++) + printf("*"); + printf("\n"); + printf("[%s]i=%d\n", __FUNCTION__, i); +} + +int main() +{ + for (i = 0; i < 5; i++) + print_star(); + printf("\n"); + + // out: + // ***** + // [print_star] i = 5 + + exit(0); +} +``` + + + +#### 变量的生命周期与作用范围 + +1. 全局变量和局部变量 +2. 局部变量和局部变量 +3. 参考图片存储类型比较 + +![存储类型比较](https://s2.loli.net/2024/03/12/y8pMEXS3LBIDAuc.png) + +> 这一块具体讲解见代码仓库`/Chapter2/变量/`。 + +在`minproj`例子中,如果在`proj.c`和`proj.h`中`static`定义`func`函数,而在`main.c`中调用`func`。 + +```bash +[main][~/LinuxC/Chapter2/Section5/minproj]$ gcc *.c +In file included from main.c:4: +proj.h:4:13: warning: ‘func’ used but never defined + 4 | static void func(void); + | ^~~~ +/usr/bin/ld: /tmp/ccQ2317U.o: in function `main': +main.c:(.text+0x2f): undefined reference to `func' +collect2: error: ld returned 1 exit status +``` + + + +### 运算符和表达式 + +#### 表达式和语句的区别 + +* 运算符部分 + + ![运算符](https://s2.loli.net/2024/03/12/3MkPpF5anv7eEGu.png) + + * 每个运算符所需要的参与运算的操作数个数 + + * 结合性 + + * 优先级 + + * 运算符的特殊用法 + + 如:`%`(要求左右两边都是整形),`=`与`==`,逻辑运算(`&&`和`||`)的短路特性 + + * 位运算的重要性 + + `<< >> ~ | ^ &` + + 1. 将操作数中第n位置1,其他位不变:`num = num | 1 << n;` + + 2. 将操作数中第n位清0,其他位不变:`num = num & ~(1 << n);` + + 3. 测试第n位:`if(num & 1 << n)` + + 4. 从一个指定宽度的数中取出某几位: + + ```c + // 假设取一个32位整数的第10位到第15位 + unsigned int number; + unsigned int mask = ((1 << 6) - 1) << 9; // 6 是位数(15-10+1),9 是起始位置(10-1) + unsigned int result = number & mask; + result = result >> 9; + ``` + +#### 运算符相关代码 + +```c +#if 0 +i++相当于i=i+1 +i--相当于i=i-1 + +int i=1; +i++;表达式值为1,i值为2 +++i;表达式值为2,i值为2 + +#endif + +#include +#include + +#if 0 +int main() +{ + int i = 1, j = 10, value; + + // value = i++ + ++j; + // 相当于 + // j = j + 1; + // value = i + j; + // i = i + 1; + // out: + // i = 0 j = 11 value = 10 + + // value = --i + j++; + // i = i - 1; + // value = i + j; + // j = j + 1; + // out: + // i = 1 j = 12 value = 12 + + // value = i++ + ++i - i-- + --i; + // 避免单个变量多次自增或者自减 + // 不同编译器可能结果不同,也难为自己和他人 + + // printf("i=%d\n", i); + // printf("j=%d\n", j); + // printf("value=%d\n", value); + + // printf("%d\n", i > j); + + int a = 1, b = 2, c = 3, d = 4; + int m = 1, n = 1; + + // (m = a > b) && (n = c > d); + // printf("m = %d\nn = %d\n", m, n); + // a>b为假,所以左边为0,右边直接不判断了,n依旧为1而不是0! + // out: + // m = 0 + // n = 1 + + (m = a > b) || (n = c > d); + printf("m = %d\nn = %d\n", m, n); + // a>b为假,所以左边为0,右边继续判断 + // out: + // m = 0 + // n = 0 + + exit(0); +} +#endif + +#if 0 +int main() +{ + int i = 0, j = 10, value; + + // int a = 6; + // a -= a *= a += 3; + // a -=.. 81-81=0 + // a *=.. 9*9=81给上面 + // a += 3; 6+3=9给上面 + // 故a=0 + + // printf("%d\n", sizeof(int)); + // printf("%d\n", sizeof(double)); + // out: + // 4 + // 8 + + // int a = 3; + // float f = 3.9; + // a = f; + // printf("a=%d\n", a); + // printf("f=%f\n", f); + // out: + // a=3 + // f=3.900000 + + // int a = 3; + // float f = 3.9; + // a = (int)f; // 这个过程不改变f本身的地址和值 + // printf("a=%d\n", a); + // printf("f=%f\n", f); + // out: + // a=3 + // f=3.900000 + + exit(0); +} +#endif + +``` + diff --git a/README.md b/README.md index e68d83b..f286570 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Linux-C-Code +# Linux-C-Notes Linux环境C语言开发学习笔记 ## 课程地址 [史上最强最细腻的linux嵌入式C语言学习教程【李慧芹老师】_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV18p4y167Md/?spm_id_from=333.999.0.0&vd_source=4e03f52e94cfa281cde032856b1f93a7) ## 本仓库内容 -教学过程中所有的代码,注释清晰。 -配套学习笔记在另外一个仓库,[Linux-C-Note](https://github.com/sinlatansen/Linux-C-Note)。 +教学过程中所有的课程笔记和代码示例。 + ## 作者博客 [fugu的小站](https://lzyyyyyy.fun/index.php/tag/%E6%9D%8E%E6%85%A7%E8%8A%B9/)也会同步更新,欢迎造访以获得更好的在线`markdown`阅读体验。