1 位运算符
位运算符是对整数的二进制表示(补码形式)进行逐位操作的运算符。以下是主要的位运算符及其功能描述:
运算符 | 描述 | 操作数个数 | 副作用 |
---|---|---|---|
& | 按位与 | 2 | 无 |
| | 按位或 | 2 | 无 |
^ | 按位异或 | 2 | 无 |
~ | 按位取反 | 1 | 无 |
<< | 按位左移 | 2 | 无 |
>> | 按位右移 | 2 | 无 |
1.1 按位与 &
- 运算规则: 对于两个数的每一位,如果两个相应的位都为 1,则结果的该位为 1;否则,结果的该位为 0(遵循 "有 0 为 0,全 1 为 1" 规则)。
- 示例: 5 & 3
- 5 的二进制表示:0101
- 3 的二进制表示:0011
- 按位与操作过程:
bash
0 1 0 1
&
0 0 1 1
-------
0 0 0 1
结果:0001(十进制1)
1.2 按位或 |
- 运算规则: 对于两个数的每一位,如果两个相应的位中至少有一个为 1,则结果的该位为 1;如果两个相应的位都为 0,则结果的该位为 0(遵循 "有 1 为 1,全 0 为 0" 规则)。
- 示例: 5 | 3
- 5 的二进制表示:0101
- 3 的二进制表示:0011
- 按位或操作过程:
bash
0 1 0 1
|
0 0 1 1
-------
0 1 1 1
结果:0111(十进制7)
1.3 按位异或 ^
- 计算规则: 对于两个数的每一位,如果两个相应的位不同(一个为 1,另一个为 0),则结果的该位为 1;如果两个相应的位相同(都为 0 或都为 1),则结果的该位为 0。(遵循 "相同为 0,不同为 1" 规则)。
- 示例: 5 ^ 3
- 5 的二进制表示:0101
- 3 的二进制表示:0011
- 按位异或操作过程:
bash
0 1 0 1
^
0 0 1 1
-------
0 1 1 0
结果:0110(十进制6)
为了清晰展示 8 位二进制数的位运算过程,我们可以使用 char 类型来进行按位与、按位或和异或操作的演示。由于 char 类型通常占用 8 位,非常适合作为位运算的示例载体。程序如下:
cpp
#include <stdio.h>
int main()
{
// 使用 char 类型(通常是 8 位)
char a = 17; // 二进制表示为 0001 0001
char b = -12; // -12 的补码表示为 1111 0100
// 按位与操作 &
// 计算规则:【有 0 为 0,全 1 为 1】
// 0001 0001 -> 17
// &
// 1111 0100 -> -12
// 0001 0000 -> 16
printf("a & b = %d\n", a & b); // 输出为 16
// 按位或操作 |
// 计算规则:【有 1 为 1,全 0 为 0】
// 0001 0001 -> 17
// |
// 1111 0100 -> -12
// 1111 0101 -> -11
printf("a | b = %d\n", a | b); // 输出为 -11
// 按位异或操作 ^
// 计算规则:【相同为 0,不同为 1】
// 0001 0001 -> 17
// ^
// 1111 0100 -> -12
// 1110 0101 -> -27
printf("a ^ b = %d\n", a ^ b); // 输出为 -27
return 0;
}
程序计算过程分析:

程序在 VS Code 中的运行结果如下所示:

1.4 按位取反 ~
- 计算规则: 对一个数的二进制表示中的每一位(包括符号位)进行取反操作,即将所有的 0 变为 1,所有的 1 变为 0。
- 示例: 例如,对于 char 类型(8 位)整数 17
- 其二进制表示为 0001 0001
- 按位取反后变为 1110 1110
- 结果为 -18
cpp
#include <stdio.h>
int main()
{
// 使用 char 类型(通常是 8 位)
char a = 17; // 二进制表示为 0001 0001
char b = -12; // -12 的补码表示为 1111 0100
// 按位取反操作 ~a
// 0001 0001 -> 17
// ~
// 1110 1110 -> -18
printf("~a = %d\n", ~a); // 输出为 -18
// 按位非操作 ~b
// 1111 0100 -> -12
// ~
// 0000 1011 -> 11
printf("~b = %d\n", ~b); // 输出为 11
return 0;
}
程序计算过程分析:

程序在 VS Code 中的运行结果如下所示:

1.5 按位左移 <<
-
功能: 将一个数的二进制表示向左移动指定的位数。左移时,左侧边缘超出的位将被丢弃,而在右侧边缘新增的位将用 0 填充。
-
语法:a << b
- a 是要被左移的数。
- b 是指定左移的位数。
-
计算规则:
- **移动操作:**将 a 的二进制表示(补码)向左移动 b 位。
- 填充规则:
- 左侧边缘超出的位将被丢弃。
- 在右侧边缘新增的位用 0 填充。
-
注意事项:
- 未定义行为: 如果指定左移的位数 b 是负数,则行为是未定义的(Undefined Behavior, UB)。
- 整数溢出: 左移后的值可能超出该整数类型的表示范围,导致整数溢出。
- 数学应用:左移操作通常用于将数乘以 2 的幂次方(假设没有溢出)。
- 无符号整数: 对于无符号整数,左移后的结果将保持为无符号数。
- 有符号整数: 对于有符号整数,左移可能导致符号位的变化,进而影响整数的正负。例如,一个正数左移后符号位变为 1,那么结果将是一个负数。
-
**示例:**5 << 1
- 5 的八位二进制表示为:0000 0101
- 左移 1 位后为 0000 1010,即十进制数 10
- 左移 1 位相当于乘以 2 的 1 次方,即 5 * 2^1 = 10
cpp
#include <stdio.h>
int main()
{
// 演示未定义行为
int a = 17;
int undefined_shift = -1;
int result = a << undefined_shift;
printf("负数移位是未定义行为,示例: %d << %d = %d \n", a, undefined_shift, result); // 输出可能是任意值
// 演示数学应用
int b = 5;
// 左移操作通常用于将数乘以 2 的幂次方(假设没有溢出)
printf("%d << 4 = %d (5 * 2^4 = 80)\n", b, b << 4); // 输出为 80
// 演示无符号整数
unsigned int e = 17;
printf("无符号整数示例: %u << 2 = %u\n", e, e << 2); // 输出为 68
// 演示有符号整数
int f = -12;
printf("有符号整数示例: %d << 2 = %d\n", f, f << 2); // 输出为 -48
return 0;
}
程序计算过程分析:

程序在 VS Code 中的运行结果如下所示:

1.6 按位右移 >>
- 功能: 将一个数的二进制表示向右移动指定的位数。右移时,右侧边缘超出的位将被丢弃,而左侧边缘新增的位根据整数的类型(有符号还是无符号)有不同的填充规则。
- 语法:a >> b
- a 是要被右移的数。
- b 是指定右移的位数。
- 计算规则:
- **移动操作:**将 a 的二进制表示(补码)向右移动 b 位。
- 填充规则:
- 右侧边缘超出的位将被丢弃。
- 无符号整数: 表现为逻辑右移,即左侧用 0 填充。
- 有符号整数: 在左侧边缘新增的位的填充规则依赖于编译器的实现,通常是算术右移,即用符号位填充,但这不是标准强制要求的。
- 算术右移 (Arithmetic Right Shift):
- 功能: 在右移时,左侧空出的位用符号位填充。
- 效果: 保持数值的符号不变。
- 适用: 用于有符号整数类型(如 int、char 等)。
- 示例: -10 >> 1
- -10 的二进制表示为 1111 0110(假设 8 位系统)
- 算术右移 1 位后为 1111 1011,即 -5
- 逻辑右移 (Logical Right Shift):
- 功能: 在右移时,左侧空出的位用 0 填充。
- 效果: 不保持数值的符号,适用于无符号数。
- 适用: 用于无符号整数类型(如 unsigned int、unsigned char 等)。
- 示例: 10 >> 1
- 10 的二进制表示为 0000 1010
- 逻辑右移 1 位后为 0000 0101,即 5
- 注意事项:
- 未定义行为: 如果指定右移的位数 b 是负数,则行为是未定义的(Undefined Behavior, UB)。
- 数学应用:对于无符号整数,右移操作通常用于将数除以 2 的幂次方(结果总是向下取整)。
- 实现依赖: 对于有符号整数,C 标准没有规定必须使用算术右移还是逻辑右移,这取决于编译器的具体实现。
cpp
#include <stdio.h>
int main()
{
// 演示未定义行为
int a = 256;
int undefined_shift = -2;
int result = a >> undefined_shift;
printf("负数移位是未定义行为,示例: %d >> %d = %d \n", a, undefined_shift, result); // 输出可能是任意值
// 演示无符号整数的逻辑右移
unsigned int b = 17;
printf("无符号整数示例: %u >> 3 = %u\n", b, b >> 3); // 输出为 2
// 演示有符号整数的算术右移
int c = -12;
printf("有符号整数示例: %d >> 3 = %d\n", c, c >> 3); // 输出为 -2
// 演示算术右移和逻辑右移的区别
unsigned int d = 128;
printf("无符号整数(逻辑右移)示例: %u >> 1 = %u\n", d, d >> 1); // 输出为 64
int e = -128;
printf("有符号整数(算术右移)示例: %d >> 1 = %d\n", e, e >> 1); // 输出为 -64
// 演示数学应用
// 对于无符号整数,右移操作通常用于将数除以 2 的幂次方(结果总是向下取整)
int f = 85;
printf("%d >> 3 = %d (85 / 2^3 = 10)\n", f, f >> 3); // 输出为 10 (85/8 = 10.625,向下取整为 10)
// 对于有符号整数,右移操作的行为依赖于编译器的实现
// 需看编译器使用算术右移还是逻辑右移
int g = -80;
printf("%d >> 3 = %d (-80 / 2^3 = -10)\n", g, g >> 3); // 输出为 -10 (-80/8 = -10)
return 0;
}
程序计算过程分析:

程序在 VS Code 中的运行结果如下所示:

1.7 位运算符的意义与应用
在 C 语言中,位运算符(包括按位与 &、按位或 |、按位异或 ^、按位非 ~、左移 << 和右移 >>)提供了对整数类型底层二进制位的直接操作能力。这类操作在系统级或嵌入式编程中尤为重要,特别是在需要精确控制硬件寄存器、标志位或其他底层资源的场景中。
位运算的核心应用之一是对特定比特位的置位(设为 1)和复位(设为 0),而不会影响其他位的状态。例如:
-
将第 n 位置为 1:使用按位或操作结合掩码 (1 << n),即执行 x |= (1 << n) ;
-
(1 << n)****:
-
这是创建一个掩码(mask),它的作用是生成一个只有第 n 位为 1,其余都为 0 的数。例如:
-
1 << 0 → 0b00000001 (第 0 位为 1)
-
1 << 1 → 0b00000010 (第 1 位为 1)
-
1 << 3 → 0b00001000 (第 3 位为 1)
-
-
注意:通常从右往左数位,最低位是第 0 位。
-
-
x | (1 << n):
-
用原来的值 x 和这个掩码做按位或操作。
-
因为 OR 的规则是:只要有 1 就为 1。
-
所以无论原来第 n 位是 0 还是 1,经过 OR 后都会变成 1。
-
-
-
将第 n 位置为 0:使用按位与操作结合取反后的掩码 ~(1 << n),即执行 x &= ~(1 << n) ;
-
(1 << n):
- 同上,先生成一个只有第 n 位为 1 的掩码。
-
~(1 << n):
-
对这个掩码取反。原来是 0b00001000,取反后变成 0b11110111。
-
这样我们就得到了一个除了第 n 位是 0,其他都是 1 的掩码。
-
-
x & ~(1 << n):
-
将 x 和这个新掩码进行按位与操作。
-
AND 的规则是:只要有一个是 0,结果就是 0。
-
所以:
-
第 n 位一定是 0(因为它和掩码中的 0 做 AND)
-
其他位保持原样(因为掩码中是 1,所以保留原来的值)
-
-
-
这些操作允许程序员高效地修改寄存器中的某些位,从而实现对硬件状态(如高低电平控制)的精细管理。由于位运算直接作用于数据的二进制表示,它们通常具有较高的执行效率,适合对性能敏感或资源受限的环境。
2 赋值运算符
在 C 语言中,赋值运算符用于将一个值赋予变量或表达式,它们是编程中非常基础且重要的操作。以下是 C 语言中常见的赋值运算符及其用法。
| 运算符 | 描述 | 操作数个数 | 表达式的值 | 副作用 |
| = | 赋值 | 2 | 左边操作数的值(赋值后的值) | 有,左边操作数的值被更新 |
| += | 相加赋值 | 2 | 左边操作数的值(相加后的值) | 有,左边操作数的值被更新 |
| -= | 相减赋值 | 2 | 左边操作数的值(相减后的值) | 有,左边操作数的值被更新 |
| *= | 相乘赋值 | 2 | 左边操作数的值(相乘后的值) | 有,左边操作数的值被更新 |
| /= | 相除赋值 | 2 | 左边操作数的值(相除后的值) | 有,左边操作数的值被更新 |
| %= | 取余赋值 | 2 | 左边操作数的值(取余后的值) | 有,左边操作数的值被更新 |
| <<= | 左移赋值 | 2 | 左边操作数的值(左移后的值) | 有,左边操作数的值被更新 |
| >>= | 右移赋值 | 2 | 左边操作数的值(右移后的值) | 有,左边操作数的值被更新 |
| &= | 按位与赋值 | 2 | 左边操作数的值(按位与后的值) | 有,左边操作数的值被更新 |
| ^= | 按位异或赋值 | 2 | 左边操作数的值(按位异或后的值) | 有,左边操作数的值被更新 |
|= | 按位或赋值 | 2 | 左边操作数的值(按位或后的值) | 有,左边操作数的值被更新 |
---|
2.1 左值和右值
- 左值(Lvalue):左值是一个具有确定内存位置的变量或表达式,可以出现在赋值操作的左侧。例如:变量名、数组元素、结构体成员等。
- 右值(Rvalue):右值是一个表示值的表达式,但没有明确的内存位置。右值通常是一个常量、算术表达式的结果、函数调用返回的结果等。
2.2 注意事项
-
赋值运算符的第一个操作数(左值)必须是变量的形式,第二个操作数可以是任何形式的表达式。
-
左值必须可修改: 赋值操作要求左侧必须是一个左值,即一个可以存储新值的内存位置。
-
**右值提供值:**赋值操作的右侧可以是一个右值,它提供了要赋给左值的具体值。
-
示例:
- 正确用法:a = b + 25;(a 是左值,b + 25 是右值)
- 错误用法:b + 25 = a;(尝试将右值 b + 25 用作左值,这是不允许的)
- 编译错误结果,如下图所示:

