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); | |
} |
注意 这里的 x
是 arr[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); | |
} |
注意 这里的 x
是 arr
的拷贝,对 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); | |
} |
注意 这里的 x
是 arr
的引用,对 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),是一种用于在字符串中匹配模式的微型语言,可实现对字符串的匹配、搜索、分词和替换操作。字符串处理神器。
正则表达式的时间复杂度取决于表达式的复杂度,在平均情况下正则表达式的时间复杂度为 。
如果读者还不了解正则表达式,建议先在这里学习一下。
# 正则语法
std::regex
默认使用 ECMAScript 语法:
符号 | 释义 |
---|---|
^ | 匹配行的开头 |
$ | 匹配行的结尾 |
. | 匹配任意单个字符 |
[…] | 匹配 [] 中的任意一个字符 |
(…) | 设定分组 |
\ | 转义字符 |
\d | 匹配数字 [0-9] |
\D | \d 取反 |
\w | 匹配字母 [a-z] ,数字,下划线 |
\W | \w 取反 |
\s | 匹配空格 |
\S | \s 取反 |
+ | 前面的元素重复 次或多次 |
* | 前面的元素重复任意次 |
? | 前面的元素重复 次或 次 |
{n} | 前面的元素重复 次 |
{n,} | 前面的元素重复至少 次 |
{n,m} | 前面的元素重复至少 次,至多 次 |
| | 逻辑或 |
注意 在 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 |
# 搜索 ( std::regex_search
)
传入 3 个参数:待匹配字符串、用于储存匹配结果的字符串 和模式字符串 ( std::regex
)。当模式字符串在待匹配字符串中能匹配一次时,则返回 true
,否则返回 false
。用于储存匹配结果的字符串 ( std::cmatch
) 中会存入匹配的子串。
其中,用于储存匹配结果的字符串的常用类型为 std::cmatch
和 std::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)
,返回匹配的字符串中的第 个元素(从 开始),以模式串中的括号()
划分。在上述例子中,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_tuple
或 std::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 元组的详细用法 - 知乎