运算符与表达式 (Operators & Expressions)


章节概述

运算符是程序的”动词”——它们连接数据(操作数)产生新的数据。C 语言以丰富的运算符著称,从基础的加减乘除到位运算、从逻辑判断到类型查询,几乎所有操作都可以通过运算符简洁表达。本章全面覆盖算术运算符、关系运算符、逻辑运算符、位运算符、三元条件运算符 ?:sizeof 运算符、逗号运算符、复合赋值运算符,以及完整的运算符优先级表。掌握运算符不仅是写出正确代码的前提,更是阅读底层性能优化代码、理解嵌入式 C 编程中位操作的核心能力。

核心主题:运算符本质上是一类特殊的”函数”——每个运算符对应一条或多条 CPU 指令。理解运算符在汇编层面的映射是理解 C 语言高效性的关键。本章多处链接到 ASM 教程帮助你建立”C 运算符 → 汇编指令”的心智模型。


第一节:算术运算符与表达式求值


1.1 基本算术运算符

运算符名称示例结果
+加法5 + 38
-减法5 - 32
*乘法5 * 315
/除法5 / 22(整数除法)
%取模(余数)5 % 21
+(一元)正号+55
-(一元)负号-5-5
#include <stdio.h>
 
int main() {
    int a = 17, b = 5;
 
    printf("加法: %d + %d = %d\n", a, b, a + b);
    printf("减法: %d - %d = %d\n", a, b, a - b);
    printf("乘法: %d * %d = %d\n", a, b, a * b);
    printf("除法: %d / %d = %d\n", a, b, a / b);  // 整数除法 → 3
    printf("取模: %d %% %d = %d\n", a, b, a % b);  // 余数 → 2
 
    // 浮点除法
    double x = 17.0, y = 5.0;
    printf("浮点除法: %.1f / %.1f = %.6f\n", x, y, x / y);
 
    // 一元运算符
    printf("取负: -( %d ) = %d\n", b, -b);
 
    return 0;
}

1.2 整数除法的陷阱

#include <stdio.h>
 
int main() {
    // 陷阱1:整数除法截断(向零取整)
    printf(" 5 / 2 = %d\n", 5 / 2);   // 2(不是 2.5)
    printf("-5 / 2 = %d\n", -5 / 2);  // -2(C99 以前是实现定义的!)
    // C99 起明确规定:向零取整
 
    // 陷阱2:取模结果的符号
    printf(" 5 %% 2 = %d\n", 5 % 2);   // 1
    printf("-5 %% 2 = %d\n", -5 % 2);  // -1(结果符号与被除数相同,C99 起)
    printf(" 5 %% -2 = %d\n", 5 % -2); // 1
 
    // 陷阱3:除法优先级问题
    int result = 10 / 2 * 5;  // (10/2)*5 = 25,不是 10/(2*5)=1
    printf("10 / 2 * 5 = %d\n", result);
 
    // 浮点陷阱
    double d1 = 5 / 2;        // 整数除法 2,然后转为 2.0
    double d2 = 5.0 / 2;      // 浮点除法
    double d3 = (double)5 / 2; // 强制浮点除法
    printf("5/2 = %f, 5.0/2 = %f, (double)5/2 = %f\n", d1, d2, d3);
 
    return 0;
}

1.3 取模运算 % 的应用

#include <stdio.h>
#include <stdbool.h>
 
int main() {
    // 判断奇偶
    int n = 7;
    printf("%d%s\n", n, (n % 2 == 0) ? "偶" : "奇");
 
    // 限制范围(取个位数)
    printf("12345 的个位: %d\n", 12345 % 10);       // 5
    printf("12345 的最后两位: %d\n", 12345 % 100);   // 45
 
    // 循环索引(0~4 循环)
    for (int i = 0; i < 10; i++) {
        printf("%d ", i % 5);
    }
    printf("\n");  // 0 1 2 3 4 0 1 2 3 4
 
    // 防止数组越界
    int pos = 12;
    int arr_size = 8;
    int safe_pos = pos % arr_size;
    printf("安全位置: %d\n", safe_pos);  // 4
 
    return 0;
}

