# 目录 - [目录](#目录) - [第二章 数据类型,运算符和表达式](#第二章-数据类型运算符和表达式) - [数据类型(基本数据类型)](#数据类型基本数据类型) - [常量与变量](#常量与变量) - [常量](#常量) - [定义](#定义) - [分类](#分类) - [变量](#变量) - [定义](#定义-1) - [变量的生命周期与作用范围](#变量的生命周期与作用范围) - [运算符和表达式](#运算符和表达式) - [表达式和语句的区别](#表达式和语句的区别) - [运算符相关代码](#运算符相关代码) # 第二章 数据类型,运算符和表达式 ## 数据类型(基本数据类型) > 数据类型所占字节数随机器硬件不同而不同,以`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 ```