2024-10-22 11:21:30 +08:00

707 lines
23 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.1 运算符、表达式和操作数
* 运算符是一种特殊的符号,用于数据的运算、赋值和比较等。
* `表达式`指的是一组运算数、运算符的组合,表达式`一定具有值`,一个变量或一个常量可以是表达式,变量、常量和运算符也可以组成表达式,如:
![](./assets/20.svg)
* `操作数`指的是`参与运算``值`或者`对象`,如:
![](./assets/21.svg)
## 1.2 运算符的分类
* 根据`操作数``个数`,可以将运算符分为:
* 一元运算符(一目运算符)。
* 二元运算符(二目运算符)。
* 三元运算符(三目运算符)。
* 根据`功能`,可以将运算符分为:
* 算术运算符。
* 关系运算符(比较运算符)。
* 逻辑运算符。
* 赋值运算符。
* 逻辑运算符。
* 位运算符。
* 三元运算符。
> [!NOTE]
>
> 掌握一个运算符,需要关注以下几个方面:
>
> * ① 运算符的含义。
> * ② 运算符操作数的个数。
> * ③ 运算符所组成的表达式。
> * ④ 运算符有无副作用,即:运算后是否会修改操作数的值。
# 第二章:算术运算符(⭐)
## 2.1 概述
* 算术运算符是对数值类型的变量进行运算的,如下所示:
| 运算符 | 描述 | 操作数个数 | 组成的表达式的值 | 副作用 |
| ------ | ------------ | ---------- | ------------------------ | ------ |
| `+` | 正号 | 1 | 操作数本身 | ❎ |
| `-` | 负号 | 1 | 操作数符号取反 | ❎ |
| `+` | 加号 | 2 | 两个操作数之和 | ❎ |
| `-` | 减号 | 2 | 两个操作数之差 | ❎ |
| `*` | 乘号 | 2 | 两个操作数之积 | ❎ |
| `/` | 除号 | 2 | 两个操作数之商 | ❎ |
| `%` | 取模(取余) | 2 | 两个操作数相除的余数 | ❎ |
| `++` | 自增 | 1 | 操作数自增前或自增后的值 | ✅ |
| `--` | 自减 | 1 | 操作数自减前或自减后的值 | ✅ |
> [!NOTE]
>
> 自增和自减:
>
> * ① 自增、自减运算符可以写在操作数的前面也可以写在操作数后面,不论前面还是后面,对操作数的副作用是一致的。
> * ② 自增、自减运算符在前在后,对于表达式的值是不同的。 如果运算符在前,表达式的值是操作数自增、自减之后的值;如果运算符在后,表达式的值是操作数自增、自减之前的值。
> * ③ `变量前++`:变量先自增 1 ,然后再运算;`变量后++`:变量先运算,然后再自增 1 。
> * ④ `变量前--`:变量先自减 1 ,然后再运算;`变量后--`:变量先运算,然后再自减 1 。
> * ⑤ 对于 `i++` 或 `i--` 各种编程语言的用法和支持是不同的例如C/C++、Java 等完全支持Python 压根一点都不支持Go 语言虽然支持 `i++` 或 `i--` ,却只支持这些操作符作为独立的语句,并且不能嵌入在其它的表达式中。
## 2.2 应用示例
* 示例:正号和负号
```c
#include <stdio.h>
int main() {
int x = 12;
int x1 = -x, x2 = +x;
int y = -67;
int y1 = -y, y2 = +y;
printf("x1=%d, x2=%d \n", x1, x2); // x1=-12, x2=12
printf("y1=%d, y2=%d \n", y1, y2); // y1=67, y2=-67
return 0;
}
```
* 示例:加、减、乘、除(整数之间做除法时,结果只保留整数部分而舍弃小数部分)、取模
```c
#include <stdio.h>
int main() {
int a = 5;
int b = 2;
printf("%d + %d = %d\n", a, b, a + b); // 5 + 2 = 7
printf("%d - %d = %d\n", a, b, a - b); // 5 - 2 = 3
printf("%d × %d = %d\n", a, b, a * b); // 5 × 2 = 10
printf("%d / %d = %d\n", a, b, a / b); // 5 / 2 = 2
printf("%d %% %d = %d\n", a, b, a % b); // 5 % 2 = 1
return 0;
}
```
* 示例:取模(运算结果的符号与被模数也就是第一个操作数相同。)
```c
#include <stdio.h>
int main() {
int res1 = 10 % 3;
printf("10 %% 3 = %d\n", res1); // 10 % 3 = 1
int res2 = -10 % 3;
printf("-10 %% 3 = %d\n", res2); // -10 % 3 = -1
int res3 = 10 % -3;
printf("10 %% -3 = %d\n", res3); // 10 % -3 = 1
int res4 = -10 % -3;
printf("-10 %% -3 = %d\n", res4); // -10 % -3 = -1
return 0;
}
```
* 示例:自增和自减
```c
#include <stdio.h>
int main() {
int i1 = 10, i2 = 20;
int i = i1++;
printf("i = %d\n", i); // i = 10
printf("i1 = %d\n", i1); // i1 = 11
i = ++i1;
printf("i = %d\n", i); // i = 12
printf("i1 = %d\n", i1); // i1 = 12
i = i2--;
printf("i = %d\n", i); // i = 20
printf("i2 = %d\n", i2); // i2 = 19
i = --i2;
printf("i = %d\n", i); // i = 18
printf("i2 = %d\n", i2); // i2 = 18
return 0;
```
* 示例:
```c
#include <stdio.h>
/*
随意给出一个整数,打印显示它的个位数,十位数,百位数的值。
格式如下:
数字xxx的情况如下
个位数:
十位数:
百位数:
例如:
数字153的情况如下
个位数3
十位数5
百位数1
*/
int main() {
int num = 153;
int bai = num / 100;
int shi = num % 100 / 10;
int ge = num % 10;
printf("百位为:%d \n", bai);
printf("十位为:%d \n", shi);
printf("个位为:%d \n", ge);
return 0;
}
```
# 第三章:关系运算符(⭐)
## 3.1 概述
* 常见的关系运算符(比较运算符),如下所示:
| 运算符 | 描述 | 操作数个数 | 组成的表达式的值 | 副作用 |
| ------ | -------- | ---------- | ---------------- | ------ |
| `==` | 相等 | 2 | 0 或 1 | ❎ |
| `!=` | 不相等 | 2 | 0 或 1 | ❎ |
| `<` | 小于 | 2 | 0 或 1 | ❎ |
| `>` | 大于 | 2 | 0 或 1 | ❎ |
| `<=` | 小于等于 | 2 | 0 或 1 | ❎ |
| `>=` | 大于等于 | 2 | 0 或 1 | ❎ |
> [!NOTE]
>
> * ① C 语言中,没有严格意义上的布尔类型,可以使用 0 或 1表示布尔类型的值。
> * ② 不要将 `==` 写成 `=``==` 是比较运算符,而 `=` 是赋值运算符。
> * ③ `>=` 或 `<=`含义是只需要满足 `大于或等于`、`小于或等于`其中一个条件,结果就返回真。
## 3.2 应用示例
* 示例:
```c
#include <stdio.h>
int main() {
int a = 8;
int b = 7;
printf("a > b 的结果是:%d \n", a > b); // a > b 的结果是1
printf("a >= b 的结果是:%d \n", a >= b); // a >= b 的结果是1
printf("a < b 的结果是:%d \n", a < b); // a < b 的结果是0
printf("a <= b 的结果是:%d \n", a <= b); // a <= b 的结果是0
printf("a == b 的结果是:%d \n", a == b); // a == b 的结果是0
printf("a != b 的结果是:%d \n", a != b); // a != b 的结果是1
return 0;
}
```
# 第四章:逻辑运算符(⭐)
## 4.1 概述
* 常见的逻辑运算符,如下所示:
| 运算符 | 描述 | 操作数个数 | 组成的表达式的值 | 副作用 |
| ------ | ------ | ---------- | ---------------- | ------ |
| `&&` | 逻辑与 | 2 | 0 或 1 | ❎ |
| `\|\|` | 逻辑或 | 2 | 0 或 1 | ❎ |
| `!` | 逻辑非 | 2 | 0 或 1 | ❎ |
* 逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,如下所示:
| a | b | a && b | a \|\| b | !a |
| ------- | ------- | ------- | -------- | ------- |
| 1 | 1 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 0 | 0 | 0 | 0 | 1 |
> [!NOTE]
>
> * ① 对于逻辑运算符来说,任何`非零值`都表示`真``零值`表示`假`,如:`5 || 0` 返回 `1` `5 && 0` 返回 `0` 。
> * ② 逻辑运算符的理解:
> * `&&` 的理解就是:`两边条件,同时满足`。
> * `||`的理解就是:`两边条件,二选一`。
> * `!` 的理解就是:`条件取反`。
> * ③ 短路现象:
> * 对于 `a && b` 操作来说,当 a 为假(或 0 )时,因为 `a && b` 结果必定为 0所以不再执行表达式 b。
> * 对于 `a || b` 操作来说,当 a 为真(或非 0 )时,因为 `a || b` 结果必定为 1所以不再执行表达式 b。
## 4.2 应用示例
* 示例:
```c
#include <stdio.h>
int main() {
int a = 0;
int b = 0;
printf("请输入整数a的值");
scanf("%d", &a);
printf("请输入整数b的值");
scanf("%d", &b);
if (a > b) {
printf("%d > %d", a, b);
} else if (a < b) {
printf("%d < %d", a, b);
} else {
printf("%d = %d", a, b);
}
return 0;
}
```
* 示例:
```c
#include <stdio.h>
// 短路现象
int main() {
int i = 0;
int j = 10;
if (i && j++ > 0) {
printf("床前明月光\n"); // 这行代码不会执行
} else {
printf("我叫郭德纲\n");
}
printf("%d \n", j); //10
return 0;
}
```
* 示例:
```c
#include <stdio.h>
// 短路现象
int main() {
int i = 1;
int j = 10;
if (i || j++ > 0) {
printf("床前明月光 \n");
} else {
printf("我叫郭德纲 \n"); // 这行代码不会被执行
}
printf("%d\n", j); //10
return 0;
}
```
# 第五章:赋值运算符(⭐)
## 5.1 概述
* 常见的赋值运算符,如下所示:
| 运算符 | 描述 | 操作数个数 | 组成的表达式的值 | 副作用 |
| ------ | ------------ | ---------- | ---------------- | ------ |
| `==` | 赋值 | 2 | 左边操作数的值 | ✅ |
| `+=` | 相加赋值 | 2 | 左边操作数的值 | ✅ |
| `-=` | 相减赋值 | 2 | 左边操作数的值 | ✅ |
| `*=` | 相乘赋值 | 2 | 左边操作数的值 | ✅ |
| `/=` | 相除赋值 | 2 | 左边操作数的值 | ✅ |
| `%=` | 取余赋值 | 2 | 左边操作数的值 | ✅ |
| `<<=` | 左移赋值 | 2 | 左边操作数的值 | ✅ |
| `>>=` | 右移赋值 | 2 | 左边操作数的值 | ✅ |
| `&=` | 按位与赋值 | 2 | 左边操作数的值 | ✅ |
| `^=` | 按位异或赋值 | 2 | 左边操作数的值 | ✅ |
| `\|=` | 按位或赋值 | 2 | 左边操作数的值 | ✅ |
> [!NOTE]
>
> * ① 赋值运算符的第一个操作数(左值)必须是变量的形式,第二个操作数可以是任何形式的表达式。
> * ② 赋值运算符的副作用针对第一个操作数。
## 5.2 应用示例
* 示例:
```c
#include <stdio.h>
int main() {
int a = 3;
a += 3; // a = a + 3
printf("a = %d\n", a); // a = 6
int b = 3;
b -= 3; // b = b - 3
printf("b = %d\n", b); // b = 0
int c = 3;
c *= 3; // c = c * 3
printf("c = %d\n", c); // c = 9
int d = 3;
d /= 3; // d = d / 3
printf("d = %d\n", d); // d = 1
int e = 3;
e %= 3; // e = e % 3
printf("e = %d\n", e); // e = 0
return 0;
}
```
# 第六章:位运算符(了解)
## 6.1 概述
* C 语言提供了一些位运算符能够让我们操作二进制位bit
* 常见的位运算符,如下所示。
| 运算符 | 描述 | 操作数个数 | 运算规则 | 副作用 |
| ------ | ---------- | ---------- | ------------------------------------------------------------ | ------ |
| `&` | 按位与 | 2 | 两个二进制位都为 1 ,结果为 1 ,否则为 0 。 | ❎ |
| `\|` | 按位或 | 2 | 两个二进制位只要有一个为 1包含两个都为 1 的情况),结果为 1 ,否则为 0 。 | ❎ |
| `^` | 按位异或 | 2 | 两个二进制位一个为 0 ,一个为 1 ,结果为 1否则为 0 。 | ❎ |
| `~` | 按位取反 | 2 | 将每一个二进制位变成相反值,即 0 变成 1 1 变 成 0 。 | ❎ |
| `<<` | 二进制左移 | 2 | 将一个数的各二进制位全部左移指定的位数,左 边的二进制位丢弃,右边补 0。 | ❎ |
| `>>` | 二进制右移 | 2 | 将一个数的各二进制位全部右移指定的位数,正数左补 0负数左补 1右边丢弃。 | ❎ |
> [!NOTE]
>
> 操作数在进行位运算的时候,以它的补码形式计算!!!
## 6.2 输出二进制位
* 在 C 语言中,`printf` 是没有提供输出二进制位的格式占位符的;但是,我们可以手动实现,以方便后期操作。
* 示例:
```c
#include <stdio.h>
/**
* 获取指定整数的二进制表示
* @param num 整数
* @return 二进制表示的字符串,不包括前导的 '0b' 字符
*/
char* getBinary(int num) {
static char binaryString[33];
int i, j;
for (i = sizeof(num) * 8 - 1, j = 0; i >= 0; i--, j++) {
const int bit = (num >> i) & 1;
binaryString[j] = bit + '0';
}
binaryString[j] = '\0';
return binaryString;
}
int main() {
int a = 17;
int b = -12;
printf("整数 %d 的二进制表示:%s \n", a, getBinary(a));
printf("整数 %d 的二进制表示:%s \n", b, getBinary(b));
return 0;
}
```
## 6.3 按位与
* 按位与 `&` 的运算规则是:如果二进制对应的位上都是 1 才是 1 ,否则为 0 ,即:
* `1 & 1` 的结果是 `1`
* `1 & 0` 的结果是 `0`
* `0 & 1` 的结果是 `0`
* `0 & 0` 的结果是 `0`
* 示例:`9 & 7 = 1`
![](./assets/22.svg)
* 示例:`-9 & 7 = 7`
![](./assets/23.svg)
## 6.4 按位或
* 按位与 `|` 的运算规则是:如果二进制对应的位上只要有 1 就是 1 ,否则为 0 ,即:
* `1 | 1` 的结果是 `1`
* `1 | 0` 的结果是 `1`
* `0 | 1` 的结果是 `1`
* `0 | 0` 的结果是 `0`
* 示例:`9 | 7 = 15`
![](./assets/24.svg)
* 示例:`-9 | 7 = -9`
![](./assets/25.svg)
## 6.5 按位异或
* 按位与 `^` 的运算规则是:如果二进制对应的位上一个为 1 一个为 0 就为 1 ,否则为 0 ,即:
* `1 ^ 1` 的结果是 `0`
* `1 ^ 0` 的结果是 `1`
* `0 ^ 1` 的结果是 `1`
* `0 ^ 0` 的结果是 `0`
> [!NOTE]
>
> 按位异或的场景有:
>
> * ① 交换两个数值:异或操作可以在不使用临时变量的情况下交换两个变量的值。
> * ② 加密或解密:异或操作用于简单的加密和解密算法。
> * ③ 错误检测和校正异或操作可以用于奇偶校验位的计算和检测错误RAID-3 以及以上)。
> * ……
* 示例:`9 ^ 7 = 14`
![](./assets/26.svg)
* 示例:`-9 ^ 7 = -16`
![](./assets/27.svg)
## 6.6 按位取反
* 运算规则:如果二进制对应的位上是 1则结果为 0如果是 0 ,则结果为 1 。
* `~0` 的结果是 `1`
* `~1` 的结果是 `0`
* 示例:`~9 = -10`
![](./assets/28.svg)
* 示例:`~-9 = 8`
![](./assets/29.svg)
## 6.7 二进制左移
* 在一定范围内,数据每向左移动一位,相当于原数据 × 2。正数、负数都适用
* 示例:`3 << 4 = 48` 3 × 2^4
![](./assets/30.svg)
* 示例:`-3 << 4 = -48` -3 × 2 ^4
![](./assets/31.svg)
## 6.8 二进制右移
* 在一定范围内,数据每向右移动一位,相当于原数据 ÷ 2。正数、负数都适用
> [!NOTE]
>
> * ① 如果不能整除,则向下取整。
> * ② 右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。
* 示例:`69 >> 4 = 4` 69 ÷ 2^4
![](./assets/32.svg)
* 示例:`-69 >> 4 = -5` -69 ÷ 2^4
![](./assets/33.svg)
# 第七章:三元运算符(⭐)
## 7.1 概述
* 语法:
```c
条件表达式 ? 表达式1 : 表达式2 ;
```
> [!NOTE]
>
> * 如果条件表达式为非 0 (真),则整个表达式的值是表达式 1 。
> * 如果条件表达式为 0 (假),则整个表达式的值是表达式 2 。
## 7.2 应用示例
* 示例:
```c
#include <stdio.h>
int main() {
int m = 110;
int n = 20;
int result = m > n ? m : n;
printf("result = %d\n", result); // result = 110
return 0;
}
```
# 第八章:运算符的优先级和结合性(⭐)
## 7.1 概述
* 在数学中,如果一个表达式是 `a + b * c` ,我们知道其运算规则就是:先算乘除再算加减。其实,在 C 语言中也是一样的先算乘法再算加减C 语言中乘除的运算符比加减的运算符的优先级要高。
## 7.2 优先级和结合性
* `优先级``结合性`的定义,如下所示:
* ① 所谓的`优先级`:就是当多个运算符出现在同一个表达式中时,先执行哪个运算符。
* ② 所谓的`结合性`:就是当多个相同优先级的运算符出现在同一个表达式中的时候,是从左到右运算,还是从右到左运算。
* `左结合性`:具有相同优先级的运算符将`从左到右`(➡️)进行计算。
* `右结合性`:具有相同优先级的运算符将`从右到左`(⬅️)进行计算。
> [!NOTE]
>
> 技巧:先看`优先级`;如果优先级相同,再看`结合性`。
* C 语言中运算符的优先级有几十个,有的运算符优先级不同,有的运算符优先级相同,如下所示:
| 优先级 | 运算符 | 名称或含义 | 结合方向 |
| ------ | -------------- | ------------------------------------------- | ------------- |
| `0` | `()` | 小括号,最高优先级 | ➡️(从左到右) |
| `1` | `++``--` | 后缀自增和自减,如:`i++``i--` 等 | ➡️(从左到右) |
| | `()` | 小括号,函数调用,如:`sum(1,2)` 等 | |
| | `[]` | 数组下标,如:`arr[0]``arr[1]` 等 | |
| | `.` | 结构体或共用体成员访问 | |
| | `->` | 结构体或共用体成员通过指针访问 | |
| `2` | `++``--` | 前缀自增和自减,如:`++i``--i` 等 | ⬅️(从右到左) |
| | `+` | 一元加运算符,表示操作数的正,如:`+2` 等 | |
| | `-` | 一元减运算符,表示操作数的负,如:`-3` 等 | |
| | `!` | 逻辑非运算符(逻辑运算符) | |
| | `~` | 按位取反运算符(位运算符) | |
| | `typename` | 强制类型转换 | |
| | `*` | 解引用运算符 | |
| | `&` | 取地址运算符 | |
| | `sizeof` | 取大小运算符 | |
| `3` | `/` | 除法运算符(算术运算符) | ➡️(从左到右) |
| | `*` | 乘法运算符(算术运算符) | |
| | `%` | 取模(取余)运算符(算术运算符) | |
| `4` | `+` | 二元加运算符(算术运算符),如:`2 + 3` 等 | ➡️(从左到右) |
| | `-` | 二元减运算符(算术运算符),如:`3 - 2` 等 | |
| `5` | `<<` | 左移位运算符(位运算符) | ➡️(从左到右) |
| | `>>` | 右移位运算符(位运算符) | |
| `6` | `>` | 大于运算符(比较运算符) | ➡️(从左到右) |
| | `>=` | 大于等于运算符(比较运算符) | |
| | `<` | 小于运算符(比较运算符) | |
| | `<=` | 小于等于运算符(比较运算符) | |
| `7` | `==` | 等于运算符(比较运算符) | ➡️(从左到右) |
| | `!=` | 不等于运算符(比较运算符) | |
| `8` | `&` | 按位与运算符(位运算符) | ➡️(从左到右) |
| `9` | `^` | 按位异或运算符(位运算符) | ➡️(从左到右) |
| `10` | `\|` | 按位或运算符(位运算符) | ➡️(从左到右) |
| `11` | `&&` | 逻辑与运算符(逻辑运算符) | ➡️(从左到右) |
| `12` | `\|\|` | 逻辑或运算符(逻辑运算符) | ➡️(从左到右) |
| `13` | `?:` | 三目(三元)运算符 | ⬅️(从右到左) |
| `14` | `=` | 简单赋值运算符(赋值运算符) | ⬅️(从右到左) |
| | `/=` | 除后赋值运算符(赋值运算符) | |
| | `*=` | 乘后赋值运算符(赋值运算符) | |
| | `%=` | 取模后赋值运算符(赋值运算符) | |
| | `+=` | 加后赋值运算符(赋值运算符) | |
| | `-=` | 减后赋值运算符(赋值运算符) | |
| | `<<=` | 左移后赋值运算符(赋值运算符) | |
| | `>>=` | 右移后赋值运算符(赋值运算符) | |
| | `&=` | 按位与后赋值运算符(赋值运算符) | |
| | `^=` | 按位异或后赋值运算符(赋值运算符) | |
| | `\|=` | 按位或后赋值运算符(赋值运算符) | |
| `15` | `,` | 逗号运算符 | ➡️(从左到右) |
> [!WARNING]
>
> * ① 不要过多的依赖运算符的优先级来控制表达式的执行顺序,这样可读性太差,尽量`使用小括号来控制`表达式的执行顺序。
> * ② 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它`分成几步`来完成。
> * ③ 运算符优先级不用刻意地去记忆,总体上:一元运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 > 三元运算符 > 赋值运算符。