1.4 汇编视角:加法/乘法如何被翻译

// C 代码
int compute(int a, int b) {
    int sum = a + b;
    int diff = a - b;
    int prod = a * b;
    return sum + diff + prod;
}

对应的 x86-64 汇编(简化):

compute:
    lea  eax, [rdi+rsi]    ; eax = a + b    (lea 用于高效加法)
    mov  edx, edi
    sub  edx, esi           ; edx = a - b
    add  eax, edx           ; eax = sum + diff
    imul edx, esi           ; edx = a * b    (有符号乘法)
    add  eax, edx           ; eax += prod
    ret

lea(Load Effective Address)指令常被编译器用来做”不要结果的加法”——因为 CPU 的地址生成器比 ALU 更快。详细的汇编指令请参考 。

小节练习

选择题 1

C99 标准下,-11 % 3 的结果是?

  • A. 2

  • B. -2

  • C. 1

  • D. 未定义

判断题 1

5 / 2 在 C 语言中的结果是 2.5。 ( )

  • 正确

  • 错误


第二节:关系运算符与条件判断


2.1 六种关系运算符

运算符名称示例
==等于a == b
!=不等于a != b
>大于a > b
<小于a < b
>=大于等于a >= b
<=小于等于a <= b

关系运算符返回 int 类型:1(真)或 0(假)。

#include <stdio.h>
 
int main() {
    int a = 10, b = 20;
 
    printf("a == b: %d\n", a == b);  // 0 (false)
    printf("a != b: %d\n", a != b);  // 1 (true)
    printf("a > b:  %d\n", a > b);   // 0
    printf("a < b:  %d\n", a < b);   // 1
    printf("a >= b: %d\n", a >= b);  // 0
    printf("a <= b: %d\n", a <= b);  // 1
 
    // 关系运算符可以在表达式中直接使用
    int result = (a < b) * 100;      // 1 * 100 = 100
    printf("result = %d\n", result);
 
    return 0;
}

2.2 == vs = —— C 语言最著名的陷阱

#include <stdio.h>
 
int main() {
    int x = 5;
 
    // 经典 bug:if (x = 10) 始终为真!
    // 因为 = 是赋值运算符,返回赋的值(10 ≠ 0 → true)
    if (x = 10) {           // 本意是 x == 10
        printf("x is 10? Actually x = %d\n", x);
    }
 
    // 防御措施1:常量放左边
    if (10 == x) {          // 如果误写成 10 = x,编译错误!
        printf("Good practice\n");
    }
 
    // 防御措施2:启用 -Wall 编译选项
    // gcc -Wall 会对 if (x = 10) 产生警告
 
    // 故意在条件中做赋值(合法但需标记意图)
    int ch;
    while ((ch = getchar()) != EOF) {  // 外层括号抑制警告
        // 读取字符直到 EOF
    }
 
    return 0;
}

2.3 汇编视角:条件跳转

C 代码: if (a > b) { ... }
汇编:
    cmp  eax, ebx       ; 比较 a 和 b(实际是 sub,但不保存结果)
    jle  .Lskip         ; 如果 a <= b 则跳转(不大于即跳过)
    ...                 ; a > b 时执行的代码
.Lskip:

cmp 指令本质上执行减法 a - b,但并不保存结果——它只设置标志寄存器(flags)。随后的条件跳转指令(jgjgejejne 等)根据标志寄存器状态决定是否跳转。详见 。

小节练习

选择题 1

以下代码中,哪个比较符的结果为 1(真)?

char ch = 'a';
int result = (ch >= 'a' && ch <= 'z');
  • A. ch >= 'a' 为真,ch <= 'z' 为假

  • B. result 的值为 1

  • C. result 的值为 ‘a’

  • D. 编译错误:不能用 && 组合 char


第三节:逻辑运算符与短路求值


3.1 三种逻辑运算符

