19 C 语言位运算、赋值、条件、逗号运算符详解:涵盖运算符优先级与复杂表达式计算过程分析
1 位运算符
位运算符是对整数的二进制表示(补码形式)进行逐位操作的运算符。以下是主要的位运算符及其功能描述:
运算符 | 描述 | 操作数个数 | 副作用 |
---|---|---|---|
& | 按位与 | 2 | 无 |
| | 按位或 | 2 | 无 |
^ | 按位异或 | 2 | 无 |
~ | 按位取反 | 1 | 无 |
<< | 按位左移 | 2 | 无 |
>> | 按位右移 | 2 | 无 |
1.1 按位与 &
- 运算规则:对于两个数的每一位,如果两个相应的位都为 1,则结果的该位为 1;否则,结果的该位为 0(遵循 “有 0 为 0,全 1 为 1” 规则)。
- 示例:5 & 3
- 5 的二进制表示:0101
- 3 的二进制表示:0011
- 按位与操作过程:
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
- 按位或操作过程:
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
- 按位异或操作过程:
0 1 0 1
^ 0 0 1 1 ------- 0 1 1 0结果:0110(十进制6)
为了清晰展示 8 位二进制数的位运算过程,我们可以使用 char 类型来进行按位与、按位或和异或操作的演示。由于 char 类型通常占用 8 位,非常适合作为位运算的示例载体。程序如下:
#include <stdio.h>int main()
{// 使用 char 类型(通常是 8 位)char a = 17; // 二进制表示为 0001 0001char b = -12; // -12 的补码表示为 1111 0100// 按位与操作 &// 计算规则:【有 0 为 0,全 1 为 1】// 0001 0001 -> 17// &// 1111 0100 -> -12// 0001 0000 -> 16printf("a & b = %d\n", a & b); // 输出为 16// 按位或操作 |// 计算规则:【有 1 为 1,全 0 为 0】// 0001 0001 -> 17// |// 1111 0100 -> -12// 1111 0101 -> -11printf("a | b = %d\n", a | b); // 输出为 -11// 按位异或操作 ^// 计算规则:【相同为 0,不同为 1】// 0001 0001 -> 17// ^// 1111 0100 -> -12// 1110 0101 -> -27printf("a ^ b = %d\n", a ^ b); // 输出为 -27return 0;
}
程序计算过程分析:
程序在 VS Code 中的运行结果如下所示:
1.4 按位取反 ~
- 计算规则:对一个数的二进制表示中的每一位(包括符号位)进行取反操作,即将所有的 0 变为 1,所有的 1 变为 0。
- 示例:例如,对于 char 类型(8 位)整数 17
- 其二进制表示为 0001 0001
- 按位取反后变为 1110 1110
- 结果为 -18
#include <stdio.h>int main()
{// 使用 char 类型(通常是 8 位)char a = 17; // 二进制表示为 0001 0001char b = -12; // -12 的补码表示为 1111 0100// 按位取反操作 ~a// 0001 0001 -> 17// ~// 1110 1110 -> -18printf("~a = %d\n", ~a); // 输出为 -18// 按位非操作 ~b// 1111 0100 -> -12// ~// 0000 1011 -> 11printf("~b = %d\n", ~b); // 输出为 11return 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
#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); // 输出为 -48return 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 标准没有规定必须使用算术右移还是逻辑右移,这取决于编译器的具体实现。
#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); // 输出为 64int 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 复合赋值运算符的使用
复合赋值运算符(如 +=、-= 等)的用法是将等号右边的表达式首先计算为一个整体的值,然后将这个值与等号左边的变量进行相应的运算,并将结果赋值回该变量。
a += b + 2;
等价于
a = a + (b + 2);
2.4 案例演示
#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 + 3printf("c = %d\n", c); // c = 33// 减法赋值c -= b; // c = c - b = 33 - 20printf("c = %d\n", c); // c = 13// 乘法赋值a *= 2; // a = a * 2 = 5 * 2printf("a = %d\n", a); // a = 10// 除法赋值b /= 2; // b = b / 2 = 20 / 2printf("b = %d\n", b); // b = 10// 取模赋值c %= 3; // c = c % 3 = 13 % 3printf("c = %d\n", c); // c = 1// 连等写法int e = 12, f;f = e *= a; // f = e * a = 12 * 10// 先计算 e *= a,然后将结果赋值给 fprintf("e = %d\n", e); // e = 120printf("f = %d\n", f); // f = 120// 左移赋值d <<= 2; // d = d << 2 = 4 << 2printf("d = %d\n", d); // d = 16// 右移赋值d >>= 1; // d = d >> 1 = 16 >> 1printf("d = %d\n", d); // d = 8// 按位与赋值a &= 1; // a = a & 1 = 10 & 1printf("a = %d\n", a); // a = 0// 按位异或赋值b ^= 3; // b = b ^ 3 = 10 ^ 3printf("b = %d\n", b); // b = 9// 按位或赋值b |= 4; // b = b | 4 = 9 | 4printf("b = %d\n", b); // b = 13return 0;
}
程序在 VS Code 中的运行结果如下所示:
2.5 复合赋值运算符表达式的值
在 C 语言中,复合赋值运算符(如 +=、-=、*= 等)不仅简化了赋值操作,还具有特定的表达式值。复合赋值运算符组成的表达式,其值为赋值后的左边操作数的值。以下通过一个案例来演示这一特性:
#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 的值是: 8return 0;
}
- 初始时,n 的值为 5。
- 执行 n += 3 时,先计算 n + 3 的值(即 8),然后将该值赋给 n。
- 表达式 n += 3 的值为赋值后的 n 的值,即 8,因此 printf 函数会输出 8。
- 最后,再次打印 n 的值,确认 n 已经被更新为 8。
程序在 VS Code 中的运行结果如下所示:
2.6 连等写法
C 语言支持连等写法,即在一个表达式中连续进行多个赋值操作。连等写法的赋值顺序是从右向左进行的。以下通过一个案例来演示连等写法:
#include <stdio.h>int main()
{int x, y;// 使用连等写法 x = y = 99x = 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 = 99return 0;
}
- 在 x = y = 99; 这个连等写法中,赋值操作是从右向左进行的。
- 首先,99 被赋给 y,此时 y 的值为 99。
- 然后,y 的值(即 99)被赋给 x,此时 x 的值也为 99。
- 最终,printf 函数输出 x = 99, y = 99,验证了连等写法的执行顺序和赋值过程。
程序在 VS Code 中的运行结果如下所示:
3 三元运算符
3.1 基本语法
三元运算符(也称为条件运算符)的基本语法如下:
条件表达式 ? 表达式1 : 表达式2;
- 条件表达式:一个逻辑表达式,用于判断其真假。
- 表达式1:如果条件表达式为真(非 0),则整个三元运算符的值为表达式 1 的值。
- 表达式2:如果条件表达式为假(0),则整个三元运算符的值为表达式 2 的值。
3.2 案例演示
#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 变为 98printf("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 的值是 98printf("n1=%.2f\n", n1); // 输出 n1=1.10return 0;
}
程序在 VS Code 中的运行结果如下所示:
3.3 案例:计算两个数的最大值
#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 中最大的数字:100return 0;
}
程序在 VS Code 中的运行结果如下所示:
3.4 案例:计算三个数的最大值
#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 语言中一个简单但强大的运算符,用于将多个表达式连接在一起。它的基本语法如下:
表达式1, 表达式2, 表达式3, ..., 表达式N;
- 表达式1, 表达式2, ..., 表达式N:这些是逗号运算符连接的多个表达式。
4.2 表达式的值
逗号运算符的特性如下:
- 求值顺序:逗号运算符从左到右依次计算每个表达式的值。
- 表达式的值:整个逗号表达式的值为最后一个表达式的值。
4.3 案例演示
#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 = 25printf("result = %d\n", result); // 输出:result = 25return 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 折
- 否则,无折扣
编写一个程序,根据消费金额计算最终价格。
#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、优先级和状态。编写一个程序,更新任务的状态和优先级,并输出更新后的信息。
#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 个灯(如果它是开的)。
#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。
#include <stdio.h>int main()
{unsigned char permissions = 0b00001000; // 初始权限// 添加权限 3permissions |= (1 << 2);// 检查是否有权限 4int 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 度。
编写一个程序,计算晚上的最终温度。
#include <stdio.h>int main()
{float temperature = 25;temperature += 3; // 上午升温temperature -= 2; // 下午降温temperature += 1; // 晚上升温printf("晚上的最终温度:%.2f 摄氏度\n", temperature);return 0;
}
程序在 VS Code 中的运行结果如下所示:
相关文章:
19 C 语言位运算、赋值、条件、逗号运算符详解:涵盖运算符优先级与复杂表达式计算过程分析
1 位运算符 位运算符是对整数的二进制表示(补码形式)进行逐位操作的运算符。以下是主要的位运算符及其功能描述: 运算符描述操作数个数副作用&按位与2无|按位或2无^按位异或2无~按位取反1无<<按位左移2无>>按位右移2无 1.1…...
ubuntu下实时检测机械硬盘和固态硬盘温度
sudo apt update sudo apt install smartmontools然后,使用smartctl命令查看硬盘的详细信息,包括温度: sudo smartctl -a /dev/sda实时监控硬盘温度 虽然smartctl不能直接实时显示温度,你可以使用watch命令结合smartctl来定期查…...
游戏开发实战(三):Python复刻「崩坏星穹铁道」嗷呜嗷呜事务所---源码级解析该小游戏背后的算法与设计模式【纯原创】
文章目录 奇美拉类摸鱼仔,负能量,真老实,小坏蛋,压力怪治愈师小团体画饼王平凡王坏脾气抗压包请假狂请假王内卷王受气包跑路侠看乐子背锅侠抢功劳急先锋说怪话帮倒忙小夸夸工作狂职业经理严酷恶魔职场清流 开始工作吧小奇美拉没想…...
DeepSpeed简介及加速模型训练
DeepSpeed是由微软开发的开源深度学习优化框架,专注于大规模模型的高效训练与推理。其核心目标是通过系统级优化技术降低显存占用、提升计算效率,并支持千亿级参数的模型训练。 官网链接:deepspeed 训练代码下载:git代码 一、De…...
AIGC降重工具
使用 PyQt5 和 Python-docx 实现AIGC降重工具 在日常工作中,文档处理是一个常见的任务,尤其是对于需要批量处理文档内容的场景。今天,我将分享一个使用 PyQt5 和 Python-docx 库实现的简单文档处理工具。这个工具可以帮助用户选择文档文件&a…...
PYTHON训练营DAY31
项目拆分 src/data/load_data.py # -*- coding: utf-8 -*- import sys import io # 设置标准输出为 UTF-8 编码 sys.stdout io.TextIOWrapper(sys.stdout.buffer, encodingutf-8) import pandas as pddef load_data(file_path: str) -> pd.DataFrame:"""加…...
使用VGG-16模型来对海贼王中的角色进行图像分类
动漫角色识别是计算机视觉的典型应用场景,可用于周边商品分类、动画制作辅助等。 这个案例是一个经典的深度学习应用,用于图像分类任务,它使用了一个自定义的VGG-16模型来对《海贼王》中的七个角色进行分类,演示如何将经典CNN模型…...
AI-02a5a7.神经网络-与学习相关的技巧-正则化
过拟合 过拟合指的是只能拟合训练数据,但不能很好地拟合不包含在训练数据中的其他数据的状态。 在机器学习的问题中,过拟合是很常见的问题。 而机器学习的目标是提高泛化能力,即便是没有包含在训练数据里的未观测数据,也希望模…...
C# 常用密码加密与解密技术完全指南
目录 密码安全的核心概念 加密 vs 哈希:何时使用? 密钥管理的重要性 常见攻击手段(中间人攻击、彩虹表) 基础加密技术 对称加密(AES) 非对称加密(RSA) 哈希算法(SH…...
C++ QT 与 win32 窗口可以互操作
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);this->setWindowTitle("天下第一剑"); // HWND hwnd FindWindow(L"天下第一剑",L"天下第一剑"); // qDebug()<<…...
MySQL--day4--排序与分页
(以下内容全部来自上述课程) 1. 排序数据 1.1 排序基本使用 #1.排序 #如果没有使用排序操作,默认情况下查询返回的数据是按照添加数据的顺序显示的 SELECT * FROM employees;# 练习:按照salary从高到低的顺序显示员工信息 # 使用 ORDER …...
文件操作和IO—初识文件
认识文件 狭义上的文件(file),是针对硬盘这种持久化存储的IO设备,当我们想要进行数据保存的时候,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念…...
P2670 [NOIP 2015 普及组] 扫雷游戏
P2670 [NOIP 2015 普及组] 扫雷游戏 - 洛谷 #include<bits/stdc.h> using namespace std; int n,m; char a[105][105]; int main(){cin>>n>>m;for(int i1;i<n;i){for(int j1;j<m;j){cin>>a[i][j];}}for(int i1;i<n;i){for(int j1;j<m;j){…...
HomeAssistant开源的智能家居docker快速部署实践笔记(CentOS7)
1. SGCC_Electricity 应用介绍 SGCC_Electricity 是一个用于将国家电网(State Grid Corporation of China,简称 SGCC)的电费和用电量数据接入 Home Assistant 的自定义集成组件。通过该应用,用户可以实时追踪家庭用电量情况&…...
02 基本介绍及Pod基础排错
01 yaml文件里的字段错误 # 多打了一个i导致的报错 [rootmaster01 yaml]# cat 01-pod.yaml apiVersion: v1 kind: Pod metadata:name: likexy spec:contaiiners:- name: aaaimage: registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1 [rootmaster01 yaml]# kubectl …...
9 定时任务与周期性调度
一、定时任务核心机制 1.1 基础调度配置 # celery.py from celery import Celery from celery.schedules import crontabapp Celery(proj) app.conf.beat_schedule {daily-report: {task: report.generate,schedule: crontab(hour3, minute30), # 每天3:30执行args: (),op…...
macOS 效率工具对比分析:Raycast、Alfred、uTools、Spotlight
macOS 效率工具对比分析:Raycast、Alfred、uTools、Spotlight 对比分析四款常见 macOS 高效工具,涵盖功能、插件生态、开发者支持、适用人群等维度,帮助你选择最适合自己的效率助手。 🔍 一、基本介绍 工具名简介SpotlightmacOS …...
接口测试速成指南:基础知识+工具使用全解析
你是否也有这样的经历:项目初期接口文档混乱,测试人员无从下手;开发说接口OK,测试却频繁遇坑?别怕,接口测试并没你想得那么难! “接口测试怎么做?用什么工具?”面试官一…...
SpringSecurity基础入门
一个身份认证、授权、防御常见攻击的框架。 spring security 中文网:Spring Security中文网 自定义配置 基于内存的用户认证 实现步骤如下: 在配置类中创建security的配置类: Configuration //声明当前类为配置类 EnableWebSecurity //…...
MySQL的安装及相关操作
目录 一. 数据库产生的背景 二. 数据库操作系统的组成 2.1 数据库(Database) 2.2 数据库管理系统(DBMS, Database Management System) 2.3 应用程序(Application) 三. 数据库的分类 3.1 关系数据库 3.2 非关系数据库 四. MySQL安装 4.1yum安装 1. Ubuntu 2. cent…...
【Code】Foundations 2017- Catalogue, List of Tables, List of Figures
Foundations 2017 目录 | Catalogue表格目录 | List of Tables图表目录 | List of Figures 目录 | Catalogue 英文原文中文翻译词汇学习(音标和解释)1. General1. 总则1.1 Scope1.1 范围1.2 Glossary1.2 术语表Glossary [ˈɡlɒsəri] 术语表ÿ…...
【TCGA-CRC】TCGA数据读取
写在前面 参考已有的帖子写的,但是临床数据和UCSC的不同。有知道的小伙伴欢迎指正。 rm(list ls()); gc() test1 data.table::fread("./00_Rawdata/GDCdata/TCGA-COAD/Transcriptome_Profiling/Gene_Expression_Quantification/00ae9ab8-6eaa-4085-af72-26…...
BYUCTF 2025
几周没会的比赛了,都是一题游。这周的BYU还不错,难度适中,只是时间有点短。周末时间不够。 Crypto Many Primes from Crypto.Util.number import bytes_to_long, getPrime import randomflag open("flag.txt").read().encode()…...
【Linux】初见,基础指令(续)
前言: 上文讲解了部分指令,本文我们来讲解剩下的指令【Linux】初见,基础指令-CSDN博客 cat指令 语法:cat 选项 文件 功能:打印文件中的内容 选项: -b 对非空行输出进行编号 -n 对输出的说有行进行编号…...
《MambaLLIE:基于隐式Retinex感知的低光照增强框架与全局-局部状态空间建模》学习笔记
Paper:2405.16105 Github:GitHub - wengjiangwei/MambaLLIE 目录 摘要 一、介绍 二、相关工作 2.1 低光图像增强 2.2 视觉空间状态模型 三、方法 3.1 预备知识 3.2 整体流程 3.3 全局优先-局部次之状态空间块 四、实验 4.1 基准数据集与实施细节 4.2 对比实验 4…...
计算机图形学Games101笔记--几何
第二部分:几何 几何介绍 光栅化解决如何渲染,几何研究模型如何存储在GPU的内存中。几何主要分为两种:隐式几何和显式几何。 **隐士几何:**用点之间的关系存储,如球的计算公式。更一般的可以用f(x,y,z)。我们可以令f…...
Web开发-Python应用Flask框架Jinja模版绑定路由参数传递页面解析SSTI注入
知识点: 1、安全开发-Python-Flask&Jinja2 2、安全开发-Python-路由传参&SSTI注入 演示案例-WEB开发-Python-Flask框架&Jinja2模版&路由传参&SSTI注入 0、Pycharm 配置Python解析 新建Flask项目 1、路由传参 app.route(/) app.route(/<id…...
聚焦开放智能,抢占技术高地 | 2025 高通边缘智能创新应用大赛第五场公开课来袭!
随着2025高通边缘智能创新应用大赛的推进,越来越多的参赛者关注如何借助高性能硬件突破技术瓶颈、打造差异化作品。 5月27日晚8点,大赛将开启初赛阶段的第五场专题公开课——由美格软件研究院院长李书杰领衔,深入解析高通平台的底层架构与参…...
NMOS和PMOS的区别
1 区分NMOS和PMOS:衬底箭头指向G级的是NMOS,衬底箭头背向G级的是PMOS 2 区分D和S级:针对NMOS,体二极管的正方向为S级;针对PMOS,体二极管正的方向为D级 3 区分电流方向:针对NMOS,电…...
Paillier加密方案的原理、实现与应用(vs)
一、实验目的 1、掌握NTL的基本配置和方法(以下是以visualstudio为例) 2、掌握Paillier加密方案的原理与实现 ①钥匙生成:首先,生成一把钥匙,包括钥匙和私钥匙。钥匙由两个大素数(p,q)的乘积n和一个整数g组成&#…...
Metal入门,使用Metal绘制3D图形
这次是使用Metal绘制一个立方体,并且添加旋转效果,绘制正方形的步骤很简单,我们绘制一个正方形就相当于绘制两个三角形,那么绘制一个正方体,我们很容易想到需要绘制他六个面,很显然,我们也需要把…...
Java 04 API
API 简介 一些已经写好的应用程序编程接口Object toString 默认返回的是当前对象在堆内存中的地址值信息:类的全类名十六进制哈希值返回该对象的返回值 class A{ } //返回的是地址哦 String sA.toString(); //细节:使用打印语句,打印对象…...
基于Gitee 的开发分支版本管理规范
一、版本管理规范概述 目的:规范代码分支管理和版本发布流程,提高团队协作效率,确保代码质量和版本可追溯性。适用范围:基于 Gitee 平台开发的所有项目。分支策略:采用 Git Flow 模型的变体,主要分支包括 …...
HOW - 结合 AI 进行 Tailwind 样式开发
文章目录 情况 1:使用 Tailwind CSS 与手写传统 CSS 的开发效率对比情况 2:AI Tailwind 自动生成 UI 的效率如何?总结 在 WHAT - Tailwind 样式方案(不写任何自定义样式) 中我们已经简单介绍过 Tailwind。今天主要认识…...
系统数据对接-从获取到处理的全流程
在后端架构的复杂生态中,子系统间或与外部系统的对接是常态,其核心要义在于实现数据的精准传输。本文聚焦于数据传输后的运算逻辑与异常处理机制,旨在为后端开发者提供深度见解。 一、数据获取机制:触发式与定时任务的权衡 &…...
Java 09Stream流与File类
Stream流与File类 Stream流 简化集合和数组的操作,startWith(“张”) 第一个为这个返回true String1.获取Stream对象 单列集合 双列集合 先获得键值对 在遍历数组 零散的数据 Stream<Integer> arrStream.of(1,2,34,3); stream.forEach(sss); 即可2.中间…...
《光与影:33号远征队》栩栩如生的角色动画是如何创建的?
《光与影:33号远征队》是一款由Sandfall Interactive公司开发的回合制RPG游戏,背景是一个黑暗的幻想世界。游戏因其独特的艺术风格和引人注目的叙事赢得了无数赞誉,成为今年大热游戏中的一匹黑马。 在该游戏制作中Sandfall依靠包括Xsens在内的…...
GESP2024年12月认证C++二级( 第三部分编程题(1)寻找数字)
参考程序(枚举): #include <iostream> //#include <cmath> using namespace std;int main() {int t;cin >> t;while (t--) {long long a;cin >> a;bool found false;// 枚举 b for (long long b 1; b * b * b * b…...
《探索具身智能机器人视觉-运动映射模型的创新训练路径》
视觉 - 运动映射模型作为实现智能交互与精准行动的核心,吸引着全球科研人员与技术爱好者的目光。这一模型就像机器人的 “神经中枢”,连接着视觉感知与肢体运动,使机器人能够在复杂的现实环境中灵活应对各种任务。 传统的视觉 - 运动映射模型…...
Python打卡DAY31
今日的示例代码包含2个部分 notebook文件夹内的ipynb文件,介绍下今天的思路项目文件夹中其他部分:拆分后的信贷项目,学习下如何拆分的,未来你看到的很多大项目都是类似的拆分方法 知识点回顾 规范的文件命名规范的文件夹管理机器学…...
【SPIN】PROMELA远程引用与控制流验证(SPIN学习系列--5)
PROMELA语言提供了两种强大的机制用于验证并发系统:远程引用(remote references)和进程变量引用。这些机制使得在不引入额外状态变量的情况下,能够精确描述系统状态和属性。 远程引用(Remote References) 远程引用允许你直接引用进程中的控制位置(labe…...
GMSL:汽车里的音视频传输
参考链接: blog.csdn.net/weixin_50875614/article/details/119995651 blog.csdn.net/syjie19900426/article/details/145269782 SerDes 应用场景 WHAT GMSL是什么 GMSL(Gigabit Multimedia Serial Links),中文名称为千兆多媒体串行链路,是Maxim公司推出的一种…...
Java并发进阶系列:深度讨论jdk1.8 ConcurrentHashMap并发环境下transfer方法桶位分配过程
在前面有多篇关于jdk1.8的ConcurrentHashMap研究是基于源代码给出的深度分析,要知道多线程环境下的ConcurrentHashMap内部运行机制是相对复杂的,好在IDEA提供的相关断点和Debug功能确实好用,使得多线程调试起来直观,通过这种方式能…...
【深度学习-Day 14】从零搭建你的第一个神经网络:多层感知器(MLP)详解
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...
fdisk和parted的区别
在Linux系统中,fdisk和parted是两种常用的分区工具。虽然它们都可以对硬盘进行分区,但在功能和适用范围上有显著的区别。 fdisk fdisk主要用于MBR(主引导记录)分区表的管理。MBR分区表有以下特点: 支持小于2TB的硬盘…...
springMVC拦截器,拦截器拦截策略设置
目录 1、MyInterceptor1 2、UserController 3、MvcConfig,拦截器4种拦截方法策略 做请求的校验,如果校验没有通过,直接返回,原来下面的处理,就不用处理了 将request进行拦截校验 将response进行拦截校验 preHandle…...
如何测试北斗卫星通讯终端的性能?
测试北斗卫星通讯终端的性能需从功能、性能、环境适应性、可靠性等多维度展开,以下是具体测试内容与方法: 一、基础功能测试 验证终端是否满足北斗系统的核心通讯功能。 (1)通信模式测试 短报文通信 测试终端发送 / 接收短报…...
基于MakeReal3D的虚拟预装系统:飞机装配效率与精度的双重突破
在航空制造领域,飞机部件的对接装配是飞机制造过程中的关键环节。传统的部件装配方式高度依赖操作人员的经验和反复调整,调姿过程耗时较长,且难以保证每次装配都能达到最优状态。随着虚拟现实技术的成熟,虚拟装配技术作为一种新兴…...
IP54是什么?
IP54是什么 定义 IP54是一种国际标准,用来指示设备的防护等级,该标准由国际电工委员会(IEC)制定,并在许多领域广泛使用13。IP是Ingress Protection的缩写,IP等级是针对电气设备外壳对异物侵入的防护等级。…...
Python异步编程详解
Python异步编程详解 引言 异步编程是Python中处理并发操作的重要方式,它允许程序在等待I/O操作时执行其他任务,从而提高程序的整体效率。本文将详细介绍Python异步编程的概念、实现方式以及实际应用场景。 1. 异步编程基础 1.1 什么是异步编程&#x…...