2021 年 9 月 1 日,NOI 赛事终于对 C++ 语言支持了 C++14 标准

由于 C++14 相比于 C++98 新增了不少内容,而大部分 OIer 此前的练习、比赛都基于 C++98 标准,为帮助大家快速上手 C++14 的重要好用的特性,这里给大家对 C++14 的新增内容作简单梳理。

本文仅针对 OI 介绍 C++14 相对于 C++98 的新增内容,因此部分 C++11 新增的内容也会在本文中出现。

本文所述「等价于」仅表示在大多数情况下运行代码产生的效果相同,并不表示在具体实现和汇编层面完全等价

# 启用 C++14

确保 GCC 版本为 5 或以上,或 Clang 版本为 3.4 或以上。

更多关于编译器支持 C++14 的版本的信息

C++14 - cppreference.com

在编译命令中加入以下选项指定 C++ 标准

-std=c++14

# 新特性

# auto

顾名思义,根据赋值表达式自动指定类型。

int a = 114514;
auto b = a + 1919810; // auto -> int

等价于:

int a = 114514;
int b = a + 1919810;
参考资料
  • 占位类型说明符 (C++11 起) - cppreference.com

# decltype

顾名思义,推断传入的参数的类型。

int a = 114514;
decltype(a) b = 1919810;  // int
参考资料
  • decltype 说明符 - cppreference.com

# 范围 for 循环

从 C++11 起,可以使用范围 for 循环遍历数组中的每一个元素

int a[N];
for (int x : a) {
    func(x);
}

等价于:

int arr[N];
for (int i = 0; i < N; i++) {
    int x = arr[i];  // copy
    func(x);
}

auto 结合起来的等价写法:

int arr[N];
for (auto x : arr) {
    func(x);
}

注意 这里的 xarr[i]拷贝,对 x 进行操作不会影响 arr[i] 的值。

使用 auto 和范围 for 循环,可以使原本复杂的迭代器遍历变得十分简洁:

(以下代码以 std::vector 为例)

std::vector<int> arr;
for (auto x : arr) {
    func(x);
}

等价于原本的一大坨:

for (std::vector<int>::iterator i = arr.begin(); i != arr.end(); i++) {
    int x = *i;  // copy
    func(x);
}

注意 这里的 xarr拷贝,对 x 进行操作不会影响 arr 的值。

如果想修改遍历的内容,可以使用 auto&

std::vector<int> arr;
for (auto &x : arr) {
    func(x);
}

等价于:

for (std::vector<int>::iterator i = arr.begin(); i != arr.end(); i++) {
    func(*i);
}

注意 这里的 xarr引用,对 x 进行操作影响 arr 的值。

使用 std::vector<bool> 时,慎用 auto ,因为 std::vector<bool> 采用了特殊的实现方法。详见下面的参考资料

参考资料
  • 基于范围的 for 循环 (C++11 起) - cppreference.com

  • [Brief Talk] auto, auto&, const auto & 以及其它形式的 auto 变种在 for-range loop 的选择 - 知乎

# 模板相关

# 变量模板

从 C++14 起,可以对变量使用模板。

template < 形参列表 > 变量声明

引用 cppreference.com 中的例子

template <class T>
constexpr T pi = T(3.1415926535897932385L);  // 变量模板
template <class T>
T circular_area(T r) {     // 函数模板
    return pi<T> * r * r;  //pi<T> 是变量模板实例化
}

# 可变模板形参

从 C++11 起,允许模板中传入可变个数的参数。

template <typename... T>

现在,你可以使用可变模板形参实现 C++98 做不到的输出多个变量自适应变量类型的 Debug 输出:

#include <iostream>
#include <string>
#define DEBUG
void debug_out() { std::cerr << '\n'; }
template <typename Head>
void debug_out(Head H) {
    std::cerr << H << std::endl;
}
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
    std::cerr << H << ", ";
    debug_out(T...);
}
#ifdef DEBUG
#define debug(...) std::cerr << "[" #__VA_ARGS__ "]: ", debug_out(__VA_ARGS__)
#else
#define debug(...)
#endif

改写自:【杂项】缺省源 - 滑蒻稽的博客

参考资料
  • 变量模板 (C++14 起) - cppreference.com

  • 模板形参与模板实参 - cppreference.com

  • 【杂项】缺省源 - 滑蒻稽的博客