运算符名称示例说明
&&逻辑与(AND)a && b两侧都为真才为真
||逻辑或(OR)a || b至少一侧为真即为真
!逻辑非(NOT)!a真变假,假变真
#include <stdio.h>
 
int main() {
    int a = 1, b = 0;
 
    printf("a && b = %d\n", a && b);   // 1 && 0 = 0
    printf("a || b = %d\n", a || b);   // 1 || 0 = 1
    printf("!a = %d\n", !a);           // !1 = 0
    printf("!b = %d\n", !b);           // !0 = 1
 
    // C 语言中,0 是 false,非 0 是 true
    printf("42 && 1 = %d\n", 42 && 1);  // 1
    printf("-1 || 0 = %d\n", -1 || 0);  // 1
 
    return 0;
}

3.2 短路求值(Short-circuit Evaluation)

&&|| 具有短路特性:一旦结果确定,剩余的表达式不再求值。

#include <stdio.h>
 
int test1() {
    printf("test1() called\n");
    return 1;
}
 
int test2() {
    printf("test2() called\n");
    return 0;
}
 
int test3() {
    printf("test3() called\n");
    return 1;
}
 
int main() {
    // && 短路:左侧为 0 → 直接返回 0,不计算右侧
    printf("=== 0 && test1() ===\n");
    0 && test1();  // test1() 不会被调用!
 
    // && 正常:左侧为 1 → 继续计算右侧
    printf("=== 1 && test2() ===\n");
    1 && test2();
 
    // || 短路:左侧为 1 → 直接返回 1,不计算右侧
    printf("=== 1 || test3() ===\n");
    1 || test3();  // test3() 不会被调用!
 
    // 利用短路做安全检查
    int *p = NULL;
    // p != NULL 为假 → 不计算 *p,避免空指针解引用!
    if (p != NULL && *p > 0) {
        printf("positive\n");
    }
 
    return 0;
}

3.3 逻辑运算符与位运算符的区别

| 特性 | && / ||(逻辑) | & / |(位) |
|------|-------------------|-----------------|
| 操作数 | 任何标量类型 | 整数类型 |
| 返回 | 0 或 1 | 按位运算结果 |
| 短路 | 是 | 否(两侧都计算) |
| 常见错误 | — | 把 &&& 用 |

int a = 2, b = 1;
printf("a && b = %d\n", a && b);  // 1 (true)
printf("a &  b = %d\n", a &  b);  // 0 (位与:10 & 01 = 00)
// 2 & 1 虽然非 0,但恰好是 0,这与 a && b 可能不同!

小节练习

选择题 1

以下代码中哪个函数调用不会被短路特性跳过?

int x = 0, y = 1;
int r = (x != 0) && (y++ > 0);
printf("y = %d\n", y);
  • A. y++ 会被计算

  • B. y++ 不会被计算

  • C. 编译错误

  • D. 取决于编译器


第四节:位运算符 —— C 语言底层操作的利器


4.1 六种位运算符

运算符名称示例运算规则
&按位与(AND)a & b两位都为 1 才得 1
|按位或(OR)a | b任一位为 1 就得 1
^按位异或(XOR)a ^ b两位不同得 1,相同得 0
~按位取反(NOT)~a每位 0→1,1→0
<<左移a << n左移 n 位,右侧补 0
>>右移a >> n右移 n 位,左侧填充因类型而异
#include <stdio.h>
 
// 打印二进制表示
void print_binary(unsigned int n) {
    int bits = sizeof(n) * 8;
    for (int i = bits - 1; i >= 0; i--) {
        printf("%d", (n >> i) & 1);
        if (i % 8 == 0) printf(" ");
    }
    printf("\n");
}
 
int main() {
    unsigned int a = 0b11001100;   // 204
    unsigned int b = 0b10101010;   // 170
 
    printf("a       = "); print_binary(a);
    printf("b       = "); print_binary(b);
    printf("a & b   = "); print_binary(a & b);   // 10001000 (136)
    printf("a | b   = "); print_binary(a | b);   // 11101110 (238)
    printf("a ^ b   = "); print_binary(a ^ b);   // 01100110 (102)
    printf("~a      = "); print_binary(~a);      // 00110011
    printf("a << 2  = "); print_binary(a << 2);  // 00110000 (左移,×4)
    printf("a >> 2  = "); print_binary(a >> 2);  // 00110011 (右移,÷4)
 
    return 0;
}

