在 C 语言的世界里,运算符是驱动程序运行的核心引擎。它们就像魔法师手中的咒语,将数据进行变形、比较、组合和控制。如果说数据类型是构建程序的 “积木”,那么运算符就是连接这些积木的 “粘合剂”。从简单的算术加减到复杂的位级操作,运算符的精确使用直接决定了程序的逻辑正确性和运行效率。本章将系统解析 C 语言核心运算符,帮助读者掌握这些 “魔法咒语” 的本质与使用规则。
符号 | 名称 | 功能描述 |
---|---|---|
+ |
加法 | 两数相加 |
- |
减法 / 负号 | 两数相减或取负数 |
* |
乘法 | 两数相乘 |
/ |
除法 | 两数相除 |
% |
取模 | 求整除后的余数(仅用于整型) |
+
、-
、*
、/
支持整型和浮点型;%
仅支持整型。5 / 2
结果为 2(直接截断小数部分)。(-5) % 2
在不同编译器可能返回 - 1 或 1(结果符号与被除数或除数有关)。area = length * width;
percent = (completed * 100) / total;
(需注意整数除法截断问题)int a = INT_MAX; a + 1;
(未定义行为,可能导致负数)int b = 0; int c = 5 / b;
(运行时崩溃)float x = 1.1f + 2.2f;
(实际结果可能为 3.3000001907)#include
#include
int main() {
// 正确用法:浮点除法
float div_float = 5.0f / 2.0f; // 2.5
printf("浮点除法: %.1f\n", div_float);
// 错误用法:整数除零(运行时崩溃)
// int zero = 0;
// int error = 10 / zero; // UB
// 取模运算示例
int mod_pos = 5 % 2; // 1
int mod_neg = (-5) % 2; // 实现定义,可能为-1或1
printf("正取模: %d,负取模: %d\n", mod_pos, mod_neg);
// 整数溢出演示
int max = INT_MAX;
printf("INT_MAX + 1 = %d\n", max + 1); // 输出-2147483648(UB)
return 0;
}
符号 | 名称 | 功能描述 |
---|---|---|
== |
等于 | 判断两值是否相等 |
!= |
不等于 | 判断两值是否不等 |
< |
小于 | 判断左值是否小于右值 |
> |
大于 | 判断左值是否大于右值 |
<= |
小于等于 | 判断左值是否小于等于右值 |
>= |
大于等于 | 判断左值是否大于等于右值 |
int
类型(1 为真,0 为假)。if (0.1 + 0.2 == 0.3)
(因精度问题永远为假)if (x = 5)
(本意是x == 5
,却误写为赋值,导致恒真)fabs(a - b) < 1e-9
if (5 == x)
(若误写为5 = x
,编译器会报错)#include
#include
int main() {
float a = 0.1f + 0.2f;
float b = 0.3f;
// 错误:直接比较浮点数
if (a == b) {
printf("相等\n"); // 不会执行
}
// 正确:使用容差比较
if (fabs(a - b) < 1e-6) {
printf("近似相等\n"); // 执行
}
// 危险:混淆=和==
int x = 0;
if (x = 1) { // 等价于x=1,条件恒真
printf("x is %d\n", x); // 输出1
}
return 0;
}
符号 | 名称 | 功能描述 | ||
---|---|---|---|---|
&& |
逻辑与 | 两操作数都为真则结果为真 | ||
` | ` | 逻辑或 | 两操作数至少一个为真则结果为真 | |
! |
逻辑非 | 取反(真变假,假变真) |
expr1 && expr2
:若expr1
为假,expr2
不执行expr1 || expr2
:若expr1
为真,expr2
不执行if (ptr != NULL && ptr->value > 0)
if (x > 0 || x < -100)
if (a & b)
(本意是逻辑与,却误用位与)if (i++ < 5 || j++ < 10)
(若i++ <5
为真,j++
不执行)#include
int increment(int *n) {
(*n)++;
return *n;
}
int main() {
int a = 0, b = 0;
// 短路求值演示:&&左边为假,右边不执行
if (0 && increment(&a)) {
printf("执行了右边\n");
}
printf("a = %d\n", a); // 0(increment未调用)
// 短路求值演示:||左边为真,右边不执行
if (1 || increment(&b)) {
printf("条件为真\n");
}
printf("b = %d\n", b); // 0(increment未调用)
// 错误:混淆&和&&
int x = 5, y = 3;
if (x & y) { // 位运算,结果为1(非零),条件为真
printf("x & y is true\n");
} else {
printf("x & y is false\n");
}
return 0;
}
符号 | 名称 | 功能描述 | |
---|---|---|---|
& |
按位与 | 对应位都为 1 则结果为 1 | |
` | ` | 按位或 | 对应位有 1 则结果为 1 |
^ |
按位异或 | 对应位不同则结果为 1 | |
~ |
按位取反 | 所有位取反(0 变 1,1 变 0) | |
<< |
左移 | 左移 n 位,低位补 0 | |
>> |
右移 | 右移 n 位(无符号数补 0,有符号数补符号位) |
flags |= MASK
(设置标志位)x << 1
等价于x * 2
,x >> 1
等价于x / 2
(仅适用于非负数)if (x & 1)
(判断最低位是否为 1)(-4) >> 1
在不同编译器可能为 - 2 或 1(取决于算术右移或逻辑右移)int x = 1; x << 32
(未定义行为,因移位位数不能≥操作数宽度)#include
int main() {
unsigned char flag = 0b1010; // 1010
// 设置位(第2位,从0开始计数)
flag |= (1 << 2); // 1010 | 0100 = 1110
printf("设置位后: 0b%08b\n", flag); // 0b1110
// 清除位(第3位)
flag &= ~(1 << 3); // 1110 & 0111 = 0110
printf("清除位后: 0b%08b\n", flag); // 0b0110
// 翻转位(第1位)
flag ^= (1 << 1); // 0110 ^ 0010 = 0100
printf("翻转位后: 0b%08b\n", flag); // 0b0100
// 有符号数右移演示(假设为算术右移)
signed char num = -4; // 11111100(补码)
num >>= 1; // 11111110(-2)
printf("有符号右移: %d\n", num); // -2
return 0;
}
=
int a = 5; // 正确
5 = a; // 错误:常量不能作为左值
,
for
循环多变量操作for (int i = 0, j = 10; i < j; i++, j--) {
printf("%d %d\n", i, j);
}
&
*
int *ptr = &var;
(获取变量地址)*ptr = 10;
(修改指针指向的值)(type)
float f = 3.14f;
int i = (int)f; // 3(正确截断)
int *p = (int*)&f; // 危险:错误解释浮点数据的二进制位
#include
int main() {
// 逗号运算符示例
int x, y;
x = (y = 10, y + 5); // x=15,逗号表达式结果为y+5=15
// 解引用空指针(UB)
int *null_ptr = NULL;
// *null_ptr = 5; // 运行时崩溃
// 强制转换风险演示
unsigned int u = (unsigned int)-1; // 4294967295(补码位模式保留)
printf("强制转换负数为无符号: %u\n", u);
return 0;
}
condition ? expr1 : expr2
expr1
或expr2
max = (a > b) ? a : b;
printf("结果: %s", score > 60 ? "及格" : "不及格");
// 不推荐
result = a ? b : c ? d : e;
// 推荐
if (a) result = b;
else if (c) result = d;
else result = e;
#include
int main() {
int a = 5, b = 3;
int min = (a < b) ? a : b; // 3
printf("最小值: %d\n", min);
// 类型转换示例
float x = 2.5f;
int y = (x > 2.0f) ? (int)x : (int)(x + 1); // 2
printf("转换后: %d\n", y);
return 0;
}
sizeof(func())
不会调用函数size_t
(无符号整型)sizeof(arr) / sizeof(arr[0])
malloc(sizeof(int) * n)
void func(int arr[])
等价于void func(int *arr)
,此时sizeof(arr)
为指针大小(4 或 8 字节)#include
void print_size(int arr[]) {
printf("函数内数组sizeof: %zu\n", sizeof(arr)); // 指针大小(如8字节)
}
int main() {
int arr[] = {1, 2, 3, 4};
printf("数组sizeof: %zu\n", sizeof(arr)); // 16(4元素×4字节)
printf("元素个数: %zu\n", sizeof(arr) / sizeof(arr[0])); // 4
print_size(arr); // 输出指针大小
return 0;
}
void
函数:return;
(可省略)void
函数:return expression;
(必须返回与类型兼容的值)void
函数无返回值: int func() { // 警告:控制流程到达函数结尾无返回值
int x = 5;
}
#include
int add(int a, int b) {
return a + b; // 正确返回值
}
void print_even(int n) {
if (n % 2 == 0) {
return; // 提前退出
}
printf("%d是奇数\n", n);
}
int main() {
int sum = add(3, 5); // 8
print_even(4); // 无输出
print_even(5); // 输出“5是奇数”
return 0;
}
(a + b) * c
比 a + b * c
更清晰int a = 5, b = 3, c = 2;
int wrong = a & b == c; // 等价于 a & (b == c),结果为0(因b==c为假,即0,a&0=0)
int correct = (a & b) == c; // 先计算a&b=1,再判断1==c(2),结果为0
表达式求值
计算5 + 3 * 2 - 10 / 2
的值,并说明计算顺序。
答案:5 + (3*2) - (10/2) = 5+6-5=6
优先级修正
找出flags & MASK == VALUE
的优先级问题,并用括号修正。
修正:(flags & MASK) == VALUE
(原表达式等价于flags & (MASK == VALUE)
,通常非本意)
未定义行为分析
解释i++ + ++i
为何是未定义行为?
原因:对i
的修改和读取顺序未定义,不同编译器可能有不同结果
位运算应用
用位运算将flags
的第 3 位设置为 1,第 1 位清除为 0。
代码:flags |= (1 << 3); flags &= ~(1 << 1);
条件运算符实践
编写函数用条件运算符求绝对值。
代码:int abs(int x) { return x >= 0 ? x : -x; }
sizeof 陷阱
解释为何函数参数中sizeof(arr)
无法得到数组长度?
原因:数组作为函数参数时退化为指针,sizeof
返回指针大小
逻辑错误分析
为什么if (x = 5)
通常是逻辑错误?如何避免?
原因:本意是判断x==5
,却误写为赋值。避免方法:常量写左边,如if (5 == x)
运算符是 C 语言的 “逻辑灵魂”,其精确性直接决定了程序的行为。通过理解每个运算符的语义、优先级和陷阱,结合编译器警告(建议用
-Wall -Wextra -Werror
),可以避免 90% 以上的常见错误。
记住:没有复杂的优先级需要记忆,只有不够明确的括号。善用括号不仅能消除歧义,更能让代码逻辑一目了然。掌握这些运算符,你将拥有编写高效、安全代码的核心能力,在程序的数字世界中自由驰骋。