2. 运算符.md 10 KB

2 运算符

2.1 完整概念讲解

运算符是 C 语言表达计算和逻辑的基本手段。C 提供了丰富的运算符集,从基本算术到精细的位操作,覆盖了从应用层到硬件寄存器操作的各种需求。

算术运算符

运算符 含义 示例
+ a + b
- a - b
* a * b
/ a / b(整数除法截断)
% 取模 a % b(仅整数)

整数除法向零截断,浮点除法保留小数。取模运算的结果符号与被除数相同。

关系运算符

运算符 含义
== 等于
!= 不等于
< 小于
> 大于
<= 小于等于
>= 大于等于

关系运算符返回 int 类型:成立为 1,不成立为 0。

逻辑运算符与短路求值

运算符 含义 短路行为
&& 逻辑与 左操作数为 0 时,右操作数不求值
\|\| 逻辑或 左操作数非 0 时,右操作数不求值
! 逻辑非 无短路

短路求值是 C 语言的重要特性,常用于空指针检查:

if (ptr != NULL && ptr->value > 0) { ... }

位运算符详解

运算符 含义 用途
& 按位与 清除特定位、提取位域
\| 按位或 设置特定位
^ 按位异或 翻转特定位、简单加密
~ 按位取反 构造掩码
<< 左移 乘以 2 的幂
>> 右移 除以 2 的幂(有符号数实现定义)

位运算在寄存器操作中的典型应用:

// 设置寄存器的第 3 位
reg |= (1 << 3);

// 清除寄存器的第 3 位
reg &= ~(1 << 3);

// 翻转寄存器的第 3 位
reg ^= (1 << 3);

// 检查第 3 位是否置位
if (reg & (1 << 3)) { ... }

// 提取 bit[5:3] 的值
unsigned int field = (reg >> 3) & 0x7;

赋值运算符

复合赋值:+=-=*=/=%=&=|=^=<<=>>=

条件运算符(三目运算符)

condition ? expr_if_true : expr_if_false

逗号运算符

从左到右依次求值,整个表达式的结果为最右边表达式的值。常用于 for 循环的多个初始化/更新。

运算符优先级(从高到低)

优先级 运算符 结合性
1 () [] -> . 左到右
2 ! ~ ++ -- + - * & (type) sizeof 右到左
3 * / % 左到右
4 + - 左到右
5 << >> 左到右
6 < <= > >= 左到右
7 == != 左到右
8-10 & ^ \| 左到右
11-12 && \|\| 左到右
13 ?: 右到左
14 = += 右到左
15 , 左到右

类型转换规则

隐式转换遵循 usual arithmetic conversions:小类型→大类型,有符号→无符号(当无符号秩 ≥ 有符号秩时)。显式转换使用强制转换 (type)expr,但要注意可能丢失精度或改变语义。


2.2 核心 API/语法

位运算常用模式

// 交换两个变量(不用临时变量)
a ^= b; b ^= a; a ^= b;

// 判断奇偶
(n & 1) == 1  // 奇数
(n & 1) == 0  // 偶数

// 向上对齐到 n 的倍数
aligned = (size + (n - 1)) & ~(n - 1);

条件运算符

int max = (a > b) ? a : b;

2.3 代码示例

示例 1:算术、关系、逻辑运算符与短路求值

#include <stdio.h>

int main(void) {
    int a = 10, b = 3;

    printf("=== 算术运算符 ===\n");
    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);
    printf("%d %% %d = %d\n", a, b, a % b);

    printf("\n=== 关系运算符 ===\n");
    printf("%d > %d  = %d\n", a, b, a > b);
    printf("%d == %d = %d\n", a, b, a == b);

    printf("\n=== 逻辑运算符与短路求值 ===\n");
    int x = 0, y = 5;
    /* && 短路:x 为 0,整个表达式为 0,y++ 不会执行 */
    if (x && y++) {
        printf("不会到达这里\n");
    }
    printf("短路后 y = %d (y++ 未执行)\n", y);

    /* || 短路:x 非零,整个表达式为 1,y++ 不会执行 */
    x = 1;
    if (x || y++) {
        printf("短路后 y = %d (y++ 未执行)\n", y);
    }

    /* 条件运算符 */
    int max = (a > b) ? a : b;
    printf("max(%d, %d) = %d\n", a, b, max);

    return 0;
}

编译命令:

gcc -std=c99 -Wall -Wextra -o operators operators.c
./operators

示例 2:位运算与寄存器模拟

#include <stdio.h>
#include <stdint.h>

/* 模拟硬件寄存器 */
#define REG_CTRL   (*(volatile uint32_t *)0x40021000)
#define BIT(n)     (1U << (n))

/* 寄存器位域定义 */
#define CTRL_ENABLE   BIT(0)
#define CTRL_INT_EN   BIT(1)
#define CTRL_DMA_EN   BIT(2)
#define CTRL_MODE_Pos 3
#define CTRL_MODE_Msk (0x7U << CTRL_MODE_Pos)

