4. 运算符与表达式:C++ 的“计算引擎”

运算符和表达式,说白了就是 C++ 里做“计算”的那一套工具。你写代码,本质上就是在处理数据——要么算数、要么比较、要么做逻辑判断。我刚开始学 C++ 时,觉得运算符就是些符号,没啥好学的。后来踩了不少坑,才发现这里面的门道比想象中多得多。

今天我们就来把这套“计算引擎”拆开看看。我会结合自己项目里的实际经验,帮你避开那些我曾经掉进去的坑。

核心观点:运算符的优先级和结合性,是 C++ 里最容易出 bug 的地方之一。很多老手都会在这里翻车。

C++ 运算符体系 运算符与表达式 算术运算符 赋值运算符 关系运算符 逻辑运算符 位运算符 三目运算符 优先级与结合性 每个运算符都有优先级和结合性,决定了表达式的计算顺序 我建议:拿不准优先级时,直接用括号!

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

算术运算符就是 +、-、*、/、%。嗯,小学就学过的东西。但在 C++ 里,有几个细节你一定要注意。

整数除法会截断! 我记得刚入行时,写了个计算平均分的代码:

int total = 95;
int count = 4;
double avg = total / count;  // 结果是 23.0,不是 23.75!

为什么会这样?因为两个整数相除,结果还是整数,小数部分直接丢掉。我当时调试了半天,最后才发现是类型问题。正确的写法是:

double avg = static_cast<double>(total) / count;  // 23.75

取模运算符 % 只适用于整数。我在做循环队列时经常用它来绕圈:

int index = (current + 1) % BUFFER_SIZE;  // 循环前进

小技巧:判断一个数是不是偶数,用 n % 2 == 0 比用位运算更直观。虽然位运算更快,但可读性更重要。

4.2 赋值运算符:不只是等号

很多人以为 = 就是赋值。其实 C++ 里有一整套复合赋值运算符:+=-=*=/=%= 等等。

我个人习惯用复合赋值,因为代码更简洁,而且编译器优化起来也更容易。比如:

int score = 100;
score += 10;   // 等价于 score = score + 10;
score *= 2;    // 等价于 score = score * 2;

注意:赋值运算符有返回值!a = b = c = 5; 这种链式赋值是合法的,因为 c = 5 返回 5,然后赋给 b,再赋给 a。但我不建议写这种代码,可读性太差。

4.3 关系运算符:比较大小

关系运算符就是 ><>=<===!=。它们返回 bool 类型,要么 true,要么 false

这里有个经典坑:不要用 == 比较浮点数。我曾经在金融项目里吃过这个亏:

double a = 0.1 + 0.2;  // 实际是 0.30000000000000004
if (a == 0.3) {         // 永远不成立!
    // 这段代码永远不会执行
}

正确的做法是判断差值是否小于一个很小的数:

const double EPSILON = 1e-9;
if (fabs(a - 0.3) < EPSILON) {
    // 可以认为相等
}

4.4 逻辑运算符:与或非

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

短路求值 是这里最重要的特性。什么意思?就是如果第一个条件已经能决定结果,后面的就不执行了。比如:

if (ptr != nullptr && ptr->value > 0) {
    // 如果 ptr 是 nullptr,第二个条件根本不会执行
    // 这就避免了空指针访问
}

我建议你利用这个特性来写更安全的代码。比如先检查指针是否有效,再访问它的成员。

避坑指南:我曾经看到有人写 if (x > 5 & x < 10),少写了一个 &。单个 & 是位运算,不是逻辑运算!结果就是 bug 找了一下午。

4.5 位运算符:底层操作的利器

位运算符包括 &(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)。

说实话,日常业务代码里用得不多。但在嵌入式、网络协议、性能优化场景下,位运算是神器。我在做网络协议解析时,经常用位运算来提取标志位:

// 假设一个字节的 flags 里,第3位表示是否加密
uint8_t flags = packet[0];
bool isEncrypted = (flags & 0x04) != 0;  // 0x04 = 0000 0100

左移右移 可以用来做快速的乘除 2 的幂:

int x = 8;
int y = x << 2;  // 相当于 x * 4 = 32
int z = x >> 1;  // 相当于 x / 2 = 4

注意:右移有符号数时,行为是 implementation-defined 的。有的编译器做算术右移(补符号位),有的做逻辑右移(补0)。我建议只对无符号数做右移操作。

4.6 三目运算符:简洁的条件表达式

三目运算符 ? : 是 C++ 里唯一的三元运算符。格式是 条件 ? 表达式1 : 表达式2

我个人觉得它很适合用来写简单的条件赋值:

int maxVal = (a > b) ? a : b;  // 取较大值

但别滥用。我曾经见过有人嵌套三层三目运算符,那代码简直没法看。我的原则是:如果一行写不下,就用 if-else

4.7 运算符优先级与结合性:最容易翻车的地方

好了,这是今天的重头戏。运算符优先级决定了谁先算,结合性决定了同优先级时从左还是从右。

我直接给你一张常用优先级表(从高到低):

优先级 运算符 结合性
1(最高)::(作用域解析)左到右
2() [] -> .左到右
3++ -- + - ! ~ & *(单目)右到左
4* / %左到右
5+ -左到右
6<< >>左到右
7< <= > >=左到右
8== !=左到右
9&(按位与)左到右
10^(按位异或)左到右
11|(按位或)左到右
12&&左到右
13||左到右
14? :(三目)右到左
15= += -= 等赋值右到左
16(最低),(逗号)左到右

你想想看,如果我不给你这张表,你能一眼看出下面这个表达式的结果吗?

int result = 2 + 3 * 4 >> 1 & 0x0F;

嗯,我建议你直接加括号。真的,拿不准优先级时,加括号永远是最安全的做法。代码是写给人看的,不是写给编译器看的。

我的经验:在团队代码规范里,我明确要求:除了简单的加减乘除,其他运算符组合必须加括号。这看起来有点啰嗦,但能避免 90% 的优先级相关 bug。

总结一下

运算符是 C++ 的基础工具,每个都很简单,但组合起来就容易出问题。记住几点:

  • 整数除法会截断,记得转型
  • 浮点数不要直接用 == 比较
  • 利用短路求值写更安全的代码
  • 位运算是底层利器,但注意右移有符号数的问题
  • 优先级拿不准就加括号,不丢人

好了,运算符这块就聊到这儿。代码写多了,这些自然就熟练了。


专注资料整理