# Lambda 表达式

Lambda 表达式,换种说法就是行内 (inline) 匿名函数。(个人理解)

比如以下的 Lambda 表达式写法:

std::sort(arr.begin(), arr.end(),
          [](int a, int b) { return a > b; });  // desending sort

等价于:

bool cmp(int a, int b) { return a > b; }
std::sort(arr.begin(), arr.end(), cmp);

auto 结合起来:

std::sort(arr.begin(), arr.end(),
          [](auto a, auto b) { return a > b; });  // desending sort

Lambda 表达式的完整语法结构为: [ 捕获 ] ( 形参 ) lambda 说明符 约束 (可选) { 函数体 }

再举个例子:

int lim = 114514;
auto function = ([lim](int x) -> bool {
    if (x > lim) {
        x = lim;
        return false;
    }
    return true;
})
function();

[ ] 中的捕获参数列表允许局部变量在函数中调用。

-> 指定了函数的返回类型。

更多关于 Lambda 的用法详见下面的参考资料。

参考资料
  • Lambda 表达式 (C++11 起) - cppreference.com

  • C ++ Lambda 表达式详解 - CSDN

# 二进制字面量 & 数位分隔符

从 C++14 起,可以在代码中直接书写二进制常量。

从 C++14 起,可以以单引号 ' 表示数位分隔符,会在编译时忽略。

int var = 0b11011111101010010;
int var = 0b0001'1011'1111'0101'0010;
int var = 114'514;

等价于:

int var = 114514;
参考资料
  • 整数字面量 - cppreference.com

# 新 STL 库功能特性

# <unordered_set> & <unordered_map>

<set> <map> 的更快的 Hash 实现,用法与 <set><map> 相同。

# <regex>

正则表达式 (Regular Expression),是一种用于在字符串中匹配模式的微型语言,可实现对字符串的匹配搜索分词替换操作。字符串处理神器。

正则表达式的时间复杂度取决于表达式的复杂度,平均情况下正则表达式的时间复杂度为 O(n)O(n)

如果读者还不了解正则表达式,建议先在这里学习一下。

# 正则语法

std::regex 默认使用 ECMAScript 语法:

符号释义
^匹配行的开头
$匹配行的结尾
.匹配任意单个字符
[…]匹配 [] 中的任意一个字符
(…)设定分组
\转义字符
\d匹配数字 [0-9]
\D\d 取反
\w匹配字母 [a-z] ,数字,下划线
\W\w 取反
\s匹配空格
\S\s 取反
+前面的元素重复 11 次或多次
*前面的元素重复任意次
?前面的元素重复 00 次或 11
{n}前面的元素重复 nn
{n,}前面的元素重复至少 nn
{n,m}前面的元素重复至少 nn 次,至多 mm
|逻辑或

注意 在 C++ 中书写正则表达式时,需要以 2 个反斜杠 \\ 代替所有转义字符的单个反斜杠 \

更多正则表达式的语法详见参考资料

# 匹配 ( std::regex_match )

传入 2 个参数:待匹配字符串模式字符串 ( std::regex )。当待匹配字符串与模式字符串完全对应时,则返回 true ,否则返回 false

以下例子使用 regex_match 来匹配 XML 格式(或是 HTML 格式)的字符串。其中, "<.*>.*</.*>" 为模式字符串。

std::regex pattern("<.*>.*</.*>");
bool ret = std::regex_match("<html>value</html>", pattern);  // true
ret = std::regex_match("<xml>value<xml>", pattern);          // false

传入 3 个参数:待匹配字符串用于储存匹配结果的字符串模式字符串 ( std::regex )。当模式字符串在待匹配字符串中能匹配一次时,则返回 true ,否则返回 false用于储存匹配结果的字符串 ( std::cmatch ) 中会存入匹配的子串。

其中,用于储存匹配结果的字符串的常用类型为 std::cmatchstd::smatch ,分别对应 C 风格字符串 char*std::string

std::regex pattern("<(.*)>(.*)</(\\1)>");
std::cmatch m;
auto ret = std::regex_search("begin<xml>value</xml>end", m, pattern);

在以上的示例中,用于储存匹配结果的字符串 m 有以下调用方法:

  • m.str()

    • m.str() ,等价于 m.str(0) ,返回匹配的完整字符串。在上述例子中,返回 "<xml>value</xml>"

    • m.str(n) ,返回匹配的字符串中的第 nn元素(从 11 开始),以模式串中的括号 () 划分。在上述例子中, m.str(1) 返回 "xml"m.str(2) 返回 "value"m.str(3) 返回 "xml"

  • m.size()

    返回匹配的字符串中的元素个数加 1。在上述例子中,返回 4

  • m.prefix()

    返回匹配的字符串的前缀(匹配的子串前的不匹配部分)。在上述例子中,返回 "begin"

  • m.suffix()

    返回匹配的字符串的后缀(匹配的子串后的不匹配部分)。在上述例子中,返回 "end"

使用迭代器,可以遍历搜索出所有匹配的子串:

std::regex pattern("<(.*)>(.*)</(\\1)>");
std::string content(
    "begin<xml>value</xml>end<widget>example</widget>I<luogu>AK</luogu>IOI");
std::smatch m;
for (auto pos = content.cbegin(), end = content.cend();
     std::regex_search(pos, end, m, pattern); pos = m.suffix().first) {
    std::cout << m.str() << std::endl;
}

其中, m.suffix().first 返回指向后缀的第一个字符的迭代器,即匹配结束的位置。

# 分词 ( std::regex_token_iterator )

模板类 std::regex_token_iterator 提供了分词迭代器:

std::string content("kkksc03@luogu.com.cn,i@ak.ioi,114@514.1919.810");
std::regex pattern(",");
std::sregex_token_iterator pos(content.begin(), content.end(), pattern, -1);
for (decltype(pos) end; pos != end; ++pos) {
    std::cout << pos->str() << std::endl;
}

其中, std::sregex_token_iterator 是针对 string 类型的特化。构造函数传入 4 个参数:待匹配字符串的开始和结束迭代器模式字符串 ( std::regex )捕获模式

当捕获模式为 -1 时,返回的迭代器指向匹配的字符串的内容。当捕获模式为 0 时,返回的迭代器指向匹配的字符串,即在上述例子中的分隔符 ","

# 替换 ( std::regex_replace )

std::regex_replace 函数实现了正则替换功能。

std::string content("1:Luogu,2:kkksc03,3:AKIOI");
std::regex pattern("(\\d+):(\\w+)");
std::cout << std::regex_replace(content, pattern, "id=$1, name=$2\n");
// OUTPUT: {id=1,name=Luogu},{id=2,name=kkksc03},{id=3,name=AKIOI}

std::regex_replace 函数传入 3 个参数:待处理的字符串模式字符串 ( std::regex )替换字符串

可以在模式字符串中使用括号 () 分组,在替换字符串中使用 $n 表示分组序号(从 1 开始),实现部分替换。

参考资料
  • 正则表达式库 - cppreference.com

  • C++ 正则表达式 - cpluspluser - 博客园

  • 正则表达式 - 语法 | 菜鸟教程

  • What's the Time Complexity of Average Regex algorithms? - Stack Overflow

# <tuple>

元组, std::pair 的推广,支持多个元素

std::tuple<int, float, int, float> tu = std::make_tuple(1, 14.f, 5, 14.f);
std::tuple<int, float, int, float> tu(1, 14.f, 5, 14.f);

使用 std::forward_as_tuplestd::tie 构造引用值的元组,使用 std::get<n> 修改引用指向的值:

int a = 1;
float b = 14.f;
int c = 5;
float d = 14.f;
auto tu = std::forward_as_tuple(a, b, c, d);
auto tu = std::tie(a, b, c, d);
// 修改引用指向的值
std::get<0>(tu) = 19;
std::get<1>(tu) = 1.9f;
std::get<2>(tu) = 8;
std::get<3>(tu) = 1.0f;

使用 std::tuple_cat 拼接多个元组:

std::tuple<int, int, int, int> a(1, 1, 4, 5);
std::tuple<int, int, int, int> b(1, 4, 1, 9);
std::tuple<int, int, int, int> c(1, 9, 8, 1);
auto d = std::tuple_cat(a, b, c);

支持按字典序比较

参考资料
  • std::tuple - cppreference.com

  • C++Tuple 元组的详细用法 - 知乎

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

玄云Fidel WeChat Pay

WeChat Pay

玄云Fidel Alipay

Alipay

玄云Fidel PayPal

PayPal

玄云Fidel afdian

afdian