C++的赋值运算符返回
被赋值对象的引用。
返回:如果将运算符视作函数,则称作返回值;如果将运算符结合运算对象视作表达式,则称作表达式的运算结果。
这种特性和Python的海象运算符:=
类似,且十分有用。普通的Python赋值语句仅执行赋值功能,而海象运算符的执行结果为对象本身,这可以极大简化语句的书写模式,下面是一个例子。
# 不使用海象运算符
a = getList()
if len(a) > 0:
...
# 使用海象运算符
if len(a := getList()) > 0:
...
C++中的效果与之相近,例如我们要在每次循环中改变一个变量的值,并根据该值判断是否符合循环终止条件。
// 不使用赋值运算符的返回值
int i = get_value();
while(i != 42){
i = get_value();
}
// 使用赋值运算符的返回值
int i;
while((i = get_value()) != 42){
// ...
}
可以看出,后者可读性更强。
递增运算符的前置版本++i
和后置版本i++
是大家的老朋友了。
两者都会对i
进行+1
的操作,但区别在于,前者返回的是i
的左值引用,而后者返回的是i
在+1
前的右值副本。
int i = 0;
int &r_i = ++i; // √, r_i绑定的就是对象i,此时r_i = 1
int j = i++; // √,i++运算结果为i加1前的右值副本,此时j = 1
int &r_j = i++; // error: 不能创建非常量右值引用
需要注意的是,很多时候,我们写i++
却并未使用其加一前的副本(例如在循环的更新表达式中,我们只是单纯的i++
)。这可能会带来性能影响。
具体而言,对于内置类型int
,编译器会做一定程度上的优化;然而对于一些支持递增递减操作的复杂类型,如迭代器,每次计算得到的右值副本将无条件存储下来,造成空间上的浪费。这里做个实验说明:
首先,用内置算术类型int
执行递增操作,为突出实验效果,前置后置均执行两次。
void test1(){
int i = 0;
++i;
++i;
i++;
int r_i = i++; // 注意这里将i++结果进行存储
}
将上述程序在-O0
下进行编译(即不开启编译器优化),得到汇编代码。
可以看到如果不保存变量的话,前置和后置的汇编结果没有差异。也就是说,对于int
这种内置算术类型,在for循环中用++i
和i++
没有太大区别。
接下来使用STL容器vector
的迭代器进行递增递减操作。
void test2(){
vector<int> a;
auto pa = a.begin(); // pa的类型为iterator>
++pa;
++pa;
pa++;
pa++;
}
可以看到尽管pa++
的值我们没有用到,但是其右值副本依旧会在栈空间中保存下来,这会带来不必要的内存开销。
因此,如果不是对加一前的副本有所需要,请尽可能使用递增/递减匀算符的前置版本。
三目条件运算符cond ? expr1 : expr2
可以在一条语句起到类似if...else...
的作用。
然而三目条件运算符的优先级较低,尤其是与IO运算符
(移位运算符)一起使用时需注意。
cout << ((grade < 60) ? "fail" : "pass"); // 正确,输出fail或者pass
cout << (grade < 60) ? "fail" : "pass"; // 输出1或者0,但这个表达式的值为"fail"或者"pass"
cout << grade < 60 ? "fail" : "pass"; // 编译错误,无法比较cout和60
?:
和<
的优先级均低于<<
,因此在使用时若没有加括号,则前两者的运算对象将优先与<<
结合,得到错误结果。
位运算符的内置版本可以作用于整型,也可以作用于标准库类型bitset
。需要注意两点:
借助异或运算的自反性,可以在不使用中间变量的情况下,完成对两个数的交换。
int a = 114, b = 514;
a = a ^ b; // 即先计算出a跟b的差异
b = a ^ b; // 该差异跟b异或得到a,跟a异或得到b
a = a ^ b; // 此时,a = 514, b = 114
sizeof
运算符用于计算类型名或表达式所占字节数,有两种使用形式,分别为sizeof (type)
和sizeof expr
。当sizeof
的运算对象为一个表达式时,并不会真的对表达式求值,而是推断出表达式的结果类型,说到底还是计算类型大小。
根据上述规则,sizeof
作用于不同类型时有以下特性:
::
直接计算类成员大小,而无需实例化该类。例如:sizeof(classA::memA)
。这也从侧面印证了sizeof
只看类型,而无需具体对象或值。sizeof
的结果的和。计算数组元素个数:sizeof arr / sizeof arr[0]
。vector
或string
这种可变长度容器,将返回固定部分大小,而不考虑可以变化的部分。(即与元素数量多少无关)逗号运算符expr1, expr2
的返回结果为右侧运算对象的值。
bool a = false, b = true;
if(a, b){
cout << "b是逗号表达式的结果"; // 此句被执行
}
在同一个运算表达式中可能存在多个运算对象,这些运算对象的求值顺序C++没有明确规定。
然而,当这些运算对象中涉及同一个对象,且试图改变该对象的值时,这将被视作一种未定义行为(Undefined Behavior, UB)。
int i = 0;
i = (++i) + (i++); // 未定义行为(谭*强:你再骂?)
上面这句在g++ 11.4.0
中的编译结果为 i = 4 ,clang 16.0.0
的编译结果为 i = 3,且会发出警告warning: multiple unsequenced modifications to 'i' [-Wunsequenced]
。
因此,一定要避免在运算表达式中对同一个对象进行顺序未定的更改。