前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个合格的ACMer的代码当中,都藏着哪些秘密?

一个合格的ACMer的代码当中,都藏着哪些秘密?

作者头像
ACM算法日常
发布2021-06-16 16:24:52
5760
发布2021-06-16 16:24:52
举报
文章被收录于专栏:ACM算法日常

今天给大家聊聊C++中的头文件,之前我在写算法专题展示源代码的时候,很多小伙伴给我留言说被我的头文件中的内容震惊了。其实之所以我的头文件这么复杂,完全是因为它是我一直从大学时期acm竞赛当中沿用下来的。对于acm竞赛的选手们来说,这样的头文件其实算是小儿科了。

今天就和大家来看看,acmer的头文件当中都藏着哪些秘密。

首先,我们先来看我完整的头文件代码。

代码语言:javascript
复制
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <functional>
#define rep(i,a,b) for (int i=a;i<b;i++)
#define Rep(i,a,b) for (int i=a;i>b;i--)
#define foreach(e,x) for (__typeof(x.begin()) e=x.begin();e!=x.end();e++)
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
#define MEM(a,x) memset(a,x,sizeof a)
#define pii pair<int, int>
#define LL long long
using namespace std;
const int N=500005;
const long long Mod=99997867;

include部分

首先我们来看include的部分,我们一个一个来看,iostream不用多说了,C++标准输入输出的头文件,包含了C++输入输出流函数,也就是经典的cin、cout。说到cin、cout多说两句,cin、cout的开销要比C语言下的scanf和printf慢很多,很容易影响程序运行的性能。所以对于acmer来说,能用scanf和printf完成的,就绝对不会用cin、cout。当然scanf和printf也不是最快的,还有更快的getchar和putchar,所以有些玩家会自己手写循环读入char然后转成int或者是float的函数,当然绝大多数情况下并不需要这样。

cstdio和iostream同样的功能,不过是C语言中的输入输出函数,不多说。

cstring同样属于C语言,是C语言中的字符串库,有很多字符串相关的函数。同样由于性能的原因,能用C语言中char[]完成的就不用使用C++的string。接下来的string库也不用多说,C++的字符串处理库。

cstdlib库函数等同于C语言中stdlib.h,封装了一些常用的库函数,如rand、srand、free、malloc等。

cmath库等于C语言中的math.h,封装了一些数学运算相关的库函数,如pow、sqrt等。

后面的queue、vector、map、set都是STL库,包含了一些比较好用的数据结构。比如queue中封装了queue以及dequeue,和priority_queue,也就是队列、双端队列和优先队列。vector、map、set分别是线性表、映射表以及集合,熟练使用它们可以极大的降低编码的复杂度。

algorithm库翻译过来就是算法库,当中自然封装了不少的算法。比如sort排序、reverse翻转、next_permutation下一个全排列、lower_bound、upper_bound函数等。

最后一个functional库,用得不多,顾名思义当中封装了一些关于函数对象的操作。比如bind、reference_wrapper等等。

其实关于include,有一个取巧的方法可以一次性include所有的头文件:

代码语言:javascript
复制
#include <bits/stdc++.h>

不过这种方法有一个小问题,并不是所有的环境都支持,尤其是在正式比赛的场合,所以一般情况下大家都是一些网络赛才会用它,我个人由于懒得区分环境,所以还是习惯自己一个一个include。

define部分

define是C++当中非常强大的功能,它可以定义规则对代码进行替换。熟练使用define同样可以大大简化编码。但是要注意,凡事不能过度,如果define使用过多会影响程序的可读性,也可能对其他人的编码造成影响。所以很多大公司是禁止使用define的,我个人倒觉得其实也不用这么严肃,define可以用,遵守规范,适当使用就可以了。

首先是这两行,是for-loop的define。

代码语言:javascript
复制
#define rep(i,a,b) for (int i=a;i<b;i++)
#define Rep(i,a,b) for (int i=a;i>b;i--)
#define foreach(e,x) for (__typeof(x.begin()) e=x.begin();e!=x.end();e++)

rep是repeat的缩写,使用的时候只需要rep(i, a, b)就可以代替冗长的for循环的编写,其中i是循环变量,a和b分别是循环的上下界,注意是左闭右开区间。

Rep也是同样的逻辑,只不过是倒序的循环。

foreach使用的是C++11的新特性,可以实现自动迭代,用的不多,在一些场景下非常方便。

代码语言:javascript
复制
#define mid ((l+r)>>1)

这行用在二分查找当中,左边界是l,右边界是r,那么它们的中点就是(l + r) / 2,用位运算表示就是:(l + r) >> 1。例子:

代码语言:javascript
复制
# define之前
while (l + 1 < r) {
    int m = (l + r) >> 1;
    if (a[m] <= v) {
  l = m;
    }else {
  r = m;
    }
}

# define之后
while (l + 1 < r) {
    if (a[mid] <= v) {
  l = mid;
    }else {
  r = mid;
    }
}

代码语言:javascript
复制
#define lson (k<<1)
#define rson (k<<1|1)

这两行主要是用在线段树上,因为C++往往不使用类来实现线段树,而是通过数组来模拟实现。在线段树当中,如果某一个节点的id是u,那么它的左孩子是2 x u,右孩子的id是2 x u + 1,用位运算来表示就是u << 1和u << 1 | 1。

代码语言:javascript
复制
#define MEM(a,x) memset(a,x,sizeof a)
#define pii pair<int, int>
#define LL long long 

最后三行放在一起说,第一行是memset的缩写,memset可以用来初始化数组。pii缩写了pair<int, int>,pair是用来绑定两个变量的数据结构。最后是LL缩写了long long,long long是64位的int,它的范围要比int大得多。

关于类型的重定义,这里使用define并不是非常好,更好的方法是使用typedef,例如上面的pair和long long可以写成:

代码语言:javascript
复制
typedef pair<int, int> pii;
typedef long long LL;

相比define之下这样更规范一些,因为define是生硬的字符串替换,而typedef则是类型别名,能够被编译器检查,所以能使用typedef还是使用typedef。

尾声

除了上面用到的这些头文件之外,还有一些更高端的用法,比如一些模板类,以及一些常用的算法,比如gcd等等。但我个人觉得意义不是非常大,对于面试、笔试的代码环节来说,以上的这些头文件已经足够使用了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ACM算法日常 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • include部分
  • define部分
  • 尾声
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档