C++20 / QUICK REFERENCE
忘了写法,来这里查。
把常用语法放在一页:看一句提醒,对照一个小例子,需要时再打开详细章节。后半部分包含以后会遇到的写法,不用现在全部学完。
没有匹配的写法。试试“引用”“循环”或英文关键词;也可以点击“显示全部”。
01
声明、初始化,还是赋值?
- 下面四种带7的写法都在建立并初始化变量;变量已经存在之后的a = 9才是赋值,不是重新声明。
- int zero{}得到0,而块内的int missing;没有可直接读取的确定初值。
- int bad{2.5};和int bad = {2.5};会因窄化被拒绝;显式static_cast<int>(2.5)得到2,但主动转换仍需自己保证输入在目标范围内。
void syntax_declaration() {
int a{7};
int b = 7;
int c(7);
int d = {7};
int zero{};
a = 9;
std::cout << a << ' ' << b << ' ' << c << ' ' << d << ' ' << zero << '\n';
}运行结果
9 7 7 7 0
02
const的位置与auto的三种接法
- const int x{7};与int const x{7};定义相同类型的只读整数;int const{7};缺少变量名,不能这样定义一个变量。
- 本例auto copy{x}建立可写int副本;auto& edit借用可写copy,const auto& read从只读路径借用同一个copy。
- 改edit会让copy和read都观察到10,原x仍是7;不要把只读引用误当成保存旧值的快照。
void syntax_const_auto() {
const int x{7};
int const y{7};
auto copy{x};
auto& edit{copy};
const auto& read{copy};
edit = 10;
std::cout << x << ' ' << y << ' ' << copy << ' ' << read << '\n';
}运行结果
7 7 10 10
03
先看运算类型,再看接收结果的类型
- 整数除法向零截断:5 / 2是2,−5 / 2是−2;5 % 2得到余数1,乘除通常先于加减。
- 先把5转换为double再除得到2.5,除完再转换只能把已经得到的2变成double。
- 本例采用小整数和非零除数;有符号溢出、除以零以及最小有符号整数除以−1都不是可用来猜输出的练习。
void syntax_arithmetic() {
int n{5};
std::cout << n / 2 << ' ' << n % 2 << ' ' << -n / 2 << '\n';
std::cout << static_cast<double>(n) / 2 << ' '
<< static_cast<double>(n / 2) << '\n';
std::cout << 2 + 3 * 4 << ' ' << (2 + 3) * 4 << '\n';
}运行结果
2 1 -2
2.5 2
14 20
04
if选择路径,switch选择标签入口
- if检查条件,else处理未满足的路径;==比较,=赋值,&&与||从左到右短路,!取逻辑反。
- switch按整数或枚举值进入匹配的case,没有匹配时进入default;进入后会继续执行,直到break等控制转移。
- 漏写break可能继续执行后一个case的语句,不会自动在下个标签停下。
void syntax_selection() {
int n{4};
if (n > 0 && n % 2 == 0) {
std::cout << "positive even\n";
} else {
std::cout << "other\n";
}
switch (n) {
case 4: std::cout << "four\n"; break;
default: std::cout << "not four\n"; break;
}
}运行结果
positive even
four
05
while先检查,do/while先做一次
- while先检查再执行,本例第一次条件就是假,a保持0;do/while先执行循环体,再检查,所以b变成1。
- do/while末尾的分号属于语法,普通while后面不要误加一个表示空循环体的分号。
- 写循环时一起检查初值、条件和更新;条件永远不会变假时,程序可能一直重复。
void syntax_while_do() {
int a{0};
while (a < 0) {
++a;
}
int b{0};
do {
++b;
} while (b < 0);
std::cout << a << ' ' << b << '\n';
}运行结果
0 1
06
普通for:初始化;条件;更新
- for的三段依次是初始化、继续条件、每轮更新;执行顺序为初始化一次→条件→循环体→更新→再判断。
- 下面用下标访问values[i],i依次为0、1、2;条件必须是i < values.size(),不是<=。
- 需要位置时用下标循环;只需要元素时可对照下一条的范围循环,修改原元素要用auto&或直接给values[i]赋值。
void syntax_index_for() {
std::array<int, 3> values{2, 4, 6};
int total{0};
for (std::size_t i{0}; i < values.size(); ++i) {
total += values[i];
}
std::cout << total << '\n';
}运行结果
12
07
范围for:副本、只读借用、可写借用
- 对本例int元素,for(auto value : values)每轮复制一个值,修改value不会修改原元素;循环变量不是下标。
- for(auto& value : values)可以修改原元素,for(const auto& value : values)借用读取且不允许经value改写。
- 第一轮把副本各加1,累计15但原数组仍为2、4、6;第二轮才把原值加倍,最后只读累计得到24。
void syntax_range_for() {
std::array<int, 3> values{2, 4, 6};
int copied_total{0};
for (auto value : values) {
value += 1;
copied_total += value;
}
for (auto& value : values) {
value *= 2;
}
int stored_total{0};
for (const auto& value : values) {
stored_total += value;
}
std::cout << copied_total << ' ' << stored_total << '\n';
}运行结果
15 24
08
continue跳过本轮,break退出一层
- continue跳过循环体剩余语句;在普通for里接着执行更新表达式,再检查条件。
- break退出最近一层循环或switch,不会自动结束所有外层循环或整个函数。
- 本例跳过2、遇到4就退出,因此只累计0、1、3,得到4。
void syntax_loop_jumps() {
int total{0};
for (int i{0}; i < 6; ++i) {
if (i == 2) { continue; }
if (i == 4) { break; }
total += i;
}
std::cout << total << '\n';
}运行结果
4
09
参数副本、引用参数和返回值
- int value按值接收副本,int& value借用并可修改调用者整数,const int& value借用读取。
- return交回本次调用的结果,不会自动写回实参;void表示没有返回值,需要提前离开时写return;。
- 返回局部整数的值可以得到独立结果,但不要返回它的引用或地址供调用者继续访问。
int next_value(int value) { return value + 1; }
void add_one(int& value) { value += 1; }
int read_value(const int& value) { return value; }
void syntax_functions() {
int x{4};
int result{next_value(x)};
std::cout << x << ' ' << result << '\n';
add_one(x);
std::cout << x << ' ' << read_value(x) << '\n';
}运行结果
4 5
5 5
10
const在星号哪边,限制就不同
- const int*限制经指针写目标,但允许改指向;int* const固定指向,但能修改合适的可写目标;const int* const同时限制两者。
- 本例*fixed把x改为7,随后read改借y,所以三个读取结果是9、7、7;const路径不表示目标不会经别的合法路径改变。
- &x取地址、*p访问目标;nullptr可比较却不能解引用,而且非空也不能证明目标仍存活。
void syntax_pointer_const() {
int x{4};
int y{9};
const int* read{&x};
int* const fixed{&x};
const int* const both{&x};
*fixed = 7;
read = &y;
std::cout << *read << ' ' << *fixed << ' ' << *both << '\n';
const int* missing{nullptr};
if (missing == nullptr) { std::cout << "missing\n"; }
}运行结果
9 7 7
missing
11
array固定长度,vector可变,string拥有字符
- std::array<int,3>拥有固定三个int,std::vector<int>拥有可变长度的连续整数,std::string拥有字符;使用对应头文件。
- 本例push_back(6)后vector的size为3,reserve(10)只预留容量,size仍为3;resize才会改变元素数量。
- 下标必须小于size,不能拿capacity当作可访问元素数;追加若发生重分配,之前借出的指针、引用和迭代器会失效。
继续按用途查阅:11.2 按键查找与唯一性 · 11.5 队列与栈的访问顺序。
void syntax_containers() {
std::array<int, 3> fixed{2, 4, 6};
std::vector<int> values{2, 4};
values.push_back(6);
std::string text{"hi"};
text += '!';
std::cout << fixed[0] << ' ' << values.size() << ' ' << text << '\n';
values.reserve(10);
std::cout << values.size() << '\n';
}运行结果
2 3 hi!
3
12
span和string_view借用已有数据
- span描述连续元素及长度,string_view描述字符及长度;它们不拥有底层数据,也不延长拥有者的寿命。
- 本例只改现有元素、不改长度或重分配,所以视图立即读到9和bat,而不是保存最初的4和cat。
- 只读视图不能通过自身修改数据,但不是快照;string_view的边界也不保证有零字符,不能随意当作C风格字符串使用。
void syntax_borrowed_views() {
std::array<int, 3> values{2, 4, 6};
std::span<const int> view{values};
std::string text{"cat"};
std::string_view word{text};
values[1] = 9;
text[0] = 'b';
std::cout << view[1] << ' ' << word << '\n';
}运行结果
9 bat
13
类、成员、构造和const查询
- class默认private、struct默认public;r.value()选择对象的成员,p->set(7)通过指针选择对象,this指向本次调用的当前对象。
- 构造函数与类同名、没有普通返回类型,冒号后直接初始化成员;多个成员按类内声明顺序初始化,不按列表书写顺序。
- explicit允许本例Reading r{4},但拒绝Reading r = 4的隐式初始化;查询末尾const限制经当前对象路径修改普通成员。
void syntax_classes() {
class Reading {
int value_;
public:
explicit Reading(int initial) : value_{initial} {}
int value() const { return value_; }
void set(int next) { this->value_ = next; }
};
Reading r{4};
Reading* p{&r};
p->set(7);
std::cout << r.value() << '\n';
}运行结果
7
14
~类型名:正常退出时的析构
- 析构函数写成~Trace(),没有普通返回类型或参数;正常离开所在块时,已经构造的自动对象会析构。
- 本例内层2先退出、外层1后退出;正常return也会处理所退出作用域中的已构造自动对象。
- 若类包含其他类成员,先执行外层析构体,再逆序销毁成员;不要手动调用析构来模拟普通离块,也不要把正常退出推广到所有终止方式。
void syntax_destruction() {
struct Trace {
int id;
explicit Trace(int value) : id{value} { std::cout << "build " << id << '\n'; }
~Trace() { std::cout << "destroy " << id << '\n'; }
};
Trace outer{1};
{
Trace inner{2};
}
}运行结果
build 1
build 2
destroy 2
destroy 1
15
lambda给算法提供一小段操作
- [limit](int value){...}在创建lambda时保存limit的值;若改成[&limit]则借用原对象,必须保证调用时它还活着。
- sort对[begin,end)原地排序,end是尾后位置;count_if用lambda判断每个值并返回满足条件的个数。
- 本例升序得到1、2、3,严格大于1的有两个;比较器若要表达小于,就不能随意写成<=。
回调离开创建位置后的寿命,继续看14.3 移动捕获与14.4 std::function调用合同。
void syntax_lambda_algorithms() {
std::vector<int> values{3, 1, 2};
std::sort(values.begin(), values.end());
int limit{1};
auto above = [limit](int value) { return value > limit; };
auto count = std::count_if(values.begin(), values.end(), above);
std::cout << values[0] << ' ' << values[1] << ' ' << values[2] << '\n';
std::cout << count << '\n';
}运行结果
1 2 3
2
16
智能指针的拥有关系与std::move
- unique_ptr独占拥有,不能复制;本例移动构造把整数7的释放责任交给second,first随后为空。
- std::move本身只转换表达式以参与重载选择,不亲自搬运数据,也不保证所有类型移动后都为空。
- shared_ptr共享拥有,weak_ptr只观察;lock成功才得到临时强拥有者,本例先让held结束再reset最后的owner,所以最终expired为真。
复制与移动的读法见12.2 std::move与实际所选操作;独占拥有见13.3 unique_ptr与make_unique。弱观察与拥有环见14.2 weak_ptr。
void syntax_ownership_move() {
auto first = std::make_unique<int>(7);
auto second = std::move(first);
std::cout << (first == nullptr) << ' ' << *second << '\n';
auto owner = std::make_shared<int>(9);
std::weak_ptr<int> observer{owner};
{
auto held = observer.lock();
if (held) { std::cout << *held << '\n'; }
}
owner.reset();
std::cout << observer.expired() << '\n';
}运行结果
1 7
9
1
17
template把变化的类型留给使用处
- template<class T>与这里的template<typename T>都声明类型参数;T是编译时使用的类型名,不是运行时变量。
- twice<int>(3)明确指定int,twice(0.5)让编译器从实参推导double,所以分别得到6和1。
- 类型能替换不等于所有输入都安全;本例只用小数值,函数仍要求相加和返回满足类型及数值合同。
编译期检查与类型要求,继续看15.3 编译期条件与15.4 concepts与requires。
template<class T>
T twice(T value) { return value + value; }
void syntax_templates() {
std::cout << twice<int>(3) << ' ' << twice(0.5) << '\n';
}运行结果
6 1
18
throw报告失败,catch在能处理处接住
- try包围可能报告异常的操作,throw离开当前正常路径,匹配的catch处理异常;本例先输出3,再处理负数输入并输出negative。
- catch(const std::runtime_error& error)按只读引用接收,what()提供说明文字;这个已处理的异常不是未定义行为,也不是程序崩溃。
- 异常传播到匹配处理器时会清理途中已构造的自动对象,但这不自动保证业务状态回滚。
继续按任务查阅:16.3 严格输入解析与16.5 异常清理与保证。
int nonnegative(int value) {
if (value < 0) { throw std::runtime_error("negative"); }
return value;
}
void syntax_exceptions() {
try {
std::cout << nonnegative(3) << '\n';
nonnegative(-1);
} catch (const std::runtime_error& error) {
std::cout << error.what() << '\n';
}
}运行结果
3
negative
19
按位运算与移位:先用无符号小整数
- &、|、^逐位做与、或、异或,~翻转每一位;它们不同于会短路的逻辑&&与||。
- 本例5和3的低三位是101和011,按位结果为1、7、6;(~a)&7u只保留低三位,避免把完整unsigned位宽写死。
- a<<1左移一位得到10,a>>1右移一位得到2;移位数必须非负且小于提升后的左操作数位宽,这里的u后缀表示unsigned int字面量。
- 输出表达式中的std::cout <<是流输出,用括号把真正的位运算分开读;完整规则可查固定C++20来源。
void syntax_bits() {
unsigned int a{5u}; // Low three bits: 101.
unsigned int b{3u}; // Low three bits: 011.
std::cout << (a & b) << ' ' << (a | b) << ' ' << (a ^ b) << '\n';
std::cout << ((~a) & 7u) << ' ' << (a << 1) << ' ' << (a >> 1) << '\n';
}运行结果
1 7 6
2 10 2
TRY THE EXAMPLES
把这些例子放到终端里跑。
上面的代码是完整文件中的函数片段。下载同一份源码,里面有头文件和调用这些函数的 main;单独复制一个片段不一定能直接编译。
下载完整 cpp-syntax.cpp ↓在下载文件所在目录执行:
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror cpp-syntax.cpp -o cpp-syntax && ./cpp-syntax使用支持 C++20 的 g++ 时,可以把命令开头的 clang++ 换成 g++。先用 g++ --version 看实际编译器;macOS 的 g++ 也可能指向 Apple Clang。
源码经过当前 Mac / Apple Clang 的编译、输出与运行时检查。GNU GCC、Windows 和 Linux 未在这次检查中运行。每项“运行结果”对应完整程序中该函数的输出。