2.3 复合赋值运算符的使用
复合赋值运算符(如 +=、-= 等)的用法是将等号右边的表达式首先计算为一个整体的值,然后将这个值与等号左边的变量进行相应的运算,并将结果赋值回该变量。
cpp
a += b + 2;
等价于
a = a + (b + 2);
2.4 案例演示
cpp
#include <stdio.h>
int main()
{
int a = 10, b = 20, c = 30, d = 4;
// 简单的赋值
a = 5;
printf("a = %d\n", a); // a = 5
// 加法赋值
c += 3; // c = c + 3 = 30 + 3
printf("c = %d\n", c); // c = 33
// 减法赋值
c -= b; // c = c - b = 33 - 20
printf("c = %d\n", c); // c = 13
// 乘法赋值
a *= 2; // a = a * 2 = 5 * 2
printf("a = %d\n", a); // a = 10
// 除法赋值
b /= 2; // b = b / 2 = 20 / 2
printf("b = %d\n", b); // b = 10
// 取模赋值
c %= 3; // c = c % 3 = 13 % 3
printf("c = %d\n", c); // c = 1
// 连等写法
int e = 12, f;
f = e *= a; // f = e * a = 12 * 10
// 先计算 e *= a,然后将结果赋值给 f
printf("e = %d\n", e); // e = 120
printf("f = %d\n", f); // f = 120
// 左移赋值
d <<= 2; // d = d << 2 = 4 << 2
printf("d = %d\n", d); // d = 16
// 右移赋值
d >>= 1; // d = d >> 1 = 16 >> 1
printf("d = %d\n", d); // d = 8
// 按位与赋值
a &= 1; // a = a & 1 = 10 & 1
printf("a = %d\n", a); // a = 0
// 按位异或赋值
b ^= 3; // b = b ^ 3 = 10 ^ 3
printf("b = %d\n", b); // b = 9
// 按位或赋值
b |= 4; // b = b | 4 = 9 | 4
printf("b = %d\n", b); // b = 13
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2.5 复合赋值运算符表达式的值
在 C 语言中,复合赋值运算符(如 +=、-=、*= 等)不仅简化了赋值操作,还具有特定的表达式值。复合赋值运算符组成的表达式,其值为赋值后的左边操作数的值。以下通过一个案例来演示这一特性:
cpp
#include <stdio.h>
int main()
{
int n = 5;
// 使用复合赋值运算符 +=,并打印表达式的结果
printf("n += 3 的结果是: %d\n", n += 3);
// 执行过程:
// 1. 计算 n + 3 = 8
// 2. 将 8 赋给 n
// 3. 表达式 n += 3 的值为赋值后的 n 的值,即 8
// 输出:n += 3 的结果是: 8
// 验证 n 的值
printf("此时 n 的值是: %d\n", n); // 输出:此时 n 的值是: 8
return 0;
}
- 初始时,n 的值为 5。
- 执行 n += 3 时,先计算 n + 3 的值(即 8),然后将该值赋给 n。
- 表达式 n += 3 的值为赋值后的 n 的值,即 8,因此 printf 函数会输出 8。
- 最后,再次打印 n 的值,确认 n 已经被更新为 8。
程序在 VS Code 中的运行结果如下所示:

2.6 连等写法
C 语言支持连等写法,即在一个表达式中连续进行多个赋值操作。连等写法的赋值顺序是从右向左进行的。以下通过一个案例来演示连等写法:
cpp
#include <stdio.h>
int main()
{
int x, y;
// 使用连等写法 x = y = 99
x = y = 99;
// 执行过程解析:
// 1. y = 99; 先执行,将 y 赋值为 99
// 2. 然后 x = y; 执行,将 x 赋值为 y 的值,即 99
// 打印 x 和 y 的值
printf("x = %d, y = %d\n", x, y); // 输出:x = 99, y = 99
return 0;
}
- 在 x = y = 99; 这个连等写法中,赋值操作是从右向左进行的。
- 首先,99 被赋给 y,此时 y 的值为 99。
- 然后,y 的值(即 99)被赋给 x,此时 x 的值也为 99。
- 最终,printf 函数输出 x = 99, y = 99,验证了连等写法的执行顺序和赋值过程。
程序在 VS Code 中的运行结果如下所示:

3 三元运算符
3.1 基本语法
三元运算符(也称为条件运算符)的基本语法如下:
cpp
条件表达式 ? 表达式1 : 表达式2;
- **条件表达式:**一个逻辑表达式,用于判断其真假。
- **表达式1:**如果条件表达式为真(非 0),则整个三元运算符的值为表达式 1 的值。
- **表达式2:**如果条件表达式为假(0),则整个三元运算符的值为表达式 2 的值。
3.2 案例演示
cpp
#include <stdio.h>
int main()
{
int a = 99;
int b = 99;
// 使用三元运算符来决定 res 的值
int res = a > b ? a++ : b--;
// 因为 a 不大于 b,所以执行 b--,并将结果赋值给 res
// 注意:b-- 是后缀自减,意味着先返回 b 的当前值(99),然后再将 b 减 1
// res 被赋值为 99,然后 b 变为 98
printf("a=%d\n", a); // 输出 a=99,因为 a 没有被改变
printf("b=%d\n", b); // 输出 b=98,因为 b 在前面的条件运算符中自减了
printf("res=%d\n", res); // 输出 res=99,因为 res 被赋值为 b 自减之前的值
float n1 = a > b ? 1.1 : 1.2; // 条件表达式为真,整个表达式的值是表达式1:1.1
// 注意:由于 b 在前面的条件运算符中已经被自减,所以这里 b 的值是 98
printf("n1=%.2f\n", n1); // 输出 n1=1.10
return 0;
}
程序在 VS Code 中的运行结果如下所示:

3.3 案例:计算两个数的最大值
cpp
#include <stdio.h>
int main()
{
int a = 10;
int b = 100;
// 使用三元运算符计算 a 和 b 中的最大值
int max = a > b ? a : b;
printf("a 和 b 中最大的数字:%d", max); // 输出:a 和 b 中最大的数字:100
return 0;
}
程序在 VS Code 中的运行结果如下所示:

3.4 案例:计算三个数的最大值
cpp
#include <stdio.h>
int main()
{
int a = 10;
int b = 100;
int c = 199;
// 1. 使用两次三元运算符来计算 a、b 和 c 中的最大值
int max_a_b = a > b ? a : b;
int max_a_b_c = max_a_b > c ? max_a_b : c;
// 输出 a、b 和 c 中最大的数字
printf("a、b、c 中最大的数字:%d\n", max_a_b_c); // 输出:a、b、c 中最大的数字:199
// 2. 使用嵌套的三元运算符来找出 a、b、c 中的最大值
// 首先比较 a 和 b,然后将较大的值与 c 比较
int max = (a > b ? a : b) > c ? (a > b ? a : b) : c;
// 输出 a、b 和 c 中最大的数字
printf("a、b、c 中最大的数字:%d\n", max); // 输出:a、b、c 中最大的数字:199
// 3. 也可以简化为下面的写法:
max = (a > b ? (a > c ? a : c) : (b > c ? b : c));
// 输出 a、b 和 c 中最大的数字
printf("a、b、c 中最大的数字:%d\n", max); // 输出:a、b、c 中最大的数字:199
// 4. 也可以使用 if-else 语句来找出 a、b、c 中的最大值
// 后续在学习 if-else 语句时会详细讲解
return 0;
}
程序在 VS Code 中的运行结果如下所示:

4 逗号运算符
4.1 基本语法
逗号运算符(,)是 C 语言中一个简单但强大的运算符,用于将多个表达式连接在一起。它的基本语法如下:
cpp
表达式1, 表达式2, 表达式3, ..., 表达式N;
- **表达式1, 表达式2, ..., 表达式N:**这些是逗号运算符连接的多个表达式。
4.2 表达式的值
逗号运算符的特性如下:
- 求值顺序: 逗号运算符从左到右依次计算每个表达式的值。
- 表达式的值: 整个逗号表达式的值为最后一个表达式的值。
4.3 案例演示
cpp
#include <stdio.h>
int main()
{
int a = 5, b = 10, c = 15;
// 使用逗号运算符依次执行多个表达式
int result = (a = b, b = c, c = a + b);
// 执行过程解析:
// 1. a = b,将 b 的值赋给 a,此时 a = 10, b = 10, c = 15
// 2. b = c,将 c 的值赋给 b,此时 a = 10, b = 15, c = 15
// 3. c = a + b,将 a 和 b 的和赋给 c,此时 a = 10, b = 15, c = 25
// 最后,result 被赋值为 c 的值,即 25
// 打印结果
printf("a = %d, b = %d, c = %d\n", a, b, c); // 输出:a = 10, b = 15, c = 25
printf("result = %d\n", result); // 输出:result = 25
return 0;
}
程序在 VS Code 中的运行结果如下所示:

4.4 使用场景
逗号运算符通常用于以下场景:
- **简化代码:**在需要执行多个操作但不想使用多行代码时,可以使用逗号运算符。
- 循环和条件语句: 在 for 循环的初始化或更新部分,或者在需要多个条件判断的地方,可以使用逗号运算符。
4.5 注意事项
- **可读性:**虽然逗号运算符可以简化代码,但过度使用可能会降低代码的可读性。
- **优先级:**逗号运算符的优先级较低,因此在复杂的表达式中,建议使用括号来明确运算顺序。
5 运算符优先级
5.1 运算符优先级表格说明
优先级 | 运算符 | 名称或含义 | 结合方向 |
---|---|---|---|
1 | [] | 数组下标 | 左到右 |
() | 函数调用、圆括号 | ||
. | 成员选择(对象) | ||
-> | 成员选择(指针) | ||
2 | -(单目) | 负号运算符 | 右到左 |
(类型) | 强制类型转换 | ||
++ | 自增运算符 | ||
-- | 自减运算符 | ||
*(单目) | 取值运算符(解引用) | ||
&(单目) | 取地址运算符 | ||
! | 逻辑非运算符 | ||
~ | 按位取反运算符 | ||
sizeof | 长度运算符 | ||
3 | / | 除 | 左到右 |
*(双目) | 乘 | ||
% | 余数(取模) | ||
4 | + | 加 | 左到右 |
-(双目) | 减 | ||
5 | << | 左移 | 左到右 |
>> | 右移 | ||
6 | > | 大于 | 左到右 |
>= | 大于等于 | ||
< | 小于 | ||
<= | 小于等于 | ||
7 | == | 等于 | 左到右 |
!= | 不等于 | ||
8 | & | 按位与 | 左到右 |
9 | ^ | 按位异或 | 左到右 |
10 | | | 按位或 | 左到右 |
11 | && | 逻辑与 | 左到右 |
12 | || | 逻辑或 | 左到右 |
13 | ?: | 条件运算符 | 右到左 |
14 | = | 赋值运算符 | 右到左 |
/= | 除后赋值 | ||
*= | 乘后赋值 | ||
%= | 取模后赋值 | ||
+= | 加后赋值 | ||
-= | 减后赋值 | ||
<<= | 左移后赋值 | ||
>>= | 右移后赋值 | ||
&= | 按位与后赋值 | ||
^= | 按位异或后赋值 | ||
|= | 按位或后赋 | ||
15 | , | 逗号运算符 | 左到右 |
- 优先级记忆: 虽然运算符优先级表看起来复杂,但通常不需要刻意记忆所有优先级。一般来说,一元运算符(如 !、-(单目)、++、-- 等)的优先级高于算术运算符,算术运算符的优先级高于关系运算符,关系运算符的优先级高于逻辑运算符,逻辑运算符的优先级高于三元运算符,三元运算符的优先级高于赋值运算符。
- 常用优先级关系:自增自减 > 逻 辑非 ! > 算术运算符 > 关系运算符 > 逻辑与 && > 逻辑或 || > 条件运算符 ?: > 赋值运算符。
- 使用括号: 如果对运算符的优先级不确定,或者为了代码的可读性,可以直接使用括号来明确运算顺序。括号可以覆盖任何运算符的优先级,确保表达式按照预期的方式计算。
5.2 复杂表达式的计算分析
表达式 5 > 3 && 8 < 4 - !0 的计算过程分析
**问题:**对于表达式 5 > 3 && 8 < 4 - !0 的最终值是多少?计算过程是怎样的?
计算过程:
- **短路性质:**首先计算 && 左边的表达式 5 > 3,其逻辑值为 1。
- 右边表达式: 计算 && 右边的表达式 8 < 4 - !0。
- 非运算:!0 的逻辑值为 1。
- **算术运算:**4 - 1 的结果为 3。
- **关系运算:**8 < 3 的逻辑值为 0。
- **逻辑与运算:**整个表达式的值为 1 && 0,最终逻辑值为 0。
表达式 a + b < c && b == c && a || b + c && b + c 的计算过程分析
**问题:**若 a = 2,b = 3,c = 4,表达式 a + b < c && b == c && a || b + c && b + c 的计算过程是怎样的?值为多少?
计算过程:
- **短路性质:**首先计算 a + b < c,即 2 + 3 < 4,其逻辑值为 0。
- **短路效果:**b == c && a 不会执行,|| 左边的值为 0。
- 右边表达式: 计算 b + c && b + c。
- **算术运算:**b + c 的结果为 7。
- **逻辑与运算:**7 && 7 的逻辑值为 1。
- **逻辑或运算:**0 || 1 的逻辑值为 1。
表达式 (m = a > b) && (n = c > d) 的计算过程分析
**问题:**设有 int a = 1, b = 2, c = 3, d = 4, m = 2, n = 2;执行 (m = a > b) && (n = c > d) 后 m 和 n 的值分别是多少?表达式的结果是多少?
计算过程:
- 短路性质: 首先计算(m = a > b)。
- **关系比较:**a > b,即 1 > 2,其逻辑值为 0。
- **赋值运算:**m = 0。
- 短路效果:(n = c > d) 不执行。
- 最终结果:
- m 的值为 0。
- n 的值为 2。
- 整个表达式的结果为 0。
总结与建议:
- **避免复杂表达式:**尽量简化表达式。
- **明确执行顺序:**使用小括号明确执行顺序,避免依赖运算符优先级。
- **拆分表达式:**复杂表达式拆分为多个步骤,提高可读性和可维护性。
6 编程练习
6.1 会员折扣
某商店对会员提供折扣:
- 消费满 100 元,打 9 折
- 消费满 200 元,打 8 折
- 否则,无折扣
编写一个程序,根据消费金额计算最终价格。
cpp
#include <stdio.h>
int main()
{
float amount; // 消费金额
printf("请输入消费金额:");
scanf("%f", &amount); // 输入消费金额
float final_amount = (amount >= 200) ? amount * 0.8 : (amount >= 100) ? amount * 0.9
: amount;
printf("最终价格:%.2f 元\n", final_amount);
return 0;
}
程序在 VS Code 中的运行结果如下所示:

6.2 多任务处理
假设你在管理一个任务列表,每个任务有 ID、优先级和状态。编写一个程序,更新任务的状态和优先级,并输出更新后的信息。
cpp
#include <stdio.h>
int main()
{
int task_id = 101, priority = 3, status = 0;
// 更新任务优先级和状态
int updated = (priority = 5, status = 1);
printf("任务 ID:%d,优先级:%d,状态:%d\n", task_id, priority, status);
return 0;
}
程序在 VS Code 中的运行结果如下所示:

6.3 灯的开关状态
你有一个 8 位的整数表示 8 个灯的状态(1 表示开,0 表示关)。初始状态为所有灯关闭。编写一个程序,打开第 2 和第 7 个灯,并关闭第 4 个灯(如果它是开的)。
cpp
#include <stdio.h>
int main()
{
unsigned char lights = 0b00000000; // 所有灯关
// 打开第 2 和第 7 个灯
lights |= (1 << 1) | (1 << 6);
// 关闭第 4 个灯(如果它是开的)
lights &= ~(1 << 3);
return 0;
}
6.4 权限检查
在一个系统中,权限使用一个整数的二进制位表示。例如,0000 1010 表示用户有权限 2 和 4(从右到左,最低位为权限 1)。编写一个程序,给用户添加权限 3,并检查用户是否有权限 4。
cpp
#include <stdio.h>
int main()
{
unsigned char permissions = 0b00001000; // 初始权限
// 添加权限 3
permissions |= (1 << 2);
// 检查是否有权限 4
int has_permission_4 = (permissions & (1 << 3)) != 0;
// 解释上面这行代码
// (permissions & (1 << 3)) != 0
// permissions & (1 << 3) 是将 permissions 的第 3 位与 1 进行按位与操作,如果第 3 位为 1,则结果为 1,否则为 0。
// != 0 是判断结果是否不等于 0,如果不等于 0,则表示第 3 位为 1,即有权限 4,否则没有权限 4。
// 加上 != 0 使得代码更加规范,因为 (permissions & (1 << 3)) 的结果是一个整数,而不是一个布尔值。
printf("用户是否有权限 4?%s\n", has_permission_4 ? "有" : "无");
return 0;
}
程序在 VS Code 中的运行结果如下所示:

6.5 温度更新
假设你有一个初始温度值为 25 摄氏度。由于天气变化,温度变化如下:
- 上午升温 3 度。
- 下午降温 2 度。
- 晚上升温 1 度。
编写一个程序,计算晚上的最终温度。
cpp
#include <stdio.h>
int main()
{
float temperature = 25;
temperature += 3; // 上午升温
temperature -= 2; // 下午降温
temperature += 1; // 晚上升温
printf("晚上的最终温度:%.2f 摄氏度\n", temperature);
return 0;
}
程序在 VS Code 中的运行结果如下所示:
