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,但主动转换仍需自己保证输入在目标范围内。
新02.3:初始化与窄化 →
C++20 · declaration
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
C++20草案N4861:[dcl.init]、[dcl.init.list] ↗

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;不要把只读引用误当成保存旧值的快照。
新05.4:auto与引用推导 →
C++20 · const-auto
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
C++20草案N4861:[dcl.type.cv]、[dcl.spec.auto] ↗

03

先看运算类型,再看接收结果的类型

  • 整数除法向零截断:5 / 2是2,−5 / 2是−2;5 % 2得到余数1,乘除通常先于加减。
  • 先把5转换为double再除得到2.5,除完再转换只能把已经得到的2变成double。
  • 本例采用小整数和非零除数;有符号溢出、除以零以及最小有符号整数除以−1都不是可用来猜输出的练习。
新02.2:表达式与整数除法 →
C++20 · arithmetic
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
C++20草案N4861:[expr.mul]、[expr.add]、[expr.static.cast] ↗

04

if选择路径,switch选择标签入口

  • if检查条件,else处理未满足的路径;==比较,=赋值,&&与||从左到右短路,!取逻辑反。
  • switch按整数或枚举值进入匹配的case,没有匹配时进入default;进入后会继续执行,直到break等控制转移。
  • 漏写break可能继续执行后一个case的语句,不会自动在下个标签停下。
新03.6:switch与其他控制形式 →
C++20 · selection
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
C++20草案N4861:[stmt.if]、[stmt.switch] ↗

05

while先检查,do/while先做一次

  • while先检查再执行,本例第一次条件就是假,a保持0;do/while先执行循环体,再检查,所以b变成1。
  • do/while末尾的分号属于语法,普通while后面不要误加一个表示空循环体的分号。
  • 写循环时一起检查初值、条件和更新;条件永远不会变假时,程序可能一直重复。
新03.6:do/while的首次执行 →
C++20 · while-do
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
C++20草案N4861:[stmt.while]、[stmt.do] ↗

06

普通for:初始化;条件;更新

  • for的三段依次是初始化、继续条件、每轮更新;执行顺序为初始化一次→条件→循环体→更新→再判断。
  • 下面用下标访问values[i],i依次为0、1、2;条件必须是i < values.size(),不是<=。
  • 需要位置时用下标循环;只需要元素时可对照下一条的范围循环,修改原元素要用auto&或直接给values[i]赋值。
新03.4:普通for;容器语法见后续主题 →
C++20 · index-for
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
C++20草案N4861:[stmt.for]、[array.overview] ↗

07

范围for:副本、只读借用、可写借用

  • 对本例int元素,for(auto value : values)每轮复制一个值,修改value不会修改原元素;循环变量不是下标。
  • for(auto& value : values)可以修改原元素,for(const auto& value : values)借用读取且不允许经value改写。
  • 第一轮把副本各加1,累计15但原数组仍为2、4、6;第二轮才把原值加倍,最后只读累计得到24。
详解:09.2 范围for与副本/引用 →
C++20 · range-for
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
C++20草案N4861:[stmt.ranged] ↗

08

continue跳过本轮,break退出一层

  • continue跳过循环体剩余语句;在普通for里接着执行更新表达式,再检查条件。
  • break退出最近一层循环或switch,不会自动结束所有外层循环或整个函数。
  • 本例跳过2、遇到4就退出,因此只累计0、1、3,得到4。
新03.5:break与continue →
C++20 · loop-jumps
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
C++20草案N4861:[stmt.break]、[stmt.cont] ↗

09

参数副本、引用参数和返回值

  • int value按值接收副本,int& value借用并可修改调用者整数,const int& value借用读取。
  • return交回本次调用的结果,不会自动写回实参;void表示没有返回值,需要提前离开时写return;。
  • 返回局部整数的值可以得到独立结果,但不要返回它的引用或地址供调用者继续访问。
新05.2:参数表达的数据方向 →
C++20 · functions
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
C++20草案N4861:[dcl.fct]、[dcl.init.ref]、[stmt.return] ↗

10

const在星号哪边,限制就不同

  • const int*限制经指针写目标,但允许改指向;int* const固定指向,但能修改合适的可写目标;const int* const同时限制两者。
  • 本例*fixed把x改为7,随后read改借y,所以三个读取结果是9、7、7;const路径不表示目标不会经别的合法路径改变。
  • &x取地址、*p访问目标;nullptr可比较却不能解引用,而且非空也不能证明目标仍存活。
新06.4:const的位置 →
C++20 · pointer-const
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
C++20草案N4861:[dcl.ptr]、[dcl.type.cv]、[expr.unary.op] ↗

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当作可访问元素数;追加若发生重分配,之前借出的指针、引用和迭代器会失效。
新08.3:vector拥有与容量 →

继续按用途查阅:11.2 按键查找与唯一性 · 11.5 队列与栈的访问顺序

C++20 · containers
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
C++20草案N4861:[array.overview]、[vector.capacity]、[basic.string] ↗

12

span和string_view借用已有数据

  • span描述连续元素及长度,string_view描述字符及长度;它们不拥有底层数据,也不延长拥有者的寿命。
  • 本例只改现有元素、不改长度或重分配,所以视图立即读到9和bat,而不是保存最初的4和cat。
  • 只读视图不能通过自身修改数据,但不是快照;string_view的边界也不保证有零字符,不能随意当作C风格字符串使用。
