1. 运算符的分类
1.1 按操作数个数
-
一元运算符(Unary)
作用于单个操作数:- 取地址
&obj
- 解引用
*ptr
- 逻辑非
!b
- 一元加减
+x
,-x
- 递增递减
++i
,i--
- 取地址
-
二元运算符(Binary)
作用于两个操作数:- 算术运算
a + b
,a * b
- 关系判断
a == b
,a < b
- 逻辑运算
a && b
,a || b
- 下标
arr[i]
(相当于*(arr + i)
)
- 算术运算
-
三元运算符(Ternary)
唯一示例:条件运算符int x = cond ? v1 : v2;
-
函数调用
虽非传统符号,但f(a,b,…)
本质上对任意数量操作数执行运算。
某些符号(如
*
、+
、-
、&
)既可作一元运算符也可作二元运算符,含义由上下文决定。
2. 表达式组合:优先级与结合律
-
优先级(Precedence)
决定无括号时运算符的组合:5 + 10 * 20 / 2 // 等同于 5 + ((10 * 20) / 2)
-
结合律(Associativity)
指同级运算符的结合方向:- 大多数算术、关系、逻辑运算符为 左结合:
a - b - c
→(a - b) - c
- 赋值、条件运算符为 右结合
- 大多数算术、关系、逻辑运算符为 左结合:
-
求值顺序(Order of Evaluation)
C++17 以后,函数参数、子表达式在大多数场合都有确定顺序;之前则可能未指定,需谨慎避免副作用冲突。
3. 操作数的类型转换
表达式中不同类型操作数经常需要统一类型:
-
算术提升(Promotion)
小于int
类型(char
、short
、bool
)提升为int
或unsigned int
。 -
算术转换(Conversion)
如果两操作数类型仍不一致,向“更高精度”或“更大容量”类型转换:int + double → double
-
指针与其他类型
默认不允许指针与整型或浮点混用,除非显式地reinterpret_cast
。 -
用户定义转换
类可通过定义operator T()
或explicit operator T()
实现自定义类型转换。
4. 重载运算符
C++ 支持为自定义类型重载运算符,赋予它们类特有的行为:
struct Vec {double x,y;Vec operator+(Vec const& rhs) const {return { x + rhs.x, y + rhs.y };}
};
重载不会改变运算符原有的优先级或结合律,只影响操作数类型和返回类型。
5. 左值(lvalue)与右值(rvalue)
-
左值
表示内存中可寻址的对象或函数,可以(通常)出现在赋值的左侧。 -
右值
表示临时性值或常量,不可取地址,不能做左侧操作数。
运算 | 操作数要求 | 返回值性质 |
---|---|---|
= | 需非常量左值 | 左值 |
& | 左值 | 右值(指针) |
*p | 指针 | 左值 |
arr[i] | 可退化为指针的数组 | 左值 |
++i | 左值 | 左值 |
i++ | 左值 | 右值(旧值) |
在“需要右值”的场合(如按值传参),可用左值;但在“需要左值”的场合,绝不可用右值。
结语
理解以上基础,能帮助你:
- 正确书写复杂表达式
- 避免隐式求值顺序和类型转换带来的隐患
- 充分利用 C++ 的运算符重载,提升代码可读性
希望这篇博客能助你在日常编程中游刃有余地驾驭 C++ 表达式,写出更健壮、高效的代码。欢迎在评论区交流更多心得!