4.2 位运算的经典应用

#include <stdio.h>
 
int main() {
    unsigned int x = 0b10101100;
 
    // 1. 设置某一位(set bit):OR 1
    unsigned int sets = x | (1 << 3);  // 设置第 3 位
    printf("设置第3位后: 0x%02X\n", sets);
 
    // 2. 清除某一位(clear bit):AND 0
    unsigned int clears = x & ~(1 << 5);  // 清除第 5 位
    printf("清除第5位后: 0x%02X\n", clears);
 
    // 3. 翻转某一位(toggle bit):XOR 1
    unsigned int toggle = x ^ (1 << 2);  // 翻转第 2 位
    printf("翻转第2位后: 0x%02X\n", toggle);
 
    // 4. 检查某一位(test bit)
    if (x & (1 << 7)) {
        printf("第 7 位是 1\n");
    } else {
        printf("第 7 位是 0\n");
    }
 
    // 5. 提取最低位的 1
    unsigned int lowest = x & -x;
    printf("最低位的1: 0x%02X\n", lowest);
 
    // 6. 判断是否为 2 的幂
    unsigned int n = 64;
    if (n > 0 && (n & (n - 1)) == 0) {
        printf("%u 是 2 的幂\n", n);
    }
 
    // 7. 交换两个数(不借助临时变量)
    int a = 5, b = 9;
    a ^= b; b ^= a; a ^= b;
    printf("交换后: a=%d, b=%d\n", a, b);
 
    // 8. 快速乘除 2 的幂
    printf("10 * 8 = %d, 10 << 3 = %d\n", 10 * 8, 10 << 3);
    printf("100 / 4 = %d, 100 >> 2 = %d\n", 100 / 4, 100 >> 2);
 
    return 0;
}

4.3 有符号整数的右移陷阱

#include <stdio.h>
 
int main() {
    int positive = 16;
    int negative = -16;
 
    // 无符号右移 → 逻辑右移(左侧补 0)
    unsigned int u = (unsigned int)negative;
    printf("unsigned -16 >> 2: %u\n", u >> 2);  // 很大的正数
 
    // 有符号右移 → 算术右移(左侧补符号位)—— 平台定义!
    printf("signed -16 >> 2: %d\n", negative >> 2);  // 通常是 -4
 
    // C 标准规定:signed 右移是"实现定义"行为
    // 大多数现代编译器使用算术右移(保持符号)
 
    return 0;
}

4.4 汇编视角:位运算就是单条指令

; C: a = b & c;
and  eax, ebx       ; eax = eax & ebx (1 条指令!)
 
; C: a = b | c;
or   eax, ebx
 
; C: a = b ^ c;
xor  eax, ebx
 
; C: a = ~b;
not  eax
 
; C: a = b << 3;
shl  eax, 3         ; 左移 3 位
 
; C: a = b >> 2;
sar  eax, 2         ; 算术右移 2 位(保持符号)
; shr eax, 2         ; 逻辑右移(用于 unsigned)

位运算是 C 语言中最接近硬件的操作——每个位运算符都对应一条 CPU 指令,执行速度极快。这也是为什么嵌入式 C 编程和 Linux 内核中大量使用位运算。

小节练习

选择题 1

unsigned int 类型的变量执行右移时,左侧填充什么?

  • A. 0

  • B. 1

  • C. 符号位

  • D. 不确定


第五节:特殊运算符


5.1 三元条件运算符 ?:

#include <stdio.h>
 