int main(void) {
    uint32_t reg = 0;

    printf("=== 位运算基本操作 ===\n");

    /* 设置位 */
    reg |= CTRL_ENABLE;
    printf("设置 ENABLE:  0x%08X\n", reg);

    reg |= CTRL_INT_EN;
    printf("设置 INT_EN:  0x%08X\n", reg);

    /* 清除位 */
    reg &= ~CTRL_INT_EN;
    printf("清除 INT_EN: 0x%08X\n", reg);

    /* 翻转位 */
    reg ^= CTRL_DMA_EN;
    printf("翻转 DMA_EN: 0x%08X\n", reg);
    reg ^= CTRL_DMA_EN;
    printf("再次翻转:    0x%08X\n", reg);

    /* 检查位 */
    printf("ENABLE 是否置位: %s\n",
           (reg & CTRL_ENABLE) ? "Yes" : "No");

    /* 读取位域 */
    uint32_t mode = 5;
    reg |= (mode << CTRL_MODE_Pos);
    uint32_t read_mode = (reg & CTRL_MODE_Msk) >> CTRL_MODE_Pos;
    printf("写入 mode=5, 读回 mode=%u\n", read_mode);

    printf("\n=== 位运算技巧 ===\n");

    /* 交换两个数 */
    int x = 42, y = 99;
    printf("交换前: x=%d, y=%d\n", x, y);
    x ^= y; y ^= x; x ^= y;
    printf("交换后: x=%d, y=%d\n", x, y);

    /* 判断奇偶 */
    int n = 7;
    printf("%d 是%s\n", n, (n & 1) ? "奇数" : "偶数");

    /* 向上对齐到 4 的倍数 */
    size_t size = 13;
    size_t aligned = (size + 3) & ~((size_t)3);
    printf("size=%zu 对齐到 4 的倍数: %zu\n", size, aligned);

    return 0;
}

编译命令:

gcc -std=c99 -Wall -Wextra -o bitops bitops.c
./bitops

示例 3:逗号运算符与运算符优先级

#include <stdio.h>

int main(void) {
    /* 逗号运算符在 for 循环中 */
    int i, j;
    for (i = 0, j = 10; i < j; i++, j--) {
        printf("i=%d, j=%d, sum=%d\n", i, j, i + j);
    }

    /* 逗号运算符的值为最右表达式 */
    int a = (1, 2, 3, 4);
    printf("逗号表达式结果: %d\n", a);  /* 输出 4 */

    /* 优先级陷阱 */
    int x = 5;
    /* *p++ 等价于 *(p++),先解引用再自增 */
    printf("注意优先级: !0 = %d\n", !0);
    printf("注意优先级: !1 = %d\n", !1);
    printf("注意优先级: ~0 = %d\n", ~0);

    return 0;
}

编译命令:

gcc -std=c99 -Wall -Wextra -o precedence precedence.c
./precedence

2.4 注意事项与易错点

  1. 除以零:整数除以零是未定义行为(UB),浮点除以零可能产生 inf 或触发异常。
  2. 有符号整数溢出:有符号整数溢出是 UB,无符号整数溢出是 defined(回绕)。
  3. 位运算不要与负数混用:右移负数的行为是实现定义的(算术移位还是逻辑移位)。
  4. 逻辑运算符返回 0 或 1!0 是 1,!1 是 0。不要用逻辑运算符的结果做位操作。
  5. 赋值运算符 = 与关系运算符 == 混淆

    if (x = 5) { ... }   // 赋值,永远为真
    if (x == 5) { ... }  // 比较
    
  6. sizeof 不计算表达式sizeof(i++)i++ 不会执行(VLA 除外)。

  7. 移位量不能超出类型宽度:对 32 位类型移位 ≥ 32 位是 UB。


2.5 面试要点

Q1:&(按位与)和 &&(逻辑与)有什么区别?

A:& 是位运算符,对两个操作数的每一位进行与运算,两个操作数都会被求值。&& 是逻辑运算符,仅在两个操作数都为非零时返回 1,且有短路特性(左操作数为 0 时不求值右操作数)。

Q2:为什么 a ^ a == 0 在 C 中可能不是你期望的结果?

A:因为 == 的优先级高于 ^,所以 a ^ a == 0 被解析为 a ^ (a == 0)。应写成 (a ^ a) == 0。这是优先级导致的经典错误。

Q3:如何不用临时变量交换两个整数?

A:使用异或:a ^= b; b ^= a; a ^= b;。原理:异或满足交换律和结合律,且任何数与自身异或为 0。注意当 ab 是同一变量时结果为 0,需额外判断。

Q4:位运算在嵌入式开发中有哪些典型应用?

A:(1) 寄存器位域的设置/清除/翻转/读取;(2) 掩码操作提取特定字段;(3) 标志位管理;(4) 位带操作(ARM Cortex-M);(5) 通信协议中的位打包/解包;(6) 快速乘除 2 的幂。

Q5:sizeof 运算符在编译时还是运行时求值?

A:对于非 VLA(变长数组)类型,sizeof 在编译时求值,结果是 size_t 类型的常量。对于 VLA,sizeof 在运行时求值,此时操作数中的表达式会被执行。