详解:09.3 span与string_view →
C++20 · borrowed-views
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
C++20草案N4861:[span.overview]、[string.view.template] ↗

13

类、成员、构造和const查询

  • class默认private、struct默认public;r.value()选择对象的成员,p->set(7)通过指针选择对象,this指向本次调用的当前对象。
  • 构造函数与类同名、没有普通返回类型,冒号后直接初始化成员;多个成员按类内声明顺序初始化,不按列表书写顺序。
  • explicit允许本例Reading r{4},但拒绝Reading r = 4的隐式初始化;查询末尾const限制经当前对象路径修改普通成员。
新07.2:初始化与构造 →
C++20 · classes
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
C++20草案N4861:[class.access]、[class.base.init]、[class.conv.ctor] ↗

14

~类型名:正常退出时的析构

  • 析构函数写成~Trace(),没有普通返回类型或参数;正常离开所在块时,已经构造的自动对象会析构。
  • 本例内层2先退出、外层1后退出;正常return也会处理所退出作用域中的已构造自动对象。
  • 若类包含其他类成员,先执行外层析构体,再逆序销毁成员;不要手动调用析构来模拟普通离块,也不要把正常退出推广到所有终止方式。
新07.4:析构和销毁顺序 →
C++20 · destruction
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
C++20草案N4861:[class.dtor]、[stmt.jump] ↗

15

lambda给算法提供一小段操作

  • [limit](int value){...}在创建lambda时保存limit的值;若改成[&limit]则借用原对象,必须保证调用时它还活着。
  • sort对[begin,end)原地排序,end是尾后位置;count_if用lambda判断每个值并返回满足条件的个数。
  • 本例升序得到1、2、3,严格大于1的有两个;比较器若要表达小于,就不能随意写成<=。
详解:10.4 lambda与捕获 →

回调离开创建位置后的寿命,继续看14.3 移动捕获14.4 std::function调用合同

C++20 · lambda-algorithms
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
C++20草案N4861:[expr.prim.lambda]、[alg.sort]、[alg.count] ↗

16

智能指针的拥有关系与std::move

  • unique_ptr独占拥有,不能复制;本例移动构造把整数7的释放责任交给second,first随后为空。
  • std::move本身只转换表达式以参与重载选择,不亲自搬运数据,也不保证所有类型移动后都为空。
  • shared_ptr共享拥有,weak_ptr只观察;lock成功才得到临时强拥有者,本例先让held结束再reset最后的owner,所以最终expired为真。
详解:13.3–13.4 独占拥有、转交与观察 →14.1–14.2 共享拥有与弱观察 →

复制与移动的读法见12.2 std::move与实际所选操作;独占拥有见13.3 unique_ptr与make_unique。弱观察与拥有环见14.2 weak_ptr

C++20 · ownership-move
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
C++20草案N4861:[unique.ptr.single.ctor]、[util.smartptr.weak]、[forward] ↗

17

template把变化的类型留给使用处

  • template<class T>与这里的template<typename T>都声明类型参数;T是编译时使用的类型名,不是运行时变量。
  • twice<int>(3)明确指定int,twice(0.5)让编译器从实参推导double,所以分别得到6和1。
  • 类型能替换不等于所有输入都安全;本例只用小数值,函数仍要求相加和返回满足类型及数值合同。
详解:15.1 函数模板与推导 →

编译期检查与类型要求,继续看15.3 编译期条件15.4 concepts与requires

C++20 · templates
template<class T>
T twice(T value) { return value + value; }
void syntax_templates() {
    std::cout << twice<int>(3) << ' ' << twice(0.5) << '\n';
}
运行结果
6 1
C++20草案N4861:[temp.param]、[temp.deduct.call] ↗

18

throw报告失败,catch在能处理处接住

  • try包围可能报告异常的操作,throw离开当前正常路径,匹配的catch处理异常;本例先输出3,再处理负数输入并输出negative。
  • catch(const std::runtime_error& error)按只读引用接收,what()提供说明文字;这个已处理的异常不是未定义行为,也不是程序崩溃。
  • 异常传播到匹配处理器时会清理途中已构造的自动对象,但这不自动保证业务状态回滚。
详解:16.4 throw/try/catch →

继续按任务查阅:16.3 严格输入解析16.5 异常清理与保证

C++20 · exceptions
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
C++20草案N4861:[except.throw]、[except.handle]、[except.ctor] ↗

19

按位运算与移位:先用无符号小整数

  • &、|、^逐位做与、或、异或,~翻转每一位;它们不同于会短路的逻辑&&与||。
  • 本例5和3的低三位是101和011,按位结果为1、7、6;(~a)&7u只保留低三位,避免把完整unsigned位宽写死。
  • a<<1左移一位得到10,a>>1右移一位得到2;移位数必须非负且小于提升后的左操作数位宽,这里的u后缀表示unsigned int字面量。
  • 输出表达式中的std::cout <<是流输出,用括号把真正的位运算分开读;完整规则可查固定C++20来源。
详解:SYS-00 位模式与系统接口 →
C++20 · bits
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
C++20草案N4861:[expr.shift]、[expr.bit.and]、[expr.xor]、[expr.or] ↗

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 未在这次检查中运行。每项“运行结果”对应完整程序中该函数的输出。