int main() {
    int score = 85;
 
    // 语法:condition ? value_if_true : value_if_false
    const char *grade = score >= 60 ? "及格" : "不及格";
    printf("成绩: %s\n", grade);
 
    // 嵌套使用(注意可读性)
    const char *level = score >= 90 ? "优秀" :
                        score >= 75 ? "良好" :
                        score >= 60 ? "及格" : "不及格";
    printf("等级: %s\n", level);
 
    // 直接用在 printf 中
    printf("结果: %s\n", score % 2 == 0 ? "偶数" : "奇数");
 
    // 取两数中较大值
    int a = 10, b = 20;
    int max = (a > b) ? a : b;
    printf("max = %d\n", max);
 
    // 汇编层面:条件运算符等价于 if/else 分支
    // 但有时编译器会生成条件传送指令(cmov)避免分支
 
    return 0;
}

5.2 sizeof 运算符

#include <stdio.h>
 
int main() {
    // sizeof 是编译时运算符(对于非 VLA 类型)
    printf("sizeof(int)    = %zu\n", sizeof(int));
    printf("sizeof(int*)   = %zu\n", sizeof(int*));
 
    int x = 42;
    printf("sizeof(x)      = %zu\n", sizeof(x));    // 不需要括号(变量)
    printf("sizeof x       = %zu\n", sizeof x);     // 可以省略括号
 
    // 数组
    int arr[10];
    printf("sizeof(arr)    = %zu\n", sizeof(arr));  // 40 (10 × 4)
    printf("元素数         = %zu\n", sizeof(arr) / sizeof(arr[0])); // 10
 
    // 指针陷阱
    void print_array_size(int *p) {
        printf("sizeof(p) = %zu\n", sizeof(p)); // 8(指针大小,不是数组!)
    }
    print_array_size(arr);
 
    // struct
    struct Point { int x; int y; };
    printf("sizeof(Point)  = %zu\n", sizeof(struct Point)); // 8
 
    return 0;
}

sizeof 不是函数,是编译时运算符。对于 VLA(变长数组),sizeof运行时计算数组大小——这是 sizeof 唯一的运行时场景。

5.3 逗号运算符 ,

#include <stdio.h>
 
int main() {
    // 逗号运算符:依次计算左右表达式,返回最右侧的值
    int x, y;
 
    // 在 for 循环中的应用
    for (int i = 0, j = 10; i < 5; i++, j--) {
        printf("i=%d, j=%d\n", i, j);
    }
 
    // 作为普通表达式
    int result = (x = 1, y = 2, x + y);  // result = 3
    printf("result = %d\n", result);
 
    // 注意:函数参数中的逗号不是逗号运算符!
    printf("不是逗号运算符: ");
    printf("%d %d\n", 1, 2);  // 这里的逗号是参数分隔符
 
    // 要强制使用逗号运算符需要加括号
    printf("逗号运算符: %d\n", (1, 2));  // 输出 2
 
    return 0;
}

5.4 复合赋值运算符

运算符等价于说明
+=x = x + a先加再赋值
-=x = x - a先减再赋值
*=x = x * a先乘再赋值
/=x = x / a先除再赋值
%=x = x % a先取模再赋值
&=x = x & a先按位与再赋值
|=x = x | a先按位或再赋值
^=x = x ^ a先按位异或再赋值
<<=x = x << a先左移再赋值
>>=x = x >> a先右移再赋值
#include <stdio.h>
 
int main() {
    int x = 10;
 
    x += 5;   printf("x += 5  → %d\n", x);   // 15
    x -= 3;   printf("x -= 3  → %d\n", x);   // 12
    x *= 2;   printf("x *= 2  → %d\n", x);   // 24
    x /= 5;   printf("x /= 5  → %d\n", x);   // 4
    x %= 3;   printf("x %%= 3  → %d\n", x);  // 1
    x <<= 3;  printf("x <<= 3 → %d\n", x);   // 8
    x >>= 1;  printf("x >>= 1 → %d\n", x);   // 4
    x |= 3;   printf("x |= 3  → %d\n", x);   // 7
    x &= 5;   printf("x &= 5  → %d\n", x);   // 5
    x ^= 3;   printf("x ^= 3  → %d\n", x);   // 6
 
    return 0;
}

小节练习

选择题 1

