运算符是 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,但要注意可能丢失精度或改变语义。
// 交换两个变量(不用临时变量)
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;
#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
#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
#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
inf 或触发异常。!0 是 1,!1 是 0。不要用逻辑运算符的结果做位操作。赋值运算符 = 与关系运算符 == 混淆:
if (x = 5) { ... } // 赋值,永远为真
if (x == 5) { ... } // 比较
sizeof 不计算表达式:sizeof(i++) 中 i++ 不会执行(VLA 除外)。
移位量不能超出类型宽度:对 32 位类型移位 ≥ 32 位是 UB。
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。注意当 a 和 b 是同一变量时结果为 0,需额外判断。
Q4:位运算在嵌入式开发中有哪些典型应用?
A:(1) 寄存器位域的设置/清除/翻转/读取;(2) 掩码操作提取特定字段;(3) 标志位管理;(4) 位带操作(ARM Cortex-M);(5) 通信协议中的位打包/解包;(6) 快速乘除 2 的幂。
Q5:sizeof 运算符在编译时还是运行时求值?
A:对于非 VLA(变长数组)类型,sizeof 在编译时求值,结果是 size_t 类型的常量。对于 VLA,sizeof 在运行时求值,此时操作数中的表达式会被执行。