语法糖
本文转载(或修改)自 洛谷日报:从 C++98 到 C++20,寻觅甜甜的语法糖们
C++ 98
algorithm 库
algorithm 中有大量的函数,这些函数都位于命名空间 std 中。
最常见的有 max/min、swap、sort、unique、lower_bound/upper_bound、reverse。
一些常见且比较重要的函数
-
find(bg,ed,val) -
返回指向第一个等于 \(val\) 的元素的指针。
-
时间复杂度 \(O(n)\)。后文所有序列操作的函数,都以 \(n\) 代指 \(ed-bg\)。
-
fill(bg,ed,val) -
将 \([bg,ed)\) 之间的所有元素赋值为 \(val\)。
-
复杂度为 \(O(n)\),常数略大于
memset。1在 $val=0/-1/\texttt{0x3f}$ 时常数与 `memset` 相同。(存疑) -
常用于弥补
memset无法赋值的问题,如赋值一个数组为 \(1\)。 -
copy(bg1,ed1,bg2) -
将 \([bg_1,ed_1)\) 中的元素复制到 \(bg_2\)。
-
复杂度 \(O(n)\)。
-
stable_sort(bg,ed) -
对 \([bg,ed)\) 进行 稳定 排序。
-
时间复杂度 \(O(n\log n)\),要 \(O(n)\) 的额外空间,当空间不足时使用 \(O(n\log^2n)\) 的双调排序。
-
nth_element(bg,md,ed): - 将 \([bg,ed)\) 中的内容重新分配顺序,小于(等于)
md的元素在 \([bg,md)\),大于(等于)md的元素都在 \((md,ed)\)。 - 时间复杂度 \(O(n)\),需要 \(O(n)\) 的额外空间。
- 第四个参数为比较函数,若不传则默认
less<>()。 -
常用于线性求序列中的第 \(k\) 大,常用于实现替罪羊树。
-
max_element/min_element(bg,ed) -
返回指向 \([bg,ed)\) 中最大 / 最小的元素的指针。
-
时间复杂度 \(O(n)\)。
-
第三个参数可传入比较函数。
-
求数组最大值就可以直接写:
*max_element(a+1,a+n+1) -
random_shuffle(bg,ed) -
打乱 \([bg,ed)\) 中元素的顺序。
- 时间复杂度 \(O(n)\)。
- 第三个参数传入一个函数
func(int n),这个函数的返回值是一个 \([1,n]\) 中的随机整数。 - 在未传入第三参数时,若 \(ed-bg>\texttt{RAND\_MAX}\) 那么其随机性将无法保证。
-
在
C++14中被弃用,在C++17中被废除,C++11之后都应当以shuffle替代之。 -
next_permutation(bg,ed) -
将 \([bg,ed)\) 更改为下一个排列,并返回 \(1\),若 \([bg,ed)\) 已经是最后一个排列,那么返回 \(0\),下一个排列的定义为字典序严格大于当前排列的下一个排列。
-
时间复杂度 \(O(n)\)。
-
事实上 \([bg,ed)\) 不一定要是排列,可以存在相同元素。
-
常用于枚举全排列:
1 2 3 4 | |
prev_permutation(bg,ed)可以求出上一个排列。
一些比较冷门的函数
-
merge(bg1,ed1,bg2,ed2,bg3) -
\([bg_1,ed_1)\) 和 \([bg_2,ed_2)\) 是两个有序序列,对其进行归并并存入 \(bg_3\),不能够原地归并,若要原地归并请使用
inplace_merge。 -
时间复杂度 \(O(ed_1-bg_1+ed_2-bg_2)\)。
-
可以传入第六参数作为比较函数。
-
inplace_merge(bg,md,ed) -
将 \([bg,md)\) 和 \([md,ed)\) 归并排序,并存入 \(bg\)。
-
时间复杂度 \(O(n)\),需要 \(O(n)\) 的额外空间,当空间不足时使用 \(O(n\log n)\) 的原地排序,常数较小,可能比手写的还快。
-
可以传入第四个参数作为比较函数。
-
常用于 CDQ 分治等分治算法,非常好用。
-
count(bg,ed,val) -
返回 \([bg,ed)\) 中等于 \(val\) 的元素的个数。
-
时间复杂度 \(O(n)\)。
-
count_if(bg,ed,func) -
返回 \([bg,ed)\) 中使得函数
func返回值为true的元素的个数。 - 时间复杂度 \(O(n\times f)\), \(f\) 为
func的复杂度。 -
常用的
func有:islower/isupper/isdigit等。 -
replace(bg,ed,val1,val2) -
将 \([bg,ed)\) 中所有等于 \(val_1\) 的元素替换为 \(val_2\)。
-
时间复杂度 \(O(n)\)。
-
for_each(bg,ed,func) - 对 \([bg,ed)\) 中所有元素执行函数
func。 - 时间复杂度 \(O(n\times f)\)。
-
其实没啥用,就是能压行。
-
transform(bg1,ed1,bg2,bg3,func) - 对 \([bg_1,ed_1)\) 和 \([bg_2,bg_2+ed_1-bg_1)\) 中的元素依次执行二元函数
func,并将返回值存入 \(bg_3\)。 -
时间复杂度 \(O(n\times f)\)。
-
rotate(bg,md,ed) -
将 \([bg,ed)\) 循环至 \(md\) 处元素位于 \(bg\)。
-
时间复杂度 \(O(n)\)
-
例子:
1 2 3 4 5 6 | |
GNU 私货
有一些以双下划线开头的函数并未在 C++ 标准中,是 GNU 的私货,在 NOI 系列赛事中可以使用。
__lg(x)- 返回 \(\lfloor \log_2x\rfloor\) 。
- 时间复杂度 \(O(1)\)。
- 常用于实现倍增、二进制运算等。
__gcd(x,y)- 返回 \(\gcd(x,y)\)。
- 复杂度是对数级别的,常数较小。
- 注意,返回值的符号 不一定 是正。
- 在 C++17 之前都是很常用的。
numeric 库
这里的函数,真的很好用。
-
accumulate(bg,ed,val) -
将 \([bg,ed)\) 中的所有所有元素与初始值 \(val\) 相加,返回这个和。
-
时间复杂度 \(O(n)\)。
-
可以传入第四个参数作为加法。
-
可以用于求序列和,但注意,该函数返回值与 \(val\) 类型一致,意味着你要注意
long long的问题:
1 2 | |
-
inner_product(bg1,ed1,bg2,val) -
将 \([bg_1,ed_1)\) 和 \([bg_2,bg_2+ed_1-bg_1)\) 对应位置一一相乘再与初始值 \(val\) 相加,返回这个和。
- 时间复杂度 \(O(n)\)。
- 可以传入第五、六个参数分别作为加法和乘法。
-
用于做向量内积。
-
partial_sum(bg1,ed1,bg2) -
对 \([bg_1,ed_1)\) 做前缀和并存入 \([bg_2,bg_2+ed_1-bg_1)\)。
- 时间复杂度 \(O(n)\)。
- 可以传入第四个参数作为加法。
-
可以原地求前缀和。
-
adjacent_difference(bg1,ed1,bg2) -
对 \([bg_1,ed_1)\) 求差分并存入 \([bg_2,bg_2+ed_1-bg_1)\)。
- 时间复杂度 \(O(n)\)。
- 可以传入第四个参数作为减法。
- 可以原地差分。
functional 库
常见的函数有 less<>/greater<> 等。
事实上,大部分运算 / 比较也在这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
cmath 库
-
fabs(x) -
返回 \(|x|\)。
-
注意,对浮点运算请都使用
fabs而不是abs,因为有可能你调用的是abs(int)。 -
fmod(x,y) -
返回 \(x\bmod y=x-y\lfloor\frac xy\rfloor\)。
-
在一些三角函数的地方可能会用到对 \(\pi\) 取模。
-
exp(x) -
返回 \(e^x\)。
-
在
double内,\(x\) 的有效区间为 \([-708.4,709.8]\)。 -
log(x) -
返回 \(\ln x\)。
-
当 \(x\in (-\infty,0]\) 时报错。
-
对数家族还有:
log10(x)和log2(x),请注意,log2(x)是 C++11 的。 -
ceil(x) -
返回 \(\lceil x\rceil\)
-
其返回值依旧是浮点类型,输出时注意格式。
-
floor(x) -
返回 \(\lfloor x\rfloor\)
- 其返回值依旧是浮点类型,输出时注意格式。
__builtin 家族
这里的内容并不在 C++ 标准中,全部都是 GNU 的私货,若使用其它编译器则可能无法通过编译。
如果 \(x\) 的类型是 long long,请务必使用 __builtin_xxxll(x)(如 __builtin_popcountll(x)),否则将可能造成 FST 等严重后果。
-
__builtin_popcount(x) -
返回 \(x\) 在二进制下 \(1\) 的个数。
-
时间复杂度有说 \(O(\log\log x)\) 的,也有说 \(O(1)\) 的,一定比手写的快。
-
__builtin_parity(x) -
返回 \(x\) 在二进制下 \(1\) 个数的奇偶性。
-
时间复杂度 \(O(1)\),快于
__builtin_popcount(x)&1。 -
__builtin_ffs(x) -
返回二进制下最后一个 1 是从后往前第几位。
-
时间复杂度 \(O(1)\)。
-
__builtin_ctz(x) -
返回二进制下后导零的个数,\(x=0\) 时UB。
-
时间复杂度 \(O(1)\)。
-
__builtin_clz(x) -
返回二进制下前导零的个数,\(x=0\) 时UB。
- 时间复杂度 \(O(1)\)。
C++ 11
从 C++98 到 C++11 是一次重大的飞跃,许许多多非常实用的函数与语法如雨后春笋般出现。
零零散散的语法糖
auto
auto 在 C++98 中是一个较偏僻冷门的东西,因此在 C++11 中直接将其抛弃,并赋予其新生。
auto 可以用来声明一个变量,其类型由初始化的内容定义。
1 2 3 4 | |
因此 auto 声明变量时必须有初始化内容,否则将 CE。
同时,也可以声明为其它变量的引用:
1 2 3 4 5 | |
常用于声明一些类型名很长的变量,如:set<pair<int,int>,greater<pair<int,int>>>::iterator
auto 用于声明有固定返回值类型的函数,在 C++11 时需要尾随返回类,但在 C++14 及之后就不需要了:
1 2 3 4 5 6 7 | |
也可以用来声明一个 lambda 函数,见下文 lambda 表达式 部分。
lambda 表达式
见 lambda 表达式。
range-for
for 循环中轻松地遍历容器(vector、set、list 等):
1 2 3 4 | |
也可以将 \(x\) 声明为引用:
1 2 3 | |
注意,\(x\) 的类型必须与 \(v\) 中元素的类型保持一致,否则会 CE。
也可以使用 auto 进行声明。
注意,若遍历数组,那将从头到尾遍历一遍,不推荐。
用处极为广泛,常用于对于 vector 存图的遍历等。
STL
emplace
在很多 STL 容器中都出现了一个新的函数——emplace,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
emplace 相比原先的 insert/push/push_back/push_front 区别是,emplace 通过调用元素的构造函数来进行插入,所以它会比之前的函数更快一些。
因此也产生了使用上的区别:
1 2 3 | |
更加便于书写。
但这要求用户自己的类型必须含有构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 | |
shrink_to_fit
vector/deque/string/basic_string 的 shrink_to_fit 可以使其 capacity 调整为 size 的大小,如:
1 2 3 4 5 6 7 8 9 10 11 12 | |
常用于 clear() 之后释放内存。
algorithm 库
shuffle(bg,ed,gen)- 打乱 \([bg,ed)\) 的顺序,
gen是一个随机数生成器(mt19937)。 - 时间复杂度 \(O(n)\)。
- C++11 之后请尽量使用
shuffle而不是random_shuffle is_sorted(bg,ed)- 判断 \([bg,ed)\) 是否升序排序。
- 时间复杂度 \(O(n)\)。
minmax(a,b)- 返回一个
pair<>,其first为 \(\min(a,b)\),second为 \(\max(a,b)\)。 - 时间复杂度 \(O(1)\)。
- 常用于无向图存边的去重问题。
max(l)/min(l)- \(l\) 是一个初始化列表,这个函数返回 \(l\) 中最大 / 最小的元素。
- 时间复杂度 \(O(size_l)\)。
- 在多个元素求最大 / 最小时非常好用:
max({a,b,c}) minmax(l)- \(l\) 是一个初始化列表,这个函数返回一个
pair<>,其first为 \(l\) 中最小的元素,second为 \(l\) 中最大的元素。 - 时间复杂度 \(O(size_l)\)。
- 若要求一个序列 / 容器中最小和最大的元素,请使用
minmax_element。 minmax_element(bg,ed)- 返回一个
pair<>,其first为 \([bg,ed)\) 中最小的元素,second为 \([bg,ed)\) 中最大的元素。 - 时间复杂度 \(O(n)\)。
numeric 库
iota(bg,ed,val)- 将 \([bg,ed)\) 中的元素依次赋值为 \(val,val+1,val+2,\cdots\)
- 时间复杂度 \(O(n)\)。
- 常用于给并查集初始化。
iterator 库
-
prev(it)/next(it) -
返回迭代器 \(it\) 的前驱 / 后继。
- 复杂度与
++/--的复杂度相同,取决于容器。 - 可以更方便的实现许多内容:
1 2 3 4 5 6 7 | |
-
请习惯在 C++98 环境下使用
next的同学们注意:这会导致声明一个叫next的变量时出错。 -
begin(container)/end(container) -
返回容器 \(\texttt{container}\) 的
begin()和end()。 -
复杂度取决于容器。
-
作用就是相比原先要短一个字节:
1 2 3 | |
random 库
用于代替垃圾的 rand。
-
mt19937 -
一个类型,作为随机数生成器。
-
其构造函数应传入一个数字作为随机种子,使用时用法和
rand相同。 - 生成
unsigned int范围内的随机数。
1 2 3 | |
- 随机种子通常为时间相关,下面的非常常用,建议背过
1 2 | |
-
uniform_int_distribution<T>(L,R) -
随机数发生器,每次调用时传入一个
mt19937,返回一个 \([L,R]\) 之间的整数,类型为T,若T为空则默认为int。 -
uniform_real_distribution<T>(L,R) -
随机数发生器,每次调用时传入一个
mt19937,返回一个 \([L,R]\) 之间的实数,类型为T,若T为空则默认为double。
1 2 3 4 5 | |
cmath 库
exp2(x)- 返回 \(2^x\)。
log2(x)- 返回 \(\log_2(x)\)。
hypot(x,y)- 返回 \(\sqrt{x^2+y^2}\)。
- 常用于求两点之间的距离,非常方便。
NAN,INFINITY- 两个
define出的量,由编译器实现定义。
C++14
零零散散的语法糖
- 弃用了
register。
auto
auto可以在 lambda 的形参中出现(但注意,在普通函数中使用auto作为形参直到 C++20 才支持):
1 2 3 | |
auto可以作为普通函数的返回值:
1 2 3 | |
functional 库
- functional 库在 C++14 给大部分模板都加入了默认类型
void,如:greater<void>、plus<void>等, 这里的void可以省略,因此以后就可以不用写具体类型了:
1 2 3 4 | |
-
bit_not<> -
在 C++98 的 functional 库中有提到。
C++17
零零散散的语法糖
结构化绑定
- 从一个结构体 / 初始化列表中获取元素.
1 2 3 4 5 6 7 8 | |
- 也可以声明为引用:
1 2 3 4 5 6 7 | |
- 可以搭配 range-for 使用:
1 2 | |
- 常用于带边权的图遍历:
1 2 3 4 5 | |
类模板的实参推导
在使用类模板时不必写出具体的实参名:
1 2 3 4 5 | |
推导也可以嵌套,主要用于简化代码。
if 和 switch 中进行初始化
在 if 语句和 switch 语句中可以像 for 循环一样加入初始化语句。
1 2 3 | |
顺便提一句,在 C++98 中就已经可以这样写了:
1 2 | |
using 声明多个名称
可以使用 using 语句后跟一个逗号分隔的声明符列表。
1 | |
起到同时声明多个的作用。
对于不喜欢 using namespace std 的同学比较有用。
STL
set/map 的 merge
- 将一个
set/map并入另一个,若将 \(t\) 并入 \(s\) ,则所有 \(t\) 中未在 \(s\) 里出现的元素将被插入 \(s\),并在 \(t\) 中被删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
- 对于
s.merge(t),复杂度为 \(O(|t|\log(|s|+|t|))\)。
algorithm 库
-
以
shuffle替代random_shuffle,random_shuffle完全被废除。 -
sample(bg,ed,out,n,gen) - 从 \([bg,ed)\) 中随机取出 \(n\) 个元素,输出到
out迭代器中,随机数生成器为gen。 - 每个元素等将概率被选择。
out的一个具体的例子是back_inserter。
numeric 库
gcd(x,y)/lcm(x,y)- 返回 \(\gcd(|x|,|y|)\) / \(\operatorname {lcm}(|x|,|y|)\) 的值。
- 复杂度对数级别的。
- 保证了返回值的符号是正。
lcm的实现是先除后乘。
iterator 库
size(container)/empty(container)- 返回一个容器的
size()/ 是否为空。 -
复杂度取决于容器。
-
data(container) - 返回
container.data()。 - 复杂度取决于容器。
cmath 库
- 出现了一些比较谔谔的《数学特殊函数》,OI 中应该不会用到,如果想了解请前往 官网。
hypot(x,y,z)- 返回 \(\sqrt{x^2+y^2+z^2}\)。
- 复杂度 \(O(1)\)。
- 常用来求三维中两点距离。
C++20
零零散散的语法糖
三路比较运算符 <=>
新增运算符 <=> 满足:
- 若 \(a<b\),则
(a<=>b)\(<0\)。 - 若 \(a>b\),则
(a<=>b)\(>0\)。 - 若 \(a=b\),则
(a<=>b)\(=0\)。
重载此运算符后,就可以直接使用< > <= >= 几种运算符,但请注意若要使用 == 和 != 仍需再写一个函数:
1 2 3 4 5 6 7 8 9 10 | |
range-for 的初始化语句和初始化器
在 range-for 前面可以加入一个初始化语句 / 初始化器,可以是
- 表达式语句(空语句)。
- 声明语句(或结构化绑定)。
1 2 3 4 5 6 7 8 9 10 11 12 | |
规定有符号整数以补码实现
谔谔,谔谔。
终于可以写什么 &-2 这种东西了,好耶!
STL
contains
在 set/map/multiset/multimap 中查找一个元素n是否存在,如果使用 count 那么复杂度关于出现次数是线性的,这不好(出现次数为 \(m\),则复杂度为 \(O(m+\log n)\))。C++ 20 中加入的 contains 的复杂度是 \(O(\log n)\) 的,返回值是 true/false ,非常好用。
bit 库
C++20 加入的新库,头文件 <bit>。
-
popcount(x) -
返回 无符号整形 \(x\) 在二进制下 \(1\) 的个数。
-
时间复杂度有说 \(O(\log\log x)\) 的,也有说 \(O(1)\) 的。
-
一定比手写的快。
-
使用
__builtin_popcount实现,但会检查传入的参数是否为无符号整形。
1 2 3 4 5 6 | |
- 下述 bit 库中的函数也都会进行这个检查。
1 2 3 4 | |
-
上述函数的复杂度都是 \(O(1)\)。
-
rotl(x)/rotr(x) -
返回 \(x\) 二进制下逐位向左 / 右旋转得到的结果。
- 复杂度 \(O(1)\)。
-
好像没什么用?
-
bit_width(x) -
当 \(x=0\) 时返回 \(0\),否则返回 \(1+\lfloor \log_2(x) \rfloor\)。
- 即二进制下 \(x\) 的位数。
-
复杂度 \(O(1)\)。
-
bit_floor(x)/bit_ceil(x) -
返回不超过 \(x\) 的最大的 \(2\) 的整次幂 / 不小于 \(x\) 的最小的 \(2\) 的整次幂。
-
复杂度 \(O(1)\)。
-
has_single_bit(x) -
返回 \(x\) 是否为 \(2\) 的整次幂,相当于
popcount(x)==1。 - 复杂度 \(O(1)\)。
ranges 库
C++20 加入的库,提供了各种处理元素范围的组件,在头文件 <ranges> ,命名空间 std::ranges::views 或 std::views 中。
在 ranges 库中的一个函数 func ,其通常有两种形态:ranges::func_view 和 views::func,下文我们将全部选择更简短的后者。
-
view是一个指定范围的视图,可以在 \(O(1)\) 时间内完成移动 / 复制 / 赋值。 -
empty() -
一个空的
view。 -
single(val) -
一个只有 \(val\) 的
view。 -
iota(bg,ed) -
由 \([bg,ed)\) 之间所有自然数组成的
view。
1 2 3 | |
-
若省略 \(ed\) 则为一个无限长的序列。
-
可以搭配下文的
take一起使用。 -
all(v) -
包含 \(v\) 所有元素的
view。
1 2 3 4 | |
-
transform(v,func) -
对 \(v\) 中每个元素执行
func后的view。
1 2 3 4 | |
-
take(v,n) -
\(v\) 的前 \(n\) 个元素组成的
view。
1 2 3 4 | |
-
take_while(v,func) -
\(v\) 的起始元素到首个使得
func返回false为止的元素组成的view。
1 2 3 4 | |
-
drop_while(v,func) -
跳过直到第一个使得
func返回false的元素后,剩余元素组成的view。
1 2 3 4 | |
-
drop(v,n) -
\(v\) 的从第 \(n+1\) 个元素开始到结束(跳过前 \(n\) 个)的
view。
1 2 3 4 | |
-
reverse(v) -
翻转 \(v\) 的
view。
1 2 3 4 | |
-
filter(v,func) -
使得
func返回值为true的元素的view。
1 2 3 4 | |
- 可以使用运算符
|连接两个或多个范围适配器:
1 2 3 4 5 6 7 8 9 | |
- 注意,这里的
|是从左向右结合的:
1 2 3 4 5 6 | |
numbers 库
头文件 <numbers>,命名空间 std::numbers 中提供了大量的数学常数:
| 代码表示 | 数学表示 |
|---|---|
e_v |
\(e\) |
log2e_v |
\(\log_2e\) |
log10e_v |
\(\log_{10}e\) |
pi_v |
\(\pi\) |
inv_pi_v |
\(\frac 1\pi\) |
inv_sqrtpi_v |
\(\frac 1{\sqrt \pi}\) |
ln2_v |
\(\ln 2\) |
ln10_v |
\(\ln 10\) |
sqrt2_v |
\(\sqrt 2\) |
sqrt3_v |
\(\sqrt 3\) |
inv_sqrt3_v |
\(\frac 1{\sqrt 3}\) |
egamma_v |
欧拉-马斯克若尼常数 \(\gamma\) |
phi_v |
黄金比 \(\phi=\frac{\sqrt 5+1}{2}\)(注意这里是加号!!!) |
这些都是类模板,调用时请填入类型:
1 2 3 4 5 6 | |
去掉末尾的 _v 后直接就是一个常量:
1 | |