4.1 基础
- 运算对象转换:小整数类型会被提升为较大的整数类型 。(如 bool b=true; bool b2 = -b; b在求值(-b)之前会转换为int类型,即b=1)
- 重载运算符:当运算符作用在类类型的运算对象时,用户可以自行定义其含义。
- 左值和右值:当一个对象被用作右值的时候,用的是对象的值(内容);被用做左值时,用的是对象的身份(在内存中的位置)。
优先级:
- 高优先级运算符的运算对象要比低优先级运算符的运算对象更为紧密地组合在一起。如果优先级相同,则其组合规则由结合律确定。
- 算术运算符满足 左结合律,意味着如果运算符的优先级相同,将按照从左向右的顺序组合运算对象。
求值顺序:
优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值。
int i = f1() + f2(); // 如果f1()、f2()都对同一对象进行了修改,因为顺序不确定,所以会编译出错,显示未定义。
int i = 0;
cout << i << " " << ++i << endl; // <<运算符只根据从左到右输出(i)和(++i),但没有规定(i)和(++i)哪个先计算。因此是未定的的。
俩条经验准则:
- 拿不准的时候最好用 括号 来强制让表达式的组合关系符合程序逻辑的要求。
- 如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象。
注:有4种运算符明确规定了运算对象的求值顺序。
- 逻辑与( && )运算符 (只有当左侧运算对象的值为真时,才继续求右侧对象的值)
- 逻辑或( || )运算符 (只有当左侧运算对象的值为假时,才继续求右侧对象的值)
- 条件( ? : )运算符
- 逗号( , )运算符
4.2 算术运算符
- 溢出:当计算的结果超出该类型所能表示的范围时就会产生溢出。
- bool类型不应该参与计算
- 取余运算m%n,结果符号与m相同
- 除法运算m/n,结果符号与正常除法运算得出结果的符号相同
运算符 | 功能 | 用法 |
+ | 一元正号 | + expr |
- | 一元负号 | - expr |
* | 乘法 | expr * expr |
/ | 除法 | expr / expr |
% | 求余 | expr % expr |
+ | 加法 | expr + expr |
- | 减法 | expr - expr |
4.3 逻辑和关系运算符
结合律 | 运算符 | 功能 | 用法 |
右 | ! | 逻辑非 | ! expr |
左 | < | 小于 | expr < expr |
左 | <= | 小于等于 | expr <= expr |
左 | > | 大于 | expr > expr |
左 | >= | 大于等于 | expr >= expr |
左 | == | 相等 | expr == expr |
左 | != | 不相等 | expr != expr |
左 | && | 逻辑与 | expr && expr |
左 | || | 逻辑或 | expr || expr |
短路求值:逻辑与(&&)运算符和逻辑或(||)运算符 都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象 无法确定表达式的结果时 才会计算右侧运算对象的值。
4.4 赋值运算符
- 赋值运算的返回的结果 是它的左侧运算对象,并且是一个左值。类型也就是左侧对象的类型。
- 如果赋值运算的左右侧运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。
- 赋值运算符满足右结合律,这点和其他二元运算符不一样。( 如 ival = jval = 0; 等价于ival = (jval = 0) )
- 赋值运算优先级比较低,所以通常需要给赋值部分加上括号。
- 复合赋值运算符:复合运算符只求值一次,普通运算符求值两次。(对性能有一点点点点影响)
4.5 递增和递减运算符
- 前置版本:j = ++i;i先加1,后赋值 j。
- 后置版本:j = i++;i先赋值j,后加1。
cout<< *iter++ <<endl;
cout<< *iter <<endl; // 这俩条语句 等价于 上面那条语句(但是上面写法更简洁)
++iter;
建议:
- 优先使用前置版本,相比之下 后置版本多一步储存原始值的步骤。(除非需要变化前的值)
- 简介是一种美德,追求简洁能降低程序出错可能性。
4.6 成员访问运算符
表达式 ptr->mem 等价于 (*ptr).mem
注意:(.)运算符优先级大于(*)运算符,混合使用时记得加括号。
4.7 条件表达式
条件运算符(?:) 允许我们把简单的 if-else逻辑 嵌入到单个表达式中去,按照如下形式:cond? expr1: expr2
if (grade < 60) {
cout << "fail";
}
else {
cout << "pass";
}
// 等价于
cout << ( (grade<60) ? "fail" : "pass" );
可以嵌套使用,满足右结合律,从右向左顺序组合
finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";
// 等价于
finalgrade = (grade > 90) ? "high pass" : ((grade < 60) ? "fail" : "pass");
注:输出表达式中 使用条件运算符 记得加括号,条件运算符优先级太低。
cout << ((grade < 60) ? "fail" : "pass"); // 正确:输出pass或者fail
cout << (grade < 60) ? "fail" : "pass"; // 错误: 输出1或者0! (等价于cout<<(grade<60); cout ? "fail":"pass";)
cout << grade < 60 ? "fail" : "pass"; // 错误: 试图比较cout和60 (等价于 cout<
4.8 位运算符
- 位运算符提供了 检查和设置 二进制位的功能
- 位运算符作用于 整数类型 的运算对象
运算符 | 功能 | 用法 |
~ | 位求反 | ~ expr |
<< | 左移 | expr << expr |
>> | 右移 | expr >> expr |
& | 位与 | expr && expr |
^ | 位异或 | expr ^ expr |
| | 位或 | expr | expr |
注:
- 二进制位向左移(<<)或者向右移(>>),移出边界外的位就被舍弃掉了。
- 有符号数负值可能移位后变号,所以强烈建议位运算符仅用于无符号数。
- 位运算符满足左结合律,优先级介于中间,使用时尽量加括号。
应用示例:
unsigned long quiz1 = 0; // 每一位代表一个学生是否通过考试(1代表通过,0代表未通过)
1UL << 12;
quiz1 |= (1UL << 12); // 将第12个学生置为已通过 (将第12位置1)
quiz1 &= ~(1UL << 12); // 将第12个学生修改为未通过 (将第12位置0)
bool stu12 = quiz1 & (1UL << 12); // 判断第12个学生是否通过 (判断第12位是0还是1)
4.9 sizeof运算符
- 返回 一条表达式或一个类型名字 所占的字节数。
- 两种形式:sizeof (type) ;sizeof expr
- 返回的类型是 size_t 的常量表达式。
- sizeof运算符并不实际计算其运算对象的值。
Sales_data data, *p;
sizeof(Sales_data); // 存储Sales_data 类型的对象所占的空间大小
sizeof data; // data的类型的大小,即sizeof(Sales_data)
sizeof p; // 指针所占的空间大小.
sizeof *p; // p所指类型的空间大小,即sizeof (Sales_ _data)
sizeof data.revenue; //Sales_data的revenue成员对应类型的大小
sizeof Sales_data :: revenue; //同上
// 应用
int arr[10];
sizeof(arr); // 返回整个数组所占空间的大小
sizeof(*arr); // 返回第一个元素类型所占空间大小
constexpr size_t sz = sizeof(arr)/sizeof(*arr); // 求得数组元素个数
int arr2[sz]; // 定义一个同样大小的数组
4.10 逗号运算符
从左向右依次求值,左侧求值结果丢弃,逗号运算符结果是右侧表达式的值。
4.11 类型转换
算术转换:
表达式中运算符的运算对象将转换成最宽的类型。 (如:表达式中有整形类型和浮点类型时,整数值会转换成相应的浮点类型)
整型提升:
- 常见的char、bool、short能存在int就会转换成int,否则提升为unsigned int
- 较大的char类型(wchar_t,char16_t,char32_t)提升为整型中int,long,long long ……最小的,且能容纳原类型所有可能值的类型。
隐式类型转换:
设计为尽可能避免损失精度,即转换为更精细类型。
普通隐式类型转换:
- 比 int类型小的整数值先提升为较大的整数类型。
- 在条件中,非布尔类型转换成布尔类型。
- 初始化过程中,初始值转换成变量的类型。
- 赋值语句中,右侧运算对象转换成左侧运算对象的类型。
- 如果算术运算或者关系运算的运算对象有多种类型,要转换成同一种类型。
- 函数调用时也会发送类型转换。
特殊隐式类型转换:
- 组转换成指针:在大多数用到数组的表达式中,数组自动转换成 指向数组首元素的指针。
- 指针的转换:常量整数值0 或者 字面值nullptr 能转换成任意指针类型,指向任意非常量的指针能转换成void*,指向任意对象的指针能转换成const void*。
- 转换布尔类型:存在一种从算术类型或指针类型向布尔类型自动转换的机制。如果指针或算术类型的值为0,转换结果是false;否则转换结果是true。
- 转换成常量:允许将指向非常量类型的 指针或者引用 ,转换成指向相应的常量类型的 指针或引用。
- 类类型定义的转换:类类型能定义由编译器自动执行的转换,不过 编译器每次只能执行一种类类型 的转换。如果同时提出多个转换请求,这些请求将被拒绝。
// 类类型转换的例子
string s,t;
t = "a value"; // 字符串字面值转换成string类型
while (cin >> s) // while的条件部分把cin转换成布尔值
显式类型转换:
格式:cast-name(expression);
解释:type是转换的目标类型,expression是要转换的值。(如果type是引用类型,则结果是左值)
C强制类型转换:(type) expr
C++强制类型转换:
- static_cast:任何明确定义的类型转换,只要不包含底层const,都可以使用。 double slope = static_cast(j);
- dynamic_cast:支持运行时类型识别。
- const_cast:只能改变运算对象的底层const,一般可用于去除const性质。 const char *pc;char *p = const_cast(pc);(只有其可以改变常量属性)
- reinterpret_cast:通常为运算对象的位模式提供低层次上的重新解释。
建议:每次书写了一条强制类型转换语句,都应该反复斟酌 能否以其他方式 实现相同的目标。就算实在无法避免,也应该尽量 限制类型转换值的作用域,并且记录 对相关类型的所有假定,这样可以减少错误发生的机会。
4.12 运算符优先级表
优先级 | 结合律和运算符 | 功能 | 用法 | 参考页码 |
1 | 左 :: | 全局作用域 | ::name | 256 |
左 :: | 类作用域 | class::name | 79 | |
左 :: | 命名空间作用域 | namespace::name | 74 | |
2 | 左 . | 成员运算符 | object. member | 20 |
左 -> | 成员选择 | pointer->member | 98 | |
左 [ ] | 下标 | expr[expr] | 104 | |
左 ( ) | 函数调用 | name(expr_list) | 20 | |
左 ( ) | 类型构造 | type(expr_list) | 145 | |
3 | 右 ++ | 后置递增运算 | lvalue++ | 131 |
右 -- | 后置递减运算 | lvalue-- | 131 | |
右 typeid | 类型ID | typeid(type) | 731 | |
右 typeid | 运行时类型ID | typeid(expr) | 731 | |
右 explicit cast | 类型转换 | cast_name<type>(expr) | 144 | |
4 | 右 ++ | 前置递增运算 | ++lvalue | 131 |
右 -- | 前置递减运算 | --lvalue | 131 | |
右 ~ | 位求反 | ~expr | 136 | |
右 ! | 逻辑非 | !expr | 126 | |
右 - | 一元负号 | -expr | 124 | |
右 + | 一元正号 | +expr | 124 | |
右 * | 解引用 | *expr | 48 | |
右 & | 取地址 | &lvalue | 47 | |
右 () | 类型转换 | (type)expr | 145 | |
右 sizeof | 对象的大小 | sizeof type | 139 | |
右 sizeof | 类型的大小 | sizeof(type) | 139 | |
右 sizeof... | 参数包的大小 | sizeof...(name) | 619 | |
右 new | 创建对象 | new type | 407 | |
右 new[ ] | 创建数组 | new type[size] | 407 | |
右 delete | 释放对象 | delete expr | 409 | |
右 delete[ ] | 释放数组 | delete[ ] expr | 409 | |
右 noexcept | 能否抛出异常 | noexcept(expr) | 690 | |
5 | 左 ->* | 指向成员选择的指针 | ptr ->* ptr_to_member | 740 |
左 .* | 指向成员选择的指针 | obj .* ptr_to_member | 740 | |
6 | 左 * | 乘法 | expr * expr | 124 |
左 / | 除法 | expr / expr | 124 | |
左 % | 取模(取余) | expr % expr | 124 | |
7 | 左 + | 加法 | expr + expr | 124 |
左 - | 剑法 | expr - expr | 124 | |
8 | 左 << | 向左移位 | expr << expr | 136 |
左 >> | 向右移位 | expr >> expr | 136 | |
9 | 左 < | 小于 | expr < expr | 126 |
左 <= | 小于等于 | expr <= expr | 126 | |
左 > | 大于 | expr > expr | 126 | |
左 >= | 大于等于 | expr >= expr | 126 | |
10 | 左 == | 相等 | expr == epxr | 126 |
左 != | 不相等 | expr != expr | 126 | |
11 | 左 & | 位与 | expr & expr | 136 |
12 | 左 ^ | 位异或 | expr ^ expr | 136 |
13 | 左 | | 位与 | expr | expr | 136 |
14 | 左 && | 逻辑与 | expr && expr | 126 |
15 | 左 || | 逻辑或 | expr || expr | 126 |
16 | 右 ? : | 条件 | expr ? expr : expr | 134 |
17 | 右 = | 赋值 | lvalue = expr | 129 |
18 | 右 *=,/=,%= | 复合赋值 | lvalue *= expr等 | 129 |
右 +=,-= | 129 | |||
右 <<=,>>= | 129 | |||
右 &=,|=,^= | 129 | |||
19 | 右 throw | 抛出异常 | throw expr | 173 |
20 | , | 逗号 | expr,expr | 140 |