4. 运算符与表达式:C语言的“小算盘”

各位同学,咱们今天聊聊C语言里的运算符和表达式。说白了,这就是让计算机帮咱们“算账”的工具。我刚开始学的时候,觉得这东西不就是加减乘除嘛,有啥好讲的?结果后来在项目里踩了不少坑,才发现这里面的门道还真不少。

嗯,咱们不搞那些虚的,直接上手。运算符分好几类:算术、关系、逻辑、位、赋值、逗号。每一类都有自己的脾气,咱们一个一个来捋。

4.1 算术运算符:最基础的“加减乘除”

算术运算符就是咱们小学就认识的 +、-、*、/、%。不过,在C语言里,它们有几个“怪癖”。

  • 整数除法会“砍掉”小数部分:比如 5 / 2 结果是 2,不是 2.5。为啥?因为两个整数相除,结果还是整数,直接截断。我当年写计算器程序时,就因为这个被用户骂过……
  • 取模 % 只对整数有效5 % 2 结果是 1。浮点数不能用 %,会报错。
  • 自增自减 ++ 和 --:这俩货有前缀和后缀之分。i++ 是先取值再加1,++i 是先加1再取值。我建议你刚开始时,尽量单独写一行,别混在复杂表达式里,不然容易把自己绕晕。

避坑指南:我曾经在循环里写了 while (i++ < 10),结果边界条件算错了,多循环了一次。后来我养成了习惯:自增自减单独一行,绝不跟其他操作混在一起。

int a = 5, b = 2;
int c = a / b;   // c = 2
int d = a % b;   // d = 1

int i = 3;
int x = i++;     // x = 3, i = 4
int y = ++i;     // i = 5, y = 5

4.2 关系运算符:比较大小,结果只有“真”或“假”

关系运算符就是 >、<、>=、<=、==、!=。它们用来比较两个值,结果要么是1(真),要么是0(假)。

这里有个经典坑:千万别把 == 写成 =。我见过太多新手(包括当年的我)在 if 条件里写 if (a = 5),结果赋值成功了,条件永远为真,程序跑飞了。你想想看,编译器还不报错,多坑人。

警告:写 if 条件时,建议把常量放左边,比如 if (5 == a)。这样万一你写成 if (5 = a),编译器会直接报错,救你一命。

int a = 10, b = 20;
int result = (a > b);  // result = 0
if (a != b) {
    printf("不相等\n");
}

4.3 逻辑运算符:与、或、非,组合判断

逻辑运算符有 &&(与)、||(或)、!(非)。它们用来组合多个条件。

  • 短路求值:这是C语言的一个“小聪明”。比如 a && b,如果 a 为假,b 根本不会执行。同理,a || b 如果 a 为真,b 也不会执行。我曾在项目里利用这个特性,把可能出错的函数放在后面,前面先做安全检查。
  • ! 运算符:把真变假,假变真。注意 !0 结果是1,!5 结果是0(因为5是非零,视为真)。

小技巧:写复杂条件时,多用括号明确优先级。别指望自己记住所有优先级,我写了十年C,有时候还得查表。加括号,既保命又让同事看得懂。

int x = 5, y = 0;
if (x > 0 && y > 0) {
    // 不会执行,因为 y > 0 为假
}
if (x > 0 || y > 0) {
    // 会执行,因为 x > 0 为真
}
if (!y) {
    printf("y 为假\n");
}

4.4 位运算符:直接操作二进制位

位运算符是C语言的“硬核”部分,包括 &(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)。

说实话,平时业务代码里用得不多,但在嵌入式、驱动、协议解析里,它们是神器。我当年做网络协议栈时,经常用位运算来解析包头里的标志位,一个 & 就能搞定,比用结构体快多了。

  • 左移 <<:相当于乘以2的n次方。比如 1 << 3 等于 8。
  • 右移 >>:相当于除以2的n次方。但注意,对于有符号负数,右移的行为是“实现定义”的,有的机器补0,有的补1。我建议你尽量用无符号数做位运算。
  • 异或 ^:相同为0,不同为1。可以用来交换两个数,不用临时变量(虽然现在编译器优化得很好,但面试爱考)。

个人经验:我曾经用位运算实现了一个高效的权限系统。每个权限用一个bit表示,检查权限时直接用 & 操作,比遍历数组快了一个数量级。

unsigned char flags = 0b1010;
unsigned char mask = 0b0010;
if (flags & mask) {
    printf("第1位为1\n");
}

// 左移
int a = 3;       // 二进制 0011
int b = a << 2;  // 二进制 1100,即 12

// 异或交换
int x = 5, y = 3;
x = x ^ y;
y = x ^ y;
x = x ^ y;
// 现在 x=3, y=5

4.5 赋值运算符:不只是“=”

赋值运算符除了 =,还有复合赋值运算符:+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=。

说白了,a += 5 就是 a = a + 5 的简写。我建议你多用复合赋值,代码更简洁,而且有些编译器对复合赋值能生成更高效的代码。

注意:赋值表达式本身也有值。比如 a = 5 这个表达式的值就是5。所以 if (a = 5) 是合法的,但通常不是你想要的。再次强调:别把 == 写成 =。

int a = 10;
a += 5;   // a = 15
a *= 2;   // a = 30
a &= 0xFF; // a = 30(高位清零)

4.6 逗号运算符:把多个表达式串起来

逗号运算符可能是最被低估的运算符。它的作用是把多个表达式连成一个,整个表达式的值等于最后一个表达式的值。

比如 int a = (1, 2, 3);,a 的值是3。前面的1和2都执行了,但被丢弃了。

我一般在 for 循环里用逗号运算符,比如同时初始化多个变量:for (i = 0, j = 10; i < j; i++, j--)。嗯,这里要注意,逗号运算符的优先级是最低的,所以经常需要加括号。

避坑指南:我曾经在函数参数里用了逗号运算符,结果发现参数个数不对。因为函数参数列表里的逗号是分隔符,不是逗号运算符。要区分清楚。

int a = (1, 2, 3);  // a = 3

int i, j;
for (i = 0, j = 10; i < j; i++, j--) {
    printf("i=%d, j=%d\n", i, j);
}

知识体系总览

下面这张图,是我自己总结的运算符分类和优先级关系。你把它存脑子里,写代码时心里就有底了。

C语言运算符分类 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 逗号运算符 + - * / % ++ -- > < >= <= == != && || ! & | ^ ~ << >> = += -= *= /= %= , (逗号) 优先级小贴士 单目 > 算术 > 关系 > 逻辑 > 赋值 > 逗号 记不住?加括号!加括号!加括号!

好了,运算符这块儿咱们就聊到这儿。说白了,多写、多练、多踩坑,自然就熟了。下次写代码时,遇到优先级拿不准的,别犹豫,直接加括号。相信我,没人会笑话你加括号,但会有人笑话你写错了。


公众号:蓝海资料掘金营,微信deep3321