Files
Linux-C-Notes/C02-数据类型,运算符和表达式/C2-数据类型,运算符和表达式.md
2024-06-23 17:45:58 +08:00

450 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 目录
- [目录](#目录)
- [第二章 数据类型,运算符和表达式](#第二章-数据类型运算符和表达式)
- [数据类型(基本数据类型)](#数据类型基本数据类型)
- [常量与变量](#常量与变量)
- [常量](#常量)
- [定义](#定义)
- [分类](#分类)
- [变量](#变量)
- [定义](#定义-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. 数据类型得与后续代码的输入输入相匹配(自相矛盾)
## 常量与变量
### 常量
#### 定义
在程序执行过程中值不会发生变化的量。
#### 分类
- 整型常量179076
- 实型常量3.145.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 <stdio.h>
#include <stdlib.h>
#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 <stdio.h>
#include <stdlib.h>
#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+19 是起始位置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++;表达式值为1i值为2
++i;表达式值为2i值为2
#endif
#include <stdio.h>
#include <stdlib.h>
#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
```