三、运算符与表达式:算术、关系、逻辑、位、赋值、自增自减运算符,优先级与结合性,表达式求值

运算符是C++语言中构建表达式的核心工具,它们决定了数据如何被计算、比较与变换。对于初学者而言,运算符种类繁多、优先级复杂,常常是编写正确代码的第一道门槛。本章将系统梳理C++中六大类运算符的用法与陷阱,深入解析优先级与结合性的底层规则,并通过实际案例演示表达式求值的完整过程。掌握本章内容,你将能够准确阅读和编写复杂的C++表达式,避免因运算符误用而导致的逻辑错误。

C++ 运算符体系 算术运算符 + - * / % 关系运算符 < > <= >= == != 逻辑运算符 && || ! 位运算符 & | ^ ~ << >> 赋值运算符 = += -= *= /= 自增自减 ++i i++ --i i-- 优先级 + 结合性 → 表达式求值 短路求值 · 类型转换 · 副作用顺序 图3-1 C++运算符分类与求值关系总览

3.1 核心原理:运算符的分类与本质

运算符本质上是对操作数进行特定运算的函数式封装。C++中的运算符可以按照操作数个数分为一元、二元和三元运算符,也可以按照功能分为算术、关系、逻辑、位、赋值和自增自减六大类。理解运算符的本质,需要把握三个关键维度:操作数类型、运算结果类型以及是否产生副作用。

算术运算符处理数值类型(int、float、double等),其中%取模运算仅适用于整数类型。关系运算符比较两个值的大小或相等性,返回bool类型(true/false)。逻辑运算符对布尔值进行与、或、非运算,支持短路求值。位运算符直接操作整数的二进制位,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)以及左移(<<)和右移(>>)。赋值运算符将右侧表达式的值赋给左侧的左值,复合赋值运算符(如+=)则先执行算术运算再赋值。自增自减运算符(++/--)是C++中特有的快捷操作,分为前缀和后缀两种形式。

运算符类别 包含运算符 操作数类型 结果类型 副作用
算术 + - * / % 数值类型 数值类型 无
关系 < > <= >= == != 可比较类型 bool 无
逻辑 && || ! bool或可转换类型 bool 短路求值
位 & | ^ ~ << >> 整数类型 整数类型 无
赋值 = += -= *= /= %= <<= >>= &= |= ^= 左值 = 右值 左值引用 修改左值
自增自减 ++i i++ --i i-- 可修改左值 前缀返回引用,后缀返回值 修改操作数

3.2 判断方法:优先级与结合性的判定规则

C++定义了严格的运算符优先级和结合性规则。优先级决定了哪个运算符先执行,结合性决定了同优先级运算符的执行顺序(从左到右或从右到左)。掌握这些规则,是正确分析复杂表达式的关键。

优先级从高到低的核心顺序可以概括为:一元运算符 > 算术 > 移位 > 关系 > 相等性 > 位与 > 位异或 > 位或 > 逻辑与 > 逻辑或 > 条件 > 赋值 > 逗号。特别需要注意的是,赋值运算符的优先级非常低,仅高于逗号运算符,且结合性为从右到左。自增自减运算符中,前缀形式(++i)优先级高于后缀形式(i++),但后缀形式在表达式中先返回原值再自增。

优先级速查口诀:“单算移关与,异或逻条赋逗”——一元、算术、移位、关系、位与、位异或、位或、逻辑与、逻辑或、条件、赋值、逗号。结合性口诀:“赋值条件右结合,其余大多左结合”。

判断表达式求值顺序的实操步骤:

  1. 划分操作数:识别表达式中的所有操作数和运算符。
  2. 标注优先级:为每个运算符标注优先级数值,从高到低排列。
  3. 确定结合性:对同优先级的运算符,根据结合性确定执行顺序。
  4. 逐步化简:从最高优先级开始,逐步将子表达式替换为其结果。
  5. 检查副作用:确认自增自减、赋值等操作对变量值的影响时机。

3.3 案例拆解:复杂表达式求值实战

下面通过一个综合案例,完整演示表达式求值的全过程。假设有以下代码片段:

int a = 5, b = 3, c = 2;
int result = a++ + (b > 2 ? b * 2 : c) && --c > 0 ? a + b : a - b;

这个表达式混合了自增、条件、逻辑、算术、关系和赋值运算符,是检验优先级理解的绝佳案例。我们逐步分析:

步骤 子表达式 优先级 求值过程 结果
1 a++ 后缀自增(最高) 先返回a的原值5,然后a变为6 5(a=6)
2 b > 2 关系(高于条件) 3 > 2 为真 true
3 b * 2 算术(高于关系) 3 * 2 = 6 6
4 b > 2 ? b*2 : c 条件(低于关系) 条件为真,取6 6
5 --c 前缀自增 c先减为1,返回1 1(c=1)
6 --c > 0 关系 1 > 0 为真 true
7 5 + 6 && true 算术高于逻辑与 11 && true,非零为真 true
8 true ? a+b : a-b 条件 条件为真,取a+b=6+3=9 9
9 result = 9 赋值(最低) 将9赋给result result=9

最终result的值为9,同时a变为6,c变为1。这个案例展示了多个关键点:后缀自增先使用后修改、条件运算符的优先级低于逻辑与、赋值运算符最后执行。

风险提示:在同一个表达式中多次修改同一变量(如i++ + i++)属于未定义行为(Undefined Behavior),不同编译器可能产生不同结果。务必避免在复杂表达式中对同一变量进行多次自增或赋值操作。

3.4 常见误区与总结复盘

在实际编程中,运算符相关的错误往往隐蔽且难以排查。以下是初学者最常犯的几类错误:

  • 混淆=与==:在条件判断中误用赋值运算符,导致条件恒为真或恒为假。例如if (x = 5)会将5赋给x,条件永远为真。
  • 忽视短路求值:逻辑与(&&)和逻辑或(||)具有短路特性,即左侧操作数已能确定结果时,右侧不再求值。例如if (ptr != nullptr && ptr->value > 0),当ptr为空时不会访问其成员。
  • 位运算符与逻辑运算符混淆:&与&&、|与||虽然符号相似,但语义完全不同。位运算符按位操作,逻辑运算符返回布尔值。
  • 自增自减的副作用误判:在函数参数传递或复杂表达式中使用自增自减,容易造成难以预测的结果。
  • 整数除法截断:两个整数相除结果仍为整数,小数部分被截断。例如5/2结果为2而非2.5,需要至少一个操作数为浮点类型。

经验建议:编写复杂表达式时,优先使用括号明确运算顺序,不要依赖记忆优先级。括号不仅提高可读性,还能避免因优先级误判导致的逻辑错误。同时,尽量将复杂表达式拆分为多个简单语句,便于调试和阅读。