第 4 章 表达式
表达式 expression
由一个或多个运算对象 operand
组成,对表达式求值将得到一个结果。字面值和变量是最简单的表达式,运算符 operator
将一个或多个对象组合起来可以生成复杂的表达式。
4.1 基础
4.1.1 基本概念
对于复杂的表达式,需要了解运算符的优先级 precedence
、结合律 associativity
及运算对象的求职顺序 order of evaluation
。
数值计算过程可能会涉及到类型提升 promoted
。
左值与右值
左值 lvalue
和右值 rvalue
的概念来源于 C 语言,但在 C++ 中不等同于是否可以出现在赋值语句左侧、右侧。
当对象被当做右值时使用的是值,当做左值时使用的是它的身份(地址,内存中的位置)。需要右值的时候可以用一个左值代替。
不同运算符对于参与运算的对象有不同的要求。
赋值运算左侧的运算对象需要是一个左值,得到的结果也是一个左值。
取地址符作用于一个左值,返回指向左值运算对象的指针,这个指针是一个右值。
内置解引用运算符、下标运算符、迭代器解引用运算符、string 和 vector 下标运算符得到的都是左值。
内置类型和迭代器的递增递减运算符作用于左值,其前置版本(前缀运算符)得到的也是左值。
使用 decltype 的时候,是区分左值右值的。当表达式的求值结果是一个左值,decltype 作用于该表达式将得到引用类型。
4.1.2 优先级与结合律
复合表达式 compound expression
是含有两个或多个运算符的表达式。
4.1.3 求值顺序
注意求值顺序与优先级和结合律无关。多数算符没有明确规定求值顺序。
四种明确规定了求值顺序的算符是 &&
、||
、?:
和 ,
。
4.2 算术运算符
所有算符满足左结合律。
C++11 规定整数商向 0 取整。
对于取余,需要保证,对于非 0 的 n 有,
(m / n) * n + m % n == m
#include <iostream>
using namespace std;
int main() {
{
int a = 32;
int b = 5;
cout << "a = " << a << ", b = " << b << ", a/b = " << (a / b) << ", a%b = " << (a % b) << '\n';
}
{
int a = 32;
int b = -5;
cout << "a = " << a << ", b = " << b << ", a/b = " << (a / b) << ", a%b = " << (a % b) << '\n';
}
{
int a = -32;
int b = 5;
cout << "a = " << a << ", b = " << b << ", a/b = " << (a / b) << ", a%b = " << (a % b) << '\n';
}
{
int a = -32;
int b = -5;
cout << "a = " << a << ", b = " << b << ", a/b = " << (a / b) << ", a%b = " << (a % b) << '\n';
}
return 0;
}
a = 32, b = 5, a/b = 6, a%b = 2
a = 32, b = -5, a/b = -6, a%b = 2
a = -32, b = 5, a/b = -6, a%b = -2
a = -32, b = -5, a/b = 6, a%b = -2
4.3 逻辑和关系运算符
短路求值 short-circuit evaluation
。
注意当比较运算符一侧的变量不是布尔类型时,不应使用 true 字面值作为比较对象,因为 true 会被自动转换成 1。
#include <iostream>
using namespace std;
int main() {
int a = 2;
cout << (a == true) << '\n'; // 0
cout << (!a) << '\n'; // 0
return 0;
}
4.4 赋值运算符
赋值运算符返回左值。左右运算对象类型不同时将右侧转化为左侧。
C++11 允许初始值列表作为赋值语句的右侧运算对象。
赋值运算满足右结合律。
注意赋值运算的优先级比关系运算低。
4.5 递增和递减运算符
前置版本返回对象本身的左值,后置版本返回原始值的副本右值。
注意经常会混用解引用和递增算符。此时递增算符的优先级更高。
*p++ // 相当于 *(p++)
注意求值顺序可能是未定义的!这在有递增、递减算符的表达式中尤其重要。
4.6 成员访问运算符
vec.size();
pvec->size();
注意解引用算符的优先级比成员访问算符低,因此
(*pvec).size() // 等价于 pvec->size();
*pvec.size() // 则表示对于 pvec 对象先调用成员函数 size() 后解引用
箭头运算符作用于一个指针类型的运算对象,结果将会是一个左值。
点运算符,如果所属对象是左值则得到左值,反之得到右值。
4.7 条件运算符
cond ? expr1 : expr2
当 expr1 和 expr2 是左值或是能转换成同一左值类型时,运算结果是左值,否则结果是右值。
条件运算符满足右结合律。
注意条件算符优先及很低,和输出的 <<
混用时,需要加括号。
4.8 位运算符
运算对象如果是“小整型”则它的值可能会被自动提升成较大的整数类型。
注意符号位处理没有明确规定,因此尽量使用无符号整数。
如果符号位的值为负,则如何处理符号位依赖于机器。
4.9 sizeof 运算符
sizeof 运算符返回一个表达式或一个类型名字所占的字节数。满足右结合律,其所得到的值是一个 size_t 类型的常量表达式。
sizeof (type)
sizeof expr
注意 sizeof 并不实际计算运算对象的值!
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int arr[10];
int arr2[5][10];
class A {
private:
int a;
long long b;
public:
char c;
void fvoid() { cout << "call fvoid()\n"; }
long long fll() { cout << "call fll()\n"; return 1LL; };
// never called in main.
}objA;
vector<int> vec_a = {1, 2, 3, 4, 5};
vector<int> vec_b = {1, 2, 3};
string s_a = "12345";
string s_b = "abc";
int main() {
cout << sizeof(char) << '\n'; // 1
// cout << sizeof int << '\n'; // ce
cout << sizeof(int) << '\n'; // 4
cout << sizeof(long long) << '\n'; // 8
// cout << sizeof(void) << '\n'; // 1 with warning
cout << sizeof(char*) << '\n'; // 4
cout << sizeof(long long*) << '\n'; // 4
cout << sizeof(void*) << '\n'; // 4
// cout << sizeof(int[]) << '\n'; // ce
cout << sizeof(int[50]) << '\n'; // 200
cout << sizeof arr << '\n'; // 40
cout << sizeof(arr) << '\n'; // 40
cout << sizeof arr2 << '\n'; // 200
// cout << sizeof A << '\n'; // ce
cout << sizeof(A) << '\n'; // 24
cout << sizeof A() << '\n'; // 24
cout << sizeof(objA) << '\n'; // 24
cout << sizeof objA << '\n'; // 24
// cout << sizeof objA.fvoid() << '\n'; // 1 with warning
cout << sizeof objA.fll() << '\n'; // 8
cout << sizeof(objA.fll()) << '\n'; // 8
cout << sizeof A::fll << '\n'; // 8
// cout << sizeof A::b << '\n';
cout << sizeof A::c << '\n'; // 1
cout << sizeof objA.c << '\n'; // 1
cout << sizeof vec_a << '\n'; // 12
cout << sizeof vec_b << '\n'; // 12
cout << sizeof s_a << '\n'; // 24
cout << sizeof s_b << '\n'; // 24
return 0;
}
4.10 逗号运算符
逗号运算符 comma operator
含有两个运算对象,首先对左侧运算对象求值之后对右侧运算对象求值。逗号表达式的结果是右侧表达式的结果,如果右侧运算对象是左值,则逗号表达式的返回值也是左值。
4.11 类型转换
如果两种类型可以相互转换 conversion
则我们说它们是关联的。
隐式转换 implicit conversion
。
何时发生隐式类型转换:
大多数表达式中比 int 小的整型会被提升。
条件中非布尔值类型会被转换成布尔类型。
初始值转换成变量类型。
算术运算或关系运算对象有多种类型则会转换成同一种类型。
函数调用时也会发生类型转换。
4.11.1 算术转换
arithmetic conversion
整型提升 integral promotion
。
比 int 小的类型能存在 int 里就提升成 int,否则提升成 unsigned int。
比较大的 char 类型(wchar_t、char16_t、char32_t)提升成 int、unsigned int、long、unsigned long、long long 和 unsigned long long 中最小的类型,且可以容纳原类型所有可能值。
4.11.2 其他隐式类型转换
数组在绝大多数时候会转换成指向数组首元素的指针,除了被当做 decltype 的参数,作为 取地址&、sizeof 和 typeid 等运算符的对象时。
指针转换:0 和 nullptr 能转换成任意类型指针。任意非常量指针可以转换成 void*
,任意指针可以转化为 const void*
。
算术或指针类型转换为 bool 类型。
非常量指针转化为对应的常量指针。
类类型定义的转换。
4.11.3 显式转换
强制类型转换 cast
。
任何具有明确定义的类型转换,只要不包含底层 const 则可以用 static_cast。
const_cast 可以得到同类型去掉底层 const 的指针。
reinterpret_cast 为运算对象的位模式提供较低层次上的重新解释。
dynamic_cast 支持运行时类型识别。
旧式的强制类型转换
type (expr);
(type) expr;
如果旧式类型转换换成 const_cast 或 static_cast 也合法,则其行为与对应的命名转换一致,否则其拥有 reinterpret_cast 类似的功能。
4.12 运算符优先级表
优先级从上到下从高到低
优先级 | 结合律(只标右) | 运算符 | 功能 | 用法 |
---|---|---|---|---|
1 | :: |
全局作用域 | ::name |
|
1 | :: |
类作用域 | class::name |
|
1 | :: |
命名空间作用域 | namespace::name |
|
2 | . |
成员选择 | object.member |
|
2 | -> |
成员选择 | ||
2 | [] |
下标 | ||
2 | () |
函数调用 | ||
2 | () |
类型构造 | ||
3 | 右 | ++ |
后递增 | |
3 | 右 | -- |
后递减 | |
3 | 右 | typeid |
类型 ID | |
3 | 右 | typeid |
运行时类型 ID | |
3 | 右 | static_cast |
||
3 | 右 | dynamic_cast |
||
3 | 右 | const_cast |
||
3 | 右 | reinterpret_cast |
||
4 | 右 | ++ |
前递增 | |
4 | 右 | -- |
前递减 | |
4 | 右 | ~ |
||
4 | 右 | ! |
||
4 | 右 | - |
一元负号 | |
4 | 右 | + |
一元正号 | |
4 | 右 | * |
解引用 | |
4 | 右 | & |
取地址 | |
4 | 右 | () |
类型转换 | |
4 | 右 | sizeof |
对象大小 | |
4 | 右 | sizeof |
类型大小 | |
4 | 右 | sizeof... |
参数包大小 | |
4 | 右 | new |
||
4 | 右 | new[] |
||
4 | 右 | delete |
||
4 | 右 | delete[] |
||
4 | 右 | noexcept |
能否抛出异常 | |
5 | ->* |
|||
5 | .* |
|||
6 | * |
|||
6 | / |
|||
6 | % |
|||
7 | + |
|||
7 | - |
|||
8 | << |
|||
8 | >> |
|||
9 | < |
|||
9 | <= |
|||
9 | > |
|||
9 | >= |
|||
10 | == |
|||
10 | != |
|||
11 | & |
|||
12 | ^ |
|||
13 | ` | ` | ||
14 | && |
|||
15 | ` | ` | ||
16 | 右 | ?: |
||
17 | 右 | = |
||
18 | 右 | *= , /= , *= , += , -= , <<= , >>= , &= , ` |
=, ^=` |
|
19 | 右 | throw |
||
20 | , |
Next: 《C++ Primer》 拾遗 第 5 章 语句