int a = 5; a += a *= 2; 执行后 a 的值是?

  • A. 15

  • B. 20

  • C. 10

  • D. 不确定


第六节:运算符优先级完整表


6.1 C 语言运算符优先级表(从高到低)

优先级运算符结合性说明
1() [] . -> ++(后缀) --(后缀)左→右函数调用/数组下标/成员访问
2! ~ +(一元) -(一元) ++(前缀) --(前缀) *(解引用) &(取地址) sizeof (type)右→左一元运算符
3* / %左→右乘除取模
4+ -左→右加减
5<< >>左→右位移
6< <= > >=左→右关系比较
7== !=左→右相等比较
8&左→右按位与
9^左→右按位异或
10|左→右按位或
11&&左→右逻辑与
12||左→右逻辑或
13?:右→左条件运算符
14= += -= *= /= %= &= ^= |= <<= >>=右→左赋值
15,左→右逗号运算符

6.2 优先级常见陷阱

#include <stdio.h>
 
int main() {
    // 陷阱1:* 和 & 的优先级
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;
    // *p++ 等价于 *(p++),不是 (*p)++
    // 后缀 ++ 优先级高于解引用 *
 
    // 陷阱2:位运算优先级低于比较运算
    int x = 1;
    // x & 0xFF == 0  被解析为 x & (0xFF == 0)
    // 正确写法:(x & 0xFF) == 0
 
    // 陷阱3:<< 优先级低于 +
    // 1 << 2 + 1 = 1 << (2 + 1) = 1 << 3 = 8
    // 正确写法:(1 << 2) + 1 = 4 + 1 = 5
    printf("1 << 2 + 1 = %d\n", 1 << 2 + 1);  // 8 (不是 5!)
    printf("(1 << 2) + 1 = %d\n", (1 << 2) + 1);  // 5
 
    // 陷阱4:&& 优先级高于 ||
    // a || b && c 等价于 a || (b && c)
    printf("0 || 0 && 1 = %d\n", 0 || 0 && 1);  // 0
    printf("(0 || 0) && 1 = %d\n", (0 || 0) && 1);  // 0
 
    // 黄金法则:不确定优先级时,加括号!
    // 括号不仅确保正确性,还提高可读性
 
    return 0;
}

小节练习

选择题 1

1 + 2 * 3 的结果是?

  • A. 7

  • B. 9

  • C. 11

  • D. 不确定

选择题 2

*p++ 等价于什么?

  • A. (*p)++(取出值后自增)

  • B. *(p++)(先取 *p,再 p 后移)

  • C. *(++p)(p 先后移,再取值)

  • D. &p++


章节测试

一、判断题(正确选,错误选)

判断题 1

C 语言中,/ 运算符对整数和浮点数操作的行为完全相同。 ( )

  • 正确

  • 错误

判断题 2

sizeof 是 C 语言标准库中的一个函数。 ( )

  • 正确

  • 错误

判断题 3

位运算符 &| 也可以进行短路求值。 ( )

  • 正确

  • 错误

判断题 4

a += ba = a + b 在语义上永远等价。 ( )

  • 正确

  • 错误

判断题 5

异或运算 a ^ a 的结果总是 0。 ( )

  • 正确

  • 错误

判断题 6

~0 的值取决于机器的字长。 ( )

  • 正确

  • 错误

判断题 7

1 << 31 在 32 位 int 上执行的结果是未定义行为。 ( )

  • 正确

  • 错误

判断题 8

5 / 2 * 2 的结果是 4。 ( )

  • 正确

  • 错误

判断题 9

!(x > 0) 等价于 x <= 0。 ( )

  • 正确

  • 错误

判断题 10

三元运算符 ?: 的结果必须赋给一个变量,不能直接作为表达式使用。 ( )

  • 正确

  • 错误


二、选择题(单项选择题)

选择题 1

int a = 10; int b = a++ + ++a; 执行后 ab 的值是?

  • A. a=12, b=22

  • B. a=12, b=23

  • C. a=11, b=21

  • D. 未定义行为

选择题 2

