当前位置: 首页 > news >正文

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 的最终值是多少?计算过程是怎样的?

        计算过程:

  1. 短路性质:首先计算 && 左边的表达式 5 > 3,其逻辑值为 1。
  2. 右边表达式:计算 && 右边的表达式 8 < 4 - !0。
    • 非运算:!0 的逻辑值为 1。
    • 算术运算:4 - 1 的结果为 3。
    • 关系运算:8 < 3 的逻辑值为 0。
  3. 逻辑与运算:整个表达式的值为 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 的计算过程是怎样的?值为多少?

        计算过程:

  1. 短路性质:首先计算 a + b < c,即 2 + 3 < 4,其逻辑值为 0。
  2. 短路效果:b == c && a 不会执行,|| 左边的值为 0。
  3. 右边表达式:计算 b + c && b + c。
    • 算术运算:b + c 的结果为 7。
    • 逻辑与运算:7 && 7 的逻辑值为 1。
  4. 逻辑或运算: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 的值分别是多少?表达式的结果是多少?

        计算过程:

  1. 短路性质:首先计算(m = a > b)。
    • 关系比较:a > b,即 1 > 2,其逻辑值为 0。
    • 赋值运算:m = 0。
  2. 短路效果:(n = c > d) 不执行。
  3. 最终结果:
    • m 的值为 0。
    • n 的值为 2。
    • 整个表达式的结果为 0。

总结与建议:

  1. 避免复杂表达式:尽量简化表达式。
  2. 明确执行顺序:使用小括号明确执行顺序,避免依赖运算符优先级。
  3. 拆分表达式:复杂表达式拆分为多个步骤,提高可读性和可维护性。

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 摄氏度。由于天气变化,温度变化如下:

  1. 上午升温 3 度。
  2. 下午降温 2 度。
  3. 晚上升温 1 度。

        编写一个程序,计算晚上的最终温度。

#include <stdio.h>int main()
{float temperature = 25;temperature += 3; // 上午升温temperature -= 2; // 下午降温temperature += 1; // 晚上升温printf("晚上的最终温度:%.2f 摄氏度\n", temperature);return 0;
}

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

相关文章:

  • POSTGRESQL 初体验
  • GitLab部署
  • 前端mjs和js文件区别,mjs和cjs区别---.es.js和.mjs的区别
  • Jules 从私有预览阶段推向全球公测
  • 虚幻引擎5-Unreal Engine笔记之摄像头camera
  • R语言学习--Day04--数据分析技巧
  • 基于HTML的Word风格编辑器实现:从零打造功能完备的富文本编辑器
  • AI-02a5a7.神经网络-与学习相关的技巧-正则化
  • leetcode 合并区间 java
  • 【神经网络与深度学习】激活函数的可微可导
  • IDEA2025版本使用Big Data Tools连接Linux上Hadoop的HDFS
  • [面试精选] 0001. 两数之和
  • 【解决】SSH 远程失败之路由配置问题
  • laravel中如何使用Validator::make定义一个变量是 ,必传的,json格式字符串
  • 【git】在Windows上搭建git服务器
  • 使用Java实现Navicat密码的加密与解密
  • Python训练营打卡 Day31
  • 牛客网 NC14736 双拆分数字串 题解
  • 【windows】音视频处理工具-FFmpeg(合并/分离)
  • I2C 协议的理解以及在 OLED 上的应用
  • 猫是影子,又是平行时空的使者
  • 姜再冬会见巴基斯坦空军参谋长:中方欢迎并支持巴印通过对话妥善处理分歧
  • 秦洪看盘|热门股或将退潮,短线波动难免
  • 又有明星分析师晋升管理层:“白金分析师”武超则已任中信建投证券党委委员
  • 63岁微波遥感领域著名专家李春升参加学术会议期间病逝
  • 零跑汽车一季度营收破百亿元:净亏收窄至1.3亿元,毛利率14.9%创新高