# 第一章:前言 ## 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 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 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 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 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 /* 随意给出一个整数,打印显示它的个位数,十位数,百位数的值。 格式如下: 数字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 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 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 // 短路现象 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 // 短路现象 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 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 /** * 获取指定整数的二进制表示 * @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 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] > > * ① 不要过多的依赖运算符的优先级来控制表达式的执行顺序,这样可读性太差,尽量`使用小括号来控制`表达式的执行顺序。 > * ② 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它`分成几步`来完成。 > * ③ 运算符优先级不用刻意地去记忆,总体上:一元运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 > 三元运算符 > 赋值运算符。