运算符求值顺序
-
运算符的优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值。对于没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为。
int i = 0; cout << i << " " << ++i << endl; //未定义的
因为程序是未定义的,所以我们无法推断它的行为。编译器可能先求++i的值再求i的值,此时输出结果为1 1;也可能先求i的值再求++i的值,输出结果为0 1;甚至编译器还可能做完全不同的操作。
-
有4种运算符明确规定了运算对象的求值顺序。
- 逻辑与
&&
运算符,它规定先求左侧运算对象的值,只有当左侧运算对象的值为真时才继续求右侧运算对象的值。 - 逻辑或
||
运算符,当且仅当左侧运算对象为假时才对右侧运算对象求值。 - 条件
?:
运算符 - 逗号
,
运算符
- 逻辑与
-
运算对象的求值顺序与优先级和结合律无关,在一条形如f() + g() * h() + j()的表达式中:
- 优先级规定,g()的返回值和h()的返回值相乘。
- 结合律规定,f()的返回值先与g()和h()的乘积相加,所得的结果再与j()的返回值相加。
- 对于这些函数的调用顺序没有明确规定。
如果f、g、h和j是无关函数,它们既不会改变同一个对象的状态也不执行IO任务,那么函数的调用顺序不受限制。反之,如果其中某几个函数影响同一个对象,则它是一条错误的表达式,将产生未定义的行为。
算数运算符(左结合律)
-
整数相除结果还是整数,也就是说,如果商含小数部分,直接丢弃。
-
当计算的结果超出该类型所能表示的范围时就会 溢出
short short_value = 32767; //如果short类型占16位,则能表示的最大值是32767 short_value += 1; //该计算导致溢出 cout << "short value: " << short_value << endl; // -32768
很多系统在编译和运行时都不报溢出错误,像其他未定义的行为一样,溢出的结果是不可预知的。通常情况下该值会发生 环绕(wrapped around)。
-
除法运算中,C++11新标准规定商一律向0取整(即直接切除小数部分)。
-
如果m%n不等于0,则它的符号和m相同。 (-m)/n = m/(-n) = -(m/n) (-m)%n = -(m%n) m%(-n) = m%n
21 % 6; // 3 21 / 6; // 3 21 % 7; // 0 21 / 7; // 3 -21 % -8; // -5 -21 / -8; // 2 21 % -5; // 1 21 / -5; // -4
递增递减运算符
-
递增和递减运算符有两种形式:前置版本和后置版本。
除非必要,否则不用递增递减运算符的后置版本。前置版本的递增运算符避免了不必要的工作,它把值加1后直接返回改变了运算符对象。与之相比,后置版本需要将原始值存储下来以便于返回这个为修改的内容。如果我们不需要修改前的值,那么后置版本的操作就是一种浪费。
位运算符
-
位运算符的运算对象可以是带符号的,也可以是无符号的。如果运算对象是带符号的且它的值为负,那么位运算符如何处理运算对象的“符号位”依赖与机器。而且,此时的左移操作可能会改变符号位的值,因此是一种未定义的行为。
关于符号位如何处理没有明确的规定,所以强烈建议仅将位运算符用于处理无符号类型。
-
二进制位或者向左移动或者向右移动,移出边界之外的位就被舍弃掉了。
- 左移运算符
<<
在右侧插入值为0的二进制位 -
右移运算符
>>
的行为则依赖与其左侧运算对象的类型- 如果该运算对象是无符号类型,则在左侧插入值为0的二进制位;
- 如果该运算对象是带符号类型,则在左侧插入符号位的副本或值为0的二进制位,如何选择要视具体环境而定。
- 左移运算符
运算符优先级表
结合律 | 运算符 | 功能 | 用法 |
---|---|---|---|
左 | :: | 全局作用域 | ::name |
左 | :: | 类作用域 | class::name |
左 | :: | 命名空间作用域 | namespace::name |
左 | . | 成员选择 | object.member |
左 | -> | 成员选择 | pointer->member |
左 | [] | 下标 | expr[expr] |
左 | () | 函数调用 | func(expr_list) |
左 | () | 类型构造 | type(expr_list) |
左 | ++ | 后置递增运算 | lvalue++ |
左 | – | 后置递减运算 | lvalue– |
右 | typeid | 类型ID | typeid(type) |
右 | typeid | 运行时类型ID | typeid(expr) |
右 | explicit cast | 类型转换 | cast_name |
右 | ++ | 前置递增运算 | ++lvalue |
右 | – | 前置递减运算 | –lvalue |
右 | ~ | 位求反 | ~expr |
右 | ! | 逻辑非 | !expr |
右 | - | 一元负号 | -expr |
右 | + | 一元正号 | +expr |
右 | * | 解引用 | *expr |
右 | & | 取地址 | &expr |
右 | () | 类型转换 | (type)expr |
右 | sizeof | 对象的大小 | sizeof expr |
右 | sizeof | 类型的大小 | sizeof(type) |
右 | sizeof | 参数包的大小 | sizeof…(name) |
右 | new | 创建对象 | new type |
右 | new[] | 创建数组 | new type[size] |
右 | delete | 删除对象 | delete expr |
右 | delete[] | 删除数组 | delete[] expr |
右 | noexcept | 能否抛出异常 | noexcept(expr) |
左 | ->* | 指向成员选择的指针 | ptr->*ptr_to_member |
左 | .* | 指向成员选择的指针 | obj.*ptr_to_member |
左 | * | 乘法 | expr * expr |
左 | / | 除法 | expr / expr |
左 | % | 取模(取余数) | expr % expr |
左 | + | 加法 | expr + expr |
左 | - | 减法 | expr - expr |
左 | « | 向左移位 | expr « expr |
左 | » | 向右移位 | expr » expr |
左 | < | 小于 | expr < expr |
左 | <= | 小于等于 | expr <= expr |
左 | > | 大于 | expr > expr |
左 | >= | 大于等于 | expr >= expr |
左 | == | 相等 | expr == expr |
左 | != | 不相等 | expr != expr |
左 | & | 位与 | expr & expr |
左 | ^ | 位异或 | expr ^ expr |
左 | | | 位或 | expr | expr |
左 | && | 逻辑与 | expr && expr |
左 | || | 逻辑或 | expr || expr |
右 | ?: | 条件 | expr ? expr : expr |
右 | =, *=, /=, %=, +=, -=, «=, »=, &=, |=, ^= | 赋值与复合赋值 | lvalue = expr等 |
右 | throw | 抛出异常 | throw expr |
左 | , | 逗号 | expr, expr |