https://zh.cppreference.com/w/cpp/language/expressions
表达式是运算符??和它们的操作数??的序列,它指定一项计算。
表达式的求值可以产生一个结果(比如 2 + 2 的求值产生结果 4),也可能产生副作用(比如对 std::printf("%d", 4) 的求值在标准输出上打印字符 '4')。
每个 C++ 表达式均被描述为具有两种独立的性质:类型和值类别。
1. 概述
- 值类别(左值 (lvalue)、右值 (rvalue)、泛左值 (glvalue)、纯右值 (prvalue)、亡值 (xvalue) (C++11 起))是根据表达式的值所进行的分类
- 实参和子表达式的求值顺序指定获得中间结果所用的顺序
运算符
常见运算符
赋值
a = b | a %= b | a <<= b |
a += b | a &= b | a >>= b |
a -= b | a |=b | |
a *= b | a ^= b | |
a /= b | a <<= b |
自增/自减
++a | a-- |
a++ | --a |
算术
+a | a +b | a%b | a<<b |
-a | a-b | a|b | a>>b |
~a | a/b | a&b | |
a*b | a^b |
逻辑
!a |
a&&b |
a||b |
比较
a == b | a < b | a > b |
a != b | a <= b | a >= b |
a <=> b |
成员访问
a[b] | a->b |
*a | a.b |
&a | a->*b |
a.*b |
其它
函数调用 | a(...) |
逗号 | a,b |
条件 | a ? b : c |
特殊运算符
static_cast | 转换一个类型为另一相关类型 |
dynamic_cast | 在继承层级中转换 |
const_cast | 添加或移除 cv 限定符 |
reinterpret_cast | 转换类型到无关类型 |
new | 创建有动态存储期的对象 |
delete | 销毁先前由 new 表达式创建的对象,并释放其所拥有的内存区域 |
sizeof | 查询类型的大小 |
sizeof... | 查询形参包的大小(C++11 起) |
typeid | 查询类型的类型信息 |
noexcept | 查询表达式是否能抛出异常(C++11 起) |
alignof | 查询类型的对齐要求(C++11 起) |
- 运算符优先级定义了运算符绑定到它的各个实参的顺序
- 替代表示是一些运算符的其他代用书写方式
- 运算符重载允许对用户定义的类指定各运算符的行为。
转换
- 标准转换是从一个类型到另一类型的隐式转换
- const_cast 转换
https://zh.cppreference.com/w/cpp/language/const_cast
在有不同 cv 限定的类型间转换。
const_cast< 目标类型 >( 表达式 )
返回 目标类型 类型的值。
#include <iostream>
struct type
{
int i;
type(): i(3) {}
void f(int v) const
{
// this->i = v; // 编译错误:this 是指向 const 的指针
const_cast<type*>(this)->i = v; // 只要该对象不是 const 就 OK
}
};
int main()
{
int i = 3; // 不声明 i 为 const
const int& rci = i;
const_cast<int&>(rci) = 4; // OK:修改 i
std::cout << "i = " << i << '\n';
type t; // 如果这是 const type t,那么 t.f(4) 会是未定义行为
t.f(4);
std::cout << "type::i = " << t.i << '\n';
const int j = 3; // 声明 j 为 const
[[maybe_unused]]
int* pj = const_cast<int*>(&j);
// *pj = 4; // 未定义行为
[[maybe_unused]]
void (type::* pmf)(int) const = &type::f; // 指向成员函数的指针
// const_cast<void(type::*)(int)>(pmf); // 编译错误:const_cast 不能用于成员函数指针
}
输出:
i = 4
type::i = 4
- static_cast 转换
https://zh.cppreference.com/w/cpp/language/static_cast
使用隐式和用户定义转换的组合来进行类型之间的转换。
static_cast<目标类型??>(表达式??)
返回 目标类型 类型的值。
#include <iostream>
#include <vector>
struct B
{
int m = 42;
const char* hello() const
{
return "Hello world,这里是 B!\n";
}
};
struct D : B
{
const char* hello() const
{
return "Hello world,这里是 D!\n";
}
};
enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };
int main()
{
// 1: 静态向下转换
D d;
B& br = d; // 通过隐式转换向上转换
std::cout << "1) " << br.hello();
D& another_d = static_cast<D&>(br); // 向下转换
std::cout << "1) " << another_d.hello();
// 2: 左值到亡值
std::vector<int> v0{1,2,3};
std::vector<int> v2 = static_cast<std::vector<int>&&>(v0);
std::cout << "2) 移动后,v0.size() = " << v0.size() << '\n';
// 3: 初始化转换
int n = static_cast<int>(3.14);
std::cout << "3) n = " << n << '\n';
std::vector<int> v = static_cast<std::vector<int>>(10);
std::cout << "3) v.size() = " << v.size() << '\n';
// 4: 弃值表达式
static_cast<void>(v2.size());
// 5. 隐式转换的逆转换
void* nv = &n;
int* ni = static_cast<int*>(nv);
std::cout << "5) *ni = " << *ni << '\n';
// 6. 数组到指针后随向上转换
D a[10];
[[maybe_unused]]
B* dp = static_cast<B*>(a);
// 7. 有作用域枚举到 int 或 float
E e = E::TWO;
int two = static_cast<int>(e);
std::cout << "7) " << two << '\n';
// 8. int 到枚举,枚举到另一枚举
E e2 = static_cast<E>(two);
[[maybe_unused]]
EU eu = static_cast<EU>(e2);
// 9. 指向成员指针向上转换
int D::*pm = &D::m;
std::cout << "9) " << br.*static_cast<int B::*>(pm) << '\n';
// 10. void* 到任何类型
void* voidp = &e;
[[maybe_unused]]
std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}
输出:
1) Hello world,这里是 B!
1) Hello world,这里是 D!
2) 移动后,v0.size() = 0
3) n = 3
3) v.size() = 10
5) *ni = 3
7) 2
9) 42
- dynamic_cast 转换
https://zh.cppreference.com/w/cpp/language/dynamic_cast
沿继承层级向上、向下及侧向,安全地转换到其他类的指针和引用。
dynamic_cast< 目标类型 >( 表达式 )
目标类型 - 指向完整类类型的指针,到完整类类型的引用,或指向(可有 cv 限定的)void 的指针
表达式 - 如果目标类型??是引用,那么是完整类类型的左值 (C++11 前)泛左值 (C++11 起)表达式,如果目标类型??是指针,那么是指向完整类类型的指针纯右值
如果转换成功,那么 dynamic_cast 就会返回目标类型??类型的值。如果转换失败且目标类型??是指针类型,那么它会返回该类型的空指针。如果转换失败且目标类型??是引用类型,那么它会抛出与类型 std::bad_cast 的处理块匹配的异常。
#include <iostream>
struct V
{
virtual void f() {} // 必须为多态,以使用带运行时检查的 dynamic_cast
};
struct A : virtual V {};
struct B : virtual V
{
B(V* v, A* a)
{
// 构造中转换(见后述 D 的构造函数中的调用)
dynamic_cast<B*>(v); // 良好定义:v 有类型 V*,V 是 B 的基类,产生 B*
dynamic_cast<B*>(a); // 未定义行为:a 有类型 A*,A 不是 B 的基类
}
};
struct D : A, B
{
D() : B(static_cast<A*>(this), this) {}
};
struct Base
{
virtual ~Base() {}
};
struct Derived: Base
{
virtual void name() {}
};
int main()
{
D d; // 最终派生对象
A& a = d; // 向上转换,可以用 dynamic_cast,但不是必须的
[[maybe_unused]]
D& new_d = dynamic_cast<D&>(a); // 向下转换
[[maybe_unused]]
B& new_b = dynamic_cast<B&>(a); // 侧向转换
Base* b1 = new Base;
if (Derived* d = dynamic_cast<Derived*>(b1); d != nullptr)
{
std::cout << "成功从 b1 向下转换到 d\n";
d->name(); // 可以安全调用
}
Base* b2 = new Derived;
if (Derived* d = dynamic_cast<Derived*>(b2); d != nullptr)
{
std::cout << "成功从 b2 向下转换到 d\n";
d->name(); // 可以安全调用
}
delete b1;
delete b2;
}
输出:
成功从 b2 向下转换到 d
- reinterpret_cast 转换
https://zh.cppreference.com/w/cpp/language/reinterpret_cast
通过重新解释底层位模式在类型间转换。
reinterpret_cast< 目标类型 >( 表达式 )
返回目标类型??类型的值。
#include <cassert>
#include <cstdint>
#include <iostream>
int f() { return 42; }
int main()
{
int i = 7;
// 指针到整数并转回
std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // 不能误用 static_cast
std::cout << "&i 的值是 " << std::showbase << std::hex << v1 << '\n';
int* p1 = reinterpret_cast<int*>(v1);
assert(p1 == &i);
// 到另一函数指针并转回
void(*fp1)() = reinterpret_cast<void(*)()>(f);
// fp1(); 未定义行为
int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
std::cout << std::dec << fp2() << '\n'; // 安全
// 通过指针的类型别名化
char* p2 = reinterpret_cast<char*>(&i);
std::cout << (p2[0] == '\x7' ? "本系统是小端的\n"
: "本系统是大端的\n");
// 通过引用的类型别名化
reinterpret_cast<unsigned int&>(i) = 42;
std::cout << i << '\n';
[[maybe_unused]] const int &const_iref = i;
// int &iref = reinterpret_cast<int&>(const_iref); // 编译错误——不能去除 const
// 必须用 const_cast 代替:int &iref = const_cast<int&>(const_iref);
}
输出:
&i 的值是 0x7fff352c3580
42
本系统是小端的
42
- 显式类型转换,可使用 C 风格写法和函数风格写法
https://zh.cppreference.com/w/cpp/language/explicit_cast
用显式和隐式转换的组合进行类型之间的转换。
( 目标类型 ) 表达式 (1)
目标类型 ( 表达式列表??(可选) ) (2)
目标类型 { 表达式列表??(可选) } (3) (C++11 起)
模板名 ( 表达式列表??(可选) ) (4) (C++17 起)
模板名 { 表达式列表??(可选) } (5) (C++17 起)
auto ( 表达式 ) (6) (C++23 起)
auto { 表达式 } (7) (C++23 起)
返回 目标类型 类型的值。
#include <cassert>
#include <iostream>
double f = 3.14;
unsigned int n1 = (unsigned int)f; // C 风格转型
unsigned int n2 = unsigned(f); // 函数风格转型
class C1;
class C2;
C2* foo(C1* p)
{
return (C2*)p; // 转换不完整类型到不完整类型
}
void cpp23_decay_copy_demo()
{
auto inc_print = [](int& x, const int& y)
{
++x;
std::cout << "x:" << x << ", y:" << y << '\n';
};
int p{1};
inc_print(p, p); // 打印 x:2 y:2,因为此处的形参 y 是 p 的别名
int q{1};
inc_print(q, auto{q}); // 打印 x:2 y:1,auto{q} (C++23) 转型为纯右值,
// 因此形参 y 是 q 的副本(而非 q 的别名)
}
// 在这个例子中,C 风格转型被转译成 static_cast
// 尽管它的作用也可以和 reinterpret_cast 一致
struct A {};
struct I1 : A {};
struct I2 : A {};
struct D : I1, I2 {};
int main()
{
D* d = nullptr;
// A* a = (A*)d; // 编译时错误
A* a = reinterpret_cast<A*>(d); // 可以编译
assert(a == nullptr);
cpp23_decay_copy_demo();
}
输出:
x:2 y:2
x:2 y:1
- 用户定义转换使得可以指定源自用户定义类的转换
https://zh.cppreference.com/w/cpp/language/cast_operator
启用从类类型到另一类型的隐式转换或显式转换。
转换函数的声明与非静态成员函数或成员函数模板类似,但没有形参和显式返回类型,并拥有下列形式的名字:
operator 转换类型标识 (1)
explicit operator 转换类型标识 (2) (C++11 起)
explicit ( 表达式 ) operator 转换类型标识 (3) (C++20 起)
1) 声明用户定义的转换函数,它参与所有隐式和显式转换。
2) 声明用户定义的转换函数,它只会参与直接初始化和显式转换。
3) 声明用户定义的转换函数,它是有条件显式的。
转换类型标识是一个类型标识,但它的声明符中不能出现函数与数组运算符 []
或 ()(因此转换到例如数组的指针的类型就需要使用类型别名、typedef 或标识模板:见下文)。
无论是否使用 typedef,转换类型标识都不能代表数组或函数类型。
//1
struct X
{
// 隐式转换
operator int() const { return 7; }
// 显式转换
explicit operator int*() const { return nullptr; }
// 错误:转换类型标识中不能出现数组运算符
// operator int(*)[3]() const { return nullptr; }
using arr_t = int[3];
operator arr_t*() const { return nullptr; } // 可以通过 typedef 转换
// operator arr_t () const; // 错误:任何情况下都不能转换到数组
};
int main()
{
X x;
int n = static_cast<int>(x); // OK:设 n 为 7
int m = x; // OK:设 m 为 7
int* p = static_cast<int*>(x); // OK:设 p 为空
// int* q = x; // 错误:没有隐式转换
int (*pa)[3] = x; // OK
}
//2
struct To
{
To() = default;
To(const struct From&) {} // 转换构造函数
};
struct From
{
operator To() const {return To();} // 转换函数
};
int main()
{
From f;
To t1(f); // 直接初始化:调用构造函数
// 注意:如果转换构造函数不可用,那么就会选择隐式的复制构造函数,并调用转换函数以准备它的实参
// To t2 = f; // 复制初始化:有歧义
// 注意:如果转换函数来自非 const 类型,例如 From::operator To();,
// 那么它在这种情况下会替代构造函数被选中
To t3 = static_cast<To>(f); // 直接初始化:调用构造函数
// const To& r = f; // 引用初始化:有歧义
}
//3
struct D;
struct B
{
virtual operator D() = 0;
};
struct D : B
{
operator D() override { return D(); }
};
int main()
{
D obj;
D obj2 = obj; // 不会调用 D::operator D()
B& br = obj;
D obj3 = br; // 通过虚派发调用 D::operator D()
}
//4
struct B {};
struct X : B
{
operator B&() { return *this; };
};
int main()
{
X x;
B& b1 = x; // 不会调用 X::operatorB&()
B& b2 = static_cast<B&>(x); // 不会调用 X::operatorB&
B& b3 = x.operator B&(); // 调用 X::operatorB&
}
//5
& x.operator int * a; // 错误:解析成 & (x.operator int*) a
// 而不是 & (x.operator int) * a
operator int [[noreturn]] (); // 错误:noreturn 属性应用到了类型
//6
struct X
{
operator int(); // OK
operator auto() -> short; // 错误:尾随返回类型不是此类语法的一部分
operator auto() const { return 10; } // OK:推导的返回类型
operator decltype(auto)() const { return 10l; } // OK:推导的返回类型
};