刚接触C语言时,很多人会觉得运算符不就是加减乘除吗?但真正开始写代码后,你会发现一个简单的i++就能让人抓狂。我在带新人时最常听到的抱怨就是:"为什么这段代码的输出和我想的不一样?"
运算符之所以重要,是因为它直接决定了程序的行为逻辑。比如在LeetCode第461题"汉明距离"中,用错一个位运算符就会导致完全错误的结果。更可怕的是,这类错误往往不会直接报错,而是悄无声息地给出错误答案,等你发现时可能已经调试了半小时。
我曾见过一个经典案例:有人想用a = a++来实现自增,结果程序陷入死循环。这是因为他不理解后缀自增的"先用后加"特性。这类问题在笔试面试中经常出现,也是区分新手和老手的重要标志。
看起来最人畜无害的除法运算符/,在实际使用中却暗藏杀机。当两个整数相除时,C语言会直接截断小数部分。比如:
c复制int a = 5 / 2; // 结果是2,不是2.5
float b = 5 / 2; // 仍然是2.0,因为先进行整数除法
正确的做法是至少让一个操作数为浮点数:
c复制float c = 5.0 / 2; // 这才是2.5
求余运算符%也有类似问题,它只能用于整数运算。很多人会错误地用在浮点数上:
c复制double d = 5.3 % 2; // 编译错误!
++和--运算符可能是C语言中最让人困惑的设计。我在LeetCode第29题"两数相除"中就踩过坑:
c复制int sign = 1;
// 错误写法
sign = sign++;
// 正确写法
sign++;
前缀和后缀的区别在于:
++i:先自增再使用("急性子")i++:先使用再自增("慢性子")更复杂的是在表达式中混合使用时:
c复制int i = 1;
int j = (i++) + (++i); // 未定义行为!不同编译器结果可能不同
位运算在算法题中经常大显身手。以LeetCode 461为例,计算两个数的汉明距离(二进制位不同的位置数):
c复制int hammingDistance(int x, int y) {
int xor = x ^ y; // 异或运算找出不同位
int count = 0;
while (xor) {
count += xor & 1; // 检查最低位
xor >>= 1; // 右移一位
}
return count;
}
这个解法用到了三个位运算符:
^(异或):找出不同位&(与):检查特定位>>(右移):遍历所有位在实际开发中,位运算可以大幅提升性能:
c复制// 快速乘除2的幂次
x << n; // 等价于 x * (2^n)
x >> n; // 等价于 x / (2^n)
// 判断奇偶
if (x & 1) { /* 奇数 */ }
// 交换两个数
a ^= b; b ^= a; a ^= b;
但要注意有符号数的右移行为是实现定义的,可能补0(逻辑右移)或补符号位(算术右移)。
C语言会自动进行类型提升,这经常导致意外结果:
c复制unsigned int u = 10;
int i = -5;
if (i < u) { // 这里i会被转换为unsigned int,变成很大的正数
printf("This won't execute!");
}
浮点数比较也容易出问题:
c复制float f = 0.1;
if (f == 0.1) { // false!因为0.1默认是double
printf("This won't execute either");
}
显式转换可以避免很多问题,但要注意:
c复制double d = 3.14;
int i = (int)d; // 正确:截断小数部分
// 危险:可能丢失精度
long l = 1234567890123;
int j = (int)l; // 溢出!
在指针转换时更要小心:
c复制int *p = (int*)malloc(sizeof(int) * 10);
// 比下面这种写法更安全
int *p = malloc(sizeof(int) * 10); // C++会报错
与其死记硬背优先级,不如掌握几个原则:
常见坑点:
c复制if (a & b == 0) // 实际是 if (a & (b == 0))
if ((a & b) == 0) // 这才是想要的效果
int mask = 1 << 4 + 1; // 实际是 1 << (4 + 1)
int mask = (1 << 4) + 1; // 这才是17
建议在团队中制定代码规范,对复杂表达式强制使用括号。
根据我多年调试经验,总结出这些黄金法则:
==比如下面这个判断闰年的例子:
c复制// 错误写法
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
// 正确写法(加括号明确优先级)
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
// 更清晰的写法
int is_leap = (year % 4 == 0);
is_leap = is_leap && (year % 100 != 0);
is_leap = is_leap || (year % 400 == 0);
记住,代码首先是给人看的,其次才是给机器执行的。运算符虽小,却能决定程序的正确性与可读性。