3. 运算符与表达式:C语言的“计算引擎”

运算符和表达式,说白了就是C语言里做“计算”的那套工具。你写代码,本质上就是在处理数据——加减乘除、比较大小、判断真假、甚至直接操作内存里的二进制位。这套东西,我几乎每天都在用。

我个人习惯把运算符分成几类来记:算术、关系、逻辑、位、赋值。每一类都有自己的脾气。咱们一个一个来看。

3.1 算术运算符:最基础的数学

加减乘除,谁都会。但C语言里有些细节,坑过不少人。

int a = 10, b = 3;
int sum = a + b;    // 13
int diff = a - b;   // 7
int prod = a * b;   // 30
int quot = a / b;   // 3,注意!整数除法会截断
int rem = a % b;    // 1,取模运算

注意整数除法:两个整数相除,结果还是整数,小数部分直接丢掉。不是四舍五入,是直接截断。我曾经在项目里写过一个计算平均值的函数,忘了把其中一个操作数转成浮点,结果算出来全是整数,排查了半天才发现。

避坑指南:取模运算符 % 只能用于整数。浮点数不能用。另外,负数取模的结果在不同C标准下可能不一样,我建议你尽量别在负数上玩取模。

3.2 关系运算符:比较大小,判断真假

关系运算符用来比较两个值,结果要么是1(真),要么是0(假)。

int x = 5, y = 10;
if (x < y) { /* 条件成立 */ }
if (x == y) { /* 条件不成立 */ }

嗯,这里有个经典错误:把 == 写成 =。我刚开始学C的时候,至少犯过三次这种错。编译器不会报错,但逻辑全乱套了。我的习惯是:把常量写在左边,比如 if (10 == x),这样万一写成 =,编译器会报错。

运算符含义示例
>大于5 > 3 → 1
<小于5 < 3 → 0
>=大于等于5 >= 5 → 1
<=小于等于5 <= 3 → 0
==等于5 == 3 → 0
!=不等于5 != 3 → 1

3.3 逻辑运算符:组合条件

逻辑运算符用来连接多个条件。C语言里,任何非0值都视为“真”,0视为“假”。

int a = 1, b = 0;
int result1 = a && b;   // 0 (假)
int result2 = a || b;   // 1 (真)
int result3 = !a;       // 0 (假)

短路求值:这是C语言的一个特性。比如 a && b,如果a已经是假,b根本不会被执行。同理,a || b 如果a是真,b也不会执行。我在项目中利用这个特性写过一些“偷懒”的代码,比如 if (ptr && ptr->value),先检查指针是否为空,再访问成员,安全又高效。

小技巧:利用短路求值,可以写出更安全的条件判断。先检查“前提条件”,再执行“核心逻辑”。

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

位运算符是C语言的“硬核”部分。它直接操作内存里的二进制位,效率极高。嵌入式开发、驱动开发、性能优化时,这东西是神器。

unsigned char a = 0b1100;  // 12
unsigned char b = 0b1010;  // 10
unsigned char c = a & b;   // 0b1000 (8)  按位与
unsigned char d = a | b;   // 0b1110 (14) 按位或
unsigned char e = a ^ b;   // 0b0110 (6)  按位异或
unsigned char f = ~a;      // 0b0011 (3)  按位取反
unsigned char g = a << 1;  // 0b1000 (24) 左移一位
unsigned char h = a >> 1;  // 0b0110 (6)  右移一位

我记得有一次做网络协议解析,需要从一个32位整数里提取出某几个bit。用位运算,几行代码就搞定了。如果用其他方式,又慢又啰嗦。

重点:左移一位相当于乘以2,右移一位相当于除以2(对于无符号整数)。但要注意,有符号整数的右移行为是“实现定义”的,不同编译器可能不一样。我建议你尽量用无符号整数做位运算。

3.5 赋值运算符:给变量“喂”值

最简单的赋值就是 =。但C语言还提供了一组复合赋值运算符,比如 +=、-=、*= 等。它们本质上是“先运算,再赋值”。

int x = 10;
x += 5;   // 等价于 x = x + 5,结果是15
x *= 2;   // 等价于 x = x * 2,结果是30

我个人很喜欢用复合赋值,代码更简洁,而且编译器通常能生成更高效的机器码。但要注意,别在复合赋值里搞太复杂的表达式,比如 x += y++,这种代码可读性很差,我建议你拆开写。

3.6 运算符优先级:谁先谁后?

运算符优先级,说白了就是“先算谁,后算谁”。C语言的优先级规则挺多的,我从来不去死记硬背。我的原则是:拿不准就加括号。

int result = a + b * c;   // 先乘后加
int result2 = (a + b) * c; // 先加后乘,括号明确优先级

我曾经在代码审查时看到过一段代码,写的是 if (x & 0x0F == 0)。你猜结果是什么?因为 == 的优先级高于 &,所以实际执行的是 x & (0x0F == 0),也就是 x & 0,永远为假。嗯,这个bug藏得很深。从那以后,我只要混用位运算符和关系运算符,一定加括号。

避坑指南:记住几个关键优先级:
1. 括号 > 一元运算符 > 算术 > 关系 > 逻辑 > 赋值
2. 拿不准就加括号,没人会怪你括号多,只会感谢你代码清晰。

知识体系总览

下面这张图,帮你把本章的知识点串起来。你可以把它当作一个“运算符地图”。

C语言运算符与表达式 算术运算符 + - * / % 关系运算符 > < == != >= <= 逻辑运算符 && || ! 位运算符 & | ^ ~ << >> 赋值运算符 = += -= *= /= 运算符优先级 括号 > 一元 > ... 核心要点 • 整数除法会截断,浮点除法才精确 • 逻辑运算符有短路求值特性 • 位运算效率高,适合底层操作 • 优先级拿不准?加括号! 运算符是C语言的“计算引擎”,掌握它们,你就掌握了数据处理的钥匙。

好了,运算符这块的内容就这些。说白了,多写、多练、多踩坑,自然就熟了。我当年也是从各种bug里爬出来的,你现在遇到的每个问题,我都遇到过。别怕,慢慢来。