信息学竞赛中的 C++ 语法糖

本文的内容基于 gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 和 C++ 14。
但是并不是所有内容都是 C++ 14 新增内容。

1. auto 关键字

auto 关键字是 C++11 新增的一种类型推导机制,它可以让编译器根据变量的初始化表达式推导出变量的类型,从而简化代码编写,提高代码的可读性和可维护性。

对于很长的迭代器类型(比如 std::vector<int>::iterator),使用 auto 自动推导可以有效减少代码量。

例如以下的代码:

int main() {
    auto i = 10; // 推导为 int
    auto d = 3.14; // 推导为 double
    std::vetcotr<int> vec = {1, 2, 3, 4, 5};
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        // 推导为 std::vector<int>::iterator 迭代器
        std::cout << *it << std::endl;
    }
}
C++

使用 decltype 类型可以获得推导的类型声明符。

template <class T1, class T2>
void add(T1 v1, T2 v2) {
    // 以下两种代码等价。
    // auto
    auto ret = v1 + v2;
    // decltype
    decltype(v1 + v2) ret = v1 + v2;
    return ret;
}
C++

2. 范围 for

范围 for 循环是 C++11 引入的一种循环结构,它可以更方便地遍历容器中的元素,例如数组、向量、列表等。

// 这些对象都可以使用范围 for
vector<int> vec = {1, 2, 3, 4, 5};
map<string, int> ma = {{"a", 1}, {"b", 2}, {"c", 3}};
// 迭代器
for (auto it = vec.begin(); it != vec.end(); ++it) {
    cout << *it << endl;
}
// 范围 for
for (int i : vec) {
    cout << i << endl;
}
C++

但是,虽然 C 风格数组(int list[100];)支持范围 for 但不建议使用,因为这会导致效率降低。

3. Lambda 表达式

“$\lambda$”!

Lambda 表达式是 C++ 的一种匿名函数的语法,类似于箭头函数(Typescript 狂喜)。

Lambda 函数常用于 algorithm 中的 sort() 上,比如:

struct Edge {
    int u, v, w;
};
Edge edges[1000];
// 使用 sort 函数对 edges 进行排序
sort(edges, edges + 1000, [](Edge a, Edge b) {
    return a.w < b.w;
});
C++

当然,你也可以使用 Lambda 表达式动态创建新函数:

auto add = [](int a, int b) {
    return a + b;
};
C++

需要注意的是,Lambda 表达式内的语句默认不能访问函数外的任何值(包括全局变量),如需要查看 / 修改需要设置 Lambda 捕捉表达式:

// 捕捉全部外部变量,值捕获(只读)
auto get = [=]() {
    return val;
};
// 捕获的值传入后无法修改(const),加上关键字 mutable 可取消常性(但修改的是 Lambda 内部的副本)
auto self_add = [=]() mutable {
    val++;
    return val;
};
// 捕捉全部外部变量,引用捕获(可修改)
auto set = [&](int now) {
    val = now;
};
C++

使用值捕获,当 Lambda 函数被定义时,变量的值就固定了,而使用引用捕获,Lambda 函数可以修改外部变量的值。

4. 参数模板

现在,我们需要编写一份快读函数(read())。但是,如果这道题既需要 int 类型的输入,又需要 long long 类型的输入,那么我们就需要写两个不同的 read() 函数。这样,我们就需要写两份代码,这样会导致代码冗余。

因此,我们可以使用参数模板来解决这个问题:

template <class T>
inline T read() {
    T x = 0;
    int f = 1;
    char ch;
    ch = getchar();
    while (ch > '9' || ch < '0') {
        if (ch == '-')
            f = -f;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        x = (x << 1) + (x << 3) + (ch & 15);
        ch = getchar();
    }
    return x * f;
}
C++

这样,我们就可以使用 read<int>() 和 read<long long>() 来读取 int 和 long long 类型的输入了。

5. std::to_string() 转字符串

to_string() 函数可以将整数、浮点数、长整数等各种你想不到的内容转换为字符串 std::string

int val = 31415926; // Also support: long long, double, float
std::string str = std::to_string(val);
char charstr[] = str.c_str(); // 将 std::string 转换为 char 数组
C++

6. 数字分隔符

高达 9 位的整数,数学课本可以这么写:

$\text{MOD} \space = \space 998'244'353$

而我们的 C++ 程序只能这么写:

#define MOD 998244353
C++

…… 吗?

自 C++14 以后,数字中可以加数字分隔符 “'”(单引号)增加可读性,比如上面的代码其实可以:

#define MOD 998'244'353
C++

其实,你在哪里加分隔符都可以。

7. 连续的 >

自 C++11 以来,STL 定义嵌套尖括号时连续的右尖括号中间不需要加空格也可以被编译器识别了。

typedef std::map<int, std::map <int, std::map <int, int> > > // c++98LongTypedef;
typedef std::map<int, std::map <int, std::map <int, int>>>   // c++11LongTypedef;
C++

此作品(信息学竞赛中的 C++ 语法糖)基于 CC-BY-NC-SA 4.0 协议授权。

转载请注明来源:作者:CodeZhangBorui,链接:https://codezhangborui.com/2024/05/oi-c-syntactic-sugar/

暂无评论

发送评论 编辑评论


				
上一篇
下一篇