从 C++98 到 C++20,寻觅甜甜的语法糖们

Acc_Robin

2024-01-17 23:38:57

科技·工程

从 C++98 到 C++20,寻觅甜甜的语法糖们

谔谔,谔谔……

于是一时兴起,写了这么一篇博客,记录一些比较平时比较少见的 / 冷门的 / 超前的语法知识,供大家学习参考。

本人水平有限,若有遗漏或错误请大家毫不吝啬地指出,谢谢!

如果不确定能不能用,请见 《关于NOI系列活动中编程语言使用限制的补充说明》

upd on 2024/1/17: for saving

目录

C++98

C++11

C++14

C++17

C++20

C++ 98

algorithm 库

algorithm 中有大量的函数,这些函数都位于命名空间 std 中。

最常见的有 max/minswapsortuniquelower_bound/upper_boundreverse

一些常见且比较重要的函数

一些比较冷门的函数

GNU 私货

有一些以双下划线开头的函数并未在 C++ 标准中,是 GNU 的私货,在 NOI 系列赛事中可以使用。

numeric 库

这里的函数,真的很好用。

functional 库

常见的函数有 less<>/greater<> 等。

事实上,大部分运算 / 比较也在这里:

plus<>;//x+y
minus<>;//x-y
multiplies<>;//x*y
divides<>;//x/y
modulus<>;//x%y
negate<>;//-x

equal_to<>;//x==y
not_equal_to<>;//x!=y
greater<>;//x>y
less<>;//x<y
greater_equal<>;//x>=y
less_equal<>;//x<=y

logical_and<>;//x&&y
logical_or<>;//x||y
logical_not<>;//!x

bit_and<>;//x&y
bit_or<>;//x|y
bit_xor<>;//x^y
//注意 bit_not(~x) 是 C++14 的哦~

cmath 库

__builtin 家族

这里的内容并不在 C++ 标准中,全部都是 GNU 的私货,若使用其它编译器则可能无法通过编译。

如果 x 的类型是 long long,请务必使用 __builtin_xxxll(x)(如 __builtin_popcountll(x)),否则将可能造成 FST 等严重后果。

C++ 11

从 C++98 到 C++11 是一次重大的飞跃,许许多多非常实用的函数与语法如雨后春笋般出现。

零零散散的语法糖

auto

auto 在 C++98 中是一个较偏僻冷门的东西,因此在 C++11 中直接将其抛弃,并赋予其新生。

lambda 表达式

lambda 相当于一个函数,其形式多变,但都是由若干部分组成:

[captures](params)->T{body}

其中 captures 填捕获的方式,params 填传入的参数,T 填返回值类型,body 就是函数主体。

也可使用 auto 将 lambda 表达式作为一个可调用的函数:

auto sqr=[](double x){
    return x*x;
};

那么每次调用 sqr(r) 都将返回 r\times r 的值。

这样写的函数将自带 inline,比写 inline 短多了。

range-for

for 循环中轻松地遍历容器(vectorsetlist 等):

vector<int>v={5,1,3,4,2};
for(int x:v)cout<<x<<' ';
//output:5 1 3 4 2
//按顺序遍历 v 中的每一个元素,并赋值给 x。

也可以将 x 声明为引用:

vector<int>v(n);
for(int&x:v)cin>>x;
//读入一个长度为 n 的序列

注意,x 的类型必须与 v 中元素的类型保持一致,否则会 CE。

也可以使用 auto 进行声明。

注意,若遍历数组,那将从头到尾遍历一遍,不推荐。

用处极为广泛,常用于对于 vector 存图的遍历等。

STL

emplace

在很多 STL 容器中都出现了一个新的函数——emplace,如:

set<int>s;
s.insert(1);//C++98
s.emplace(1);//C++11

queue<int>q;
q.push(2);//C++98
q.emplace(2);//C++11

vector<int>v;
v.push_back(3)//C++98
v.emplace_back(3);//C++11

deque<int>dq;
dq.push_front(4)//C++98
dq.emplace_front(4);//C++11

emplace 相比原先的 insert/push/push_back/push_front 区别是,emplace 通过调用元素的构造函数来进行插入,所以它会比之前的函数更快一些。

因此也产生了使用上的区别:

set<pair<int,int>>s;
s.insert(make_pair(1,2));//C++98
s.emplace(1,2);//C++11

更加便于书写。

但这要求用户自己的类型必须含有构造函数:

struct A{
    int x;
};
queue<A>q;
q.emplace(1);//CE,A 没有构造函数

struct B{
    int x;
    B(int _x=0):x(_x){}
};
queue<B>p;
p.emplace(1);//OK,B 有构造函数

shrink_to_fit

vector/deque/string/basic_stringshrink_to_fit 可以使其 capacity 调整为 size 的大小,如:

vector<int>v={1,2,3};
cout<<v.size()<<' '<<v.capacity()<<'\n';
v.clear();
cout<<v.size()<<' '<<v.capacity()<<'\n';
v.shrink_to_fit();
cout<<v.size()<<' '<<v.capacity()<<'\n';
/*
output:
3 3
0 3
0 0
*/

常用于 clear() 之后释放内存。

algorithm 库

numeric 库

iterator 库

functional 库

random 库

用于代替垃圾的 rand

mt19937 gen(chrono::system_clock::now().time_since_epoch().count());
uniform_int_distribution<>dist(1,1000);
int x=dist(gen);// x is a random int in [1,1000]
uniform_real_distribution<>dist2(-10,10);
double y=dist2(gen);// y is a random double in [-10,10]

cmath 库

C++14

零零散散的语法糖

auto

functional 库

C++17

零零散散的语法糖

结构化绑定

类模板的实参推导

在使用类模板时不必写出具体的实参名:

auto x=pair<int,double>(1,2.5);//C++11
auto y=pair(5,7.2);//C++17:推导了 pair<int,double>

vector<int>v={1,2,3};//C++11
vector w={1,2,3};//C++17:推导了 vector<int>

推导也可以嵌套,主要用于简化代码。

if 和 switch 中进行初始化

在 if 语句和 switch 语句中可以像 for 循环一样加入初始化语句。

cin>>n;
if(int x=f(n);x>=10)
  cout<<x<<'\n';

顺便提一句,在 C++98 中就已经可以这样写了:

if(int x=f(n))//if x is true then:
  //...

using 声明多个名称

可以使用 using 语句后跟一个逗号分隔的声明符列表。

using std::cin,std::cout,std::cerr;

起到同时声明多个的作用。

对于不喜欢 using namespace std 的同学比较有用。

STL

set/map 的 merge

algorithm 库

numeric 库

iterator 库

cmath 库

C++20

零零散散的语法糖

三路比较运算符 <=>

新增运算符 <=> 满足:

重载此运算符后,就可以直接使用< > <= >= 几种运算符,但请注意若要使用 ==!= 仍需再写一个函数:

struct T{
 int x,y;
 T(int _x=0,int _y=0):x(_x),y(_y){}
 auto operator<=>(const T&_)const{
  return x-_.x;
 }
}a(1,3),b(2,4);

if(a<b)cout<<"a<b\n";//OK,output: a<b
if(a==b)cout<<"a==b\n";//CE not operator==

range-for 的初始化语句和初始化器

range-for 前面可以加入一个初始化语句 / 初始化器,可以是

#include<bits/stdc++.h>
using namespace std;
int main() {
 vector<pair<int,int>>v={{1,2},{3,4}};
 for(int i=0;auto[x,y]:v)
  cout<<(i++)<<" : ("<<x<<','<<y<<")\n";
}
/*
output:
0 : (1,2)
1 : (3,4)
*/

更便利了捏~

规定有符号整数以补码实现

谔谔,谔谔。

终于可以写什么 &-2 这种东西了,好耶!

STL

contains

set/map/multiset/multimap 中查找一个元素是否存在,如果使用 count 那么复杂度关于出现次数是线性的,这不好(出现次数为 m,则复杂度为 O(m+\log n))。C++ 20 中加入的 contains 的复杂度是 O(\log ) 的,返回值是 true/false ,非常好用。

bit 库

C++20 加入的新库,头文件 <bit>

ranges 库

C++20 加入的库,提供了各种处理元素范围的组件,在头文件 <ranges> ,命名空间 std::ranges::viewsstd::views 中。

在 ranges 库中的一个函数 func ,其通常有两种形态:ranges::func_viewviews::func,下文我们将全部选择更简短的后者。

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}(注意这里是加号!!!)

这些都是类模板,调用时请填入类型:

#include<numbers>
#include<iomanip>
#include<iostream>
int main(){
  std::cout<<std::fixed<<std::setprecision(9)<<std::numbers::pi_v<double><<'\n';
}

去掉末尾的 _v 后直接就是一个常量:

cout<<numbers::e<<'\n';