printf("%d\n", 5 && 0); 输出什么?

  • A. 5

  • B. 0

  • C. 1

  • D. 未定义

选择题 3

以下哪个表达式的结果为 1?

  • A. 5 == 5 == 1

  • B. (5 == 5) == 1

  • C. 5 == (5 == 1)

  • D. 以上都不是

选择题 4

以下哪个程序段可以正确交换 a 和 b 的值?

  • A. a = b; b = a;

  • B. a ^= b; b ^= a; a ^= b;

  • C. a = a + b; b = a;

  • D. a ^= b; a ^= b; b ^= a;

选择题 5

sizeof('a') 在 C 语言中的值是?

  • A. 1

  • B. 4(在 32 位 int 平台上)

  • C. 2

  • D. 不确定

选择题 6

unsigned int x = 1; printf("%u\n", ~x); 输出什么?(32 位 unsigned int)

  • A. 0

  • B. 1

  • C. 4294967294

  • D. -2

选择题 7

以下哪个表达式用于判断整数 x 是否为 2 的幂?

  • A. x % 2 == 0

  • B. x & 1 == 0

  • C. x > 0 && (x & (x - 1)) == 0

  • D. (x | (x - 1)) == 0

选择题 8

(1, 2, 3) 这个表达式的值是?

  • A. 1

  • B. 2

  • C. 3

  • D. 语法错误

选择题 9

以下关于 x << 1 的说法正确的是?

  • A. 总是等价于 x * 2

  • B. 等价于 x * 2,但当 x 为负数时结果不确定

  • C. 等价于 x / 2

  • D. 等价于 x ^ 2

选择题 10

int a = 5; int b = a = 10; 执行后 a 和 b 的值是?

  • A. a=5, b=5

  • B. a=10, b=5

  • C. a=10, b=10

  • D. 编译错误


️ 动手练习题

练习题 1:表达式求值练习

难度:

编写程序,在不编译运行的情况下手算以下表达式的值,然后用程序验证:

(1) 10 + 3 * 5 - 2
(2) 10 / 3 * 3
(3) 10 % 3 + 4 / 2
(4) 5 + 3 << 1
(5) 0 && 1 || 2
(6) 5 > 3 ? 10 : 20
(7) sizeof(char) + 1
(8) ~0 & 0xFF

对每个表达式,写出计算步骤和最终结果。

练习题 2:位运算工具集

难度:

编写一个程序,提供以下位操作功能:

  1. 输入一个整数,打印其二进制表示
  2. 设置/清除/翻转指定位置的位
  3. 统计整数中 1 的个数(Brian Kernighan 算法:while(n) { n &= n-1; count++; }
  4. 反转一个字节的位序(如 0b11000001 → 0b10000011)
  5. 不使用临时变量交换两个整数(XOR 交换)

力扣练习:力扣位运算题

练习题 3:运算符优先级纠错

难度:

编写一个程序,故意使用容易引起歧义的表达式(不加括号),观察编译器行为:

  1. 1 << 2 + 3 vs (1 << 2) + 3
  2. *p++ vs (*p)++ vs *(p++)
  3. a & 0xF == 0 vs (a & 0xF) == 0
  4. !x == 0 vs !(x == 0)
  5. n > 0 ? 1 : n < 0 ? -1 : 0(三元运算符嵌套的陷阱)

-Wall 编译,注意哪些表达式产生了警告。

练习题 4:整型溢出与取模

难度:

力扣练习:力扣大数运算题

用 C 语言完成该题,注意大数问题。额外练习:

  1. 实现一个函数 add_mod(a, b, mod),在不溢出的前提下计算 (a + b) % mod
  2. 实现快速幂算法(使用位运算优化):power_mod(base, exp, mod)
  3. 理解为何 (a * b) % mod 在直接计算时会溢出

练习题 5:表达式树求值

难度:

实现一个简单的表达式求值程序,支持 + - * / 和括号:

  • 使用栈处理运算符优先级
  • 支持多位数和负数
  • 处理除零错误

力扣练习:力扣表达式求值题