前言¶
约 1248 个字 170 行代码 预计阅读时间 6 分钟
鉴于多次看到学弟学妹各种的奇葩命名和较为繁琐的代码,2023.4.10动笔,2023.4.11完成。
程序设计竞赛的命名规范,与开发略有不同。首先最重要的点,是不要重名,下面抓一个cf橙名选手在ecf犯下的错误
//图片1(待上传)
其次是尽快的编码速度,也就是短命名。
其次是稍稍考虑可读性,至少队友之间能看得懂。
本文还介绍了短路,能有效降低代码阅读难度,最后对于编码风格做了一定的讨论。
命名规范¶
define¶
首先是个人观点,除了ifdef这种,以及dls的计算几何板子(它太好用了),能不用define就不用define
当然相当多的选手,甚至顶尖选手,用一大堆define加快编码速度,个人不苟同
define能完成的事,大多可以用其他语法代替,而且能有效避免重名问题
typedef¶
有些类型名太长,为加快编码速度,因此对类型重命名,使用typedef,例子如下
typedef long long ll;
typedef long double ld;
int main()
{
ll a=114514;
ld b=1.919180;
}
const¶
有些需要多次使用的数字,为加快编码速度,便于阅读,加快程序运行速度(指模数),以及避免打错,将其命名为常量,使用const,例子如下
const int N=2e5+3;
//表示n的最大值多一点点,例如重链剖分的众多数组开同样大的空间,和欧拉筛限定范围
//通常用变量的大写作为常量,表示该变量的数据范围,比如M表示m的最大值多一点
int son[N], siz[N], dep[N], fa[N],top[N], dfn[N];
void seive()
{
int cnt=0;
for(int i=2;i<N;++i)
{
if(!v[i])
p[cnt++]=i;
for(int j=0;j<cnt && i*p[j]<N;++j)
{
v[i*p[j]]=1;
if(i%p[j]==0)
break;
}
}
}
const double PI = acos(-1.0);
//圆周率pi,在计算几何中用到偏多
const int mod1 = 1e9 + 7;//常见模数1
const int mod2 = 998244353;//常见模数2
//modulus的缩写,表示模数,模数定义为常量和变量,二者运行速度差很多,所以必须用常量
const int INF = 0x3f3f3f3f;
//infinite的缩写,表示无穷大,相比于int最大值,满足无穷大加无穷大等于无穷大的需求
const double EPS = 1e-6;
//epsilon的缩写,表示无穷小,用来判断两个浮点数是否相等,因为计算机计算有误差
//
int sign(double x) { return x < -EPS ? -1 : x > EPS; }
//判断数符号,负数返回-1,0返回0,正数返回1
bool floatEqual(double x, double y) { return !sign(x - y); }
//
标识符命名¶
尽量用3-4个字母,是单词或者短句的缩写,下面给出一些例子
dis[N]//distance的缩写,表示图上点到起点的最短距离
vis[N]//visited的缩写,表示是否访问过该节点
fa[N]//father的缩写,在dfs中为避免走回头路,表示来时的路,或者树上的祖先
p[N]//prime的缩写,表示质数表
siz[N]//size的缩写,表示子树大小,或者其他的集合大小
hson[N]//heavy son的缩写,表示重链剖分的重儿子
dep[N]//depth的缩写,表示树上节点的深度
dfn[N]//dfs number的缩写,表示dfs时到达节点的先后顺序
lz[N]//lazy tag的缩写,表示线段树的懒标记
f[N]//union&find的缩写,表示并查集
C[N][N]//combination的缩写,表示组合数
fac[N]//factorial的缩写,表示阶乘
queue<int> q//queue的缩写,表示队列
priority_queue<int> pq//priority_queue的缩写,表示优先队列
namespace bit{}//binary indexed tree的缩写,表示树状数组或者二进制索引树
vector<int> e[N]//edge的缩写,表示邻接表
int head[N]//表示链式前向星的链头
cnt,tot,res,ans,id,tmp//count,total,result,answer,identify,temporary的缩写
dfs,bfs,kru,dij等各种算法//depth first search,breadth first search,kruskal,dijkstra的缩写
变量作用域¶
局部变量¶
生效在函数的{}内,出花括号就结束生命周期,需要赋初值,否则为随机
占据的空间是在栈(stack)中一段连续的空间,栈的空间默认大小为2M或1M,较小
为避免重名,小变量能用局部变量的尽量用局部变量,函数如果用到局部变量用传参解决(能引用不传值)
当然,代码特别短的时候,小变量可以作为全局变量
全局变量¶
生效在所有地方,int类型初值为0,char类型初值为'\0',其他类型都有默认初值
占据的空间是在全局区(静态区)(static),全局区的空间为2G
所以大数组或者STL 容器一般作为全局变量
手动申请内存的变量¶
malloc和new出的空间,开在堆(heap)的一段不连续的空间,理论上是硬盘大小
不过在程序设计竞赛内,我只见过用指针写法的一些数据结构或者长链剖分,一般情况下不用指针
短路¶
概念:当已经知道整体表达式的值,不必再计算后面的表达式
在与运算 && 中,若顺序靠前的逻辑表达式为false,则不执行后面逻辑表达式
类似的,在或运算 || 中,若顺序靠前的逻辑表达式为true,则不执行后面逻辑表达式
类似的,在ifelse中,若顺序靠前的逻辑表达式为true,则不判断后面逻辑表达式(给定分数输出等级是一个例子)
if(score<60)
//
else if(score>=60 && score<70)
//
else if(score>=70 && score<90)
//
else
//
上述代码可以改写成下面这样
if(score<60)
//
else if(score<70)
//
else if(score<90)
//
else
//
还有一种短路是尽量使用continue,break,return这些
例如跳出多层循环,把循环放到函数中用return会更方便,下面是对比
int flag=0;
for(int i=0;i<n && !flag;++i)
{
for(int j=0;j<n && !flag;++j)
{
for(int k=0;k<n && !flag;++k)
{
if(XXX)
flag=1;
}
}
}
上述代码可以改写成下面这样
void calculateDP(int n)
{
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
for(int k=0;k<n;++k)
{
if(XXX)
return;
}
}
}
}
if(XXX)
{
cout<<"NO\n";
continue;
}
/*
*/
cout<<"YES\n";
上述代码可以改写成下面这样
bool solve()
{
if(XXX)
return false;
/*
*/
return true;
}
cout<<(solve()?"YES":"NO")<<"\n";
如果要输出方案,可以改成这样
if(solve())
{
cout<<"YES\n";
//
}
else
cout<<"NO\n";
if嵌套,使用continue或者break会更简洁
for()
{
if()
{
if()
{
if()
{}
}
}
}
上述代码可以改写成下面这样
for()
{
if()
continue;
if()
continue;
/*
*/
}
编码风格¶
本文不讨论换行缩进空格这些格式上的问题。
首先需要了解一下未定义行为(undefined behavior,简称ub),c/cpp语言标准未做规定的行为。编译器可能不会报错,但是这些行为编译器会自行处理,所以不同的编译器会出现不同的结果。
最著名的ub应该是下面这条
int a=1;
a=++a+a++;
一行代码只写一条语句¶
形如下面的代码极有可能是ub,所以不建议这么写,而是每条语句单独成行
a++,b+=a,c+=b;
命名空间¶
以jly为代表的选手,不使用using namespace std;
不用的话,可以用max,min这些原来会和std库重名的标识符了
不过个人还是喜欢用using namespace std;
标识符命名¶
直观,望文生义,最好用英文单词缩写,最好不用拼音
对于某些字体下难以区分的字符,要么换字体,要么不用这些字符命名,比如lL1|(分别是小写l,大写L,数字1,或运算|)和oO0(分别是小写o,大写O,数字0)
lambda¶
如果是一次性函数,可以用,如果是多次用的函数,建议不用
程序功能块划分¶
当一个函数行数太多时,一般建议拆分成多个函数,按照功能来拆分,使得每一块更好debug
个人习惯是,多测和输入输出都放在main函数,比如下面这样
int n,a[N];
int solve()
{
}
int main()
{
int T;
cin>>T;
for(int kase=1;kase<=T;++kase)
{
cin>>n;
for(int i=0;i<n;++i)
cin>>a[i];
cout<<solve()<<"\n";
}
}