天天看点

整洁即是正义

读到这句话往往有两个原因:1.你是一个程序员,2.你想成为更好的程序员。

——《代码整洁之道》马丁(美)

如何让代码变得整洁

命名

无论是在现实世界还是在代码中,命名都是一件令人头疼的事情。在现实世界,人们往往基于其特征与人们对其寄予的期望对某事某物或者其他人命名,而在代码中,命名的原则与现实世界相同——也就是“见名知意”。但是与现实世界稍有不同的是,代码中的命名还要考虑命名冲突的问题。

  1. 见名知意,如临时保存的变量命名为 tmp 或者 temp ,代表答案的变量命名为 ans ,代表局部结果的变量命名为 res 等等……方便他人阅读,也方便日后的自己理解。
  2. 不用单个字母做变量名,除了大部分循环控制变量或题目中给定名称的变量之外,尽量避免用单个字母做变量名,既能在长代码中防止重复定义导致调用错误,又容易理解。
  3. 避免使用双关词,避免将同一个词用于不同目的,遵循一词一义的原则,也必须避免留下掩藏代码本意的错误线索。
  4. 符合意义的命名,类、结构体、变量的名称使用名词、名词短语或形容词等,函数名使用动词或动宾短语等等。
  5. 约定俗成,如把make_pair用mp代替,把push_back用pb代替等等。
  6. 不要卖萌,一定要通俗易懂,代码不是只有自己在看。
#define mp make_pair
#define pb push_back

typedef set<unsigned long long>::iterator ullIter;
typedef pair<int,int> pii;

const int maxn=int()+;
int n,m;
int tmp, ans;
long long sum[maxn];
           

-

函数

马丁:“Function should do one thing. They should do it well. They should do it only. ”,这句话已经抵得过千言万语。听起来简单,践行起来却并不容易。

  1. 短小,函数只做好一件事,当一个函数可以划分为多个功能不用的区段的时候,那么它该做的太多了,它不是一个好函数。一个函数要么执行某种操作,要么返回某个查询的值。
  2. 没有副作用,这一点相对于其他原则在算法竞赛中显得尤为重要or致命,比如一个会对查询对象动手动脚的查询函数,不仅不是一个好函数,甚至还会是一个致命的函数。不过有些不得不带有副作用的函数,必须要三思而后写,比如说需要下推标记的线段树查询函数。
  3. 不要重复自己(DRY),把代码写进函数的重要意义之一就是简化代码,使代码变得更具有结构性、更美观(高雅),但如果连函数自己都在重复自己的代码的话,这一切就失去了应有的意义。
void Modify(int l,int r,int add) {
    register int i;
    for(i=l;i<=r;++i)
        a[i]+=add;
    return;
}

int Getmax(int l,int r) {
    register int i, tmp=-inf;
    for(i=l;i<=r;++i)
        tmp=a[i]>tmp?a[i]:tmp;
    return tmp;
}

int Solve() {
    register int i;
    for(i=;i<q;++i)
        if(type[i]) Modify(l[i],r[i],add[i]);
        else printf("%d\n",Getmax(l[i],r[i]));
    return;
}
           

-

注释

“注释不是对劣质代码的补救”,这句话同样出自马金。如果一份代码是优秀的代码,那么尽管没有注释,这段代码也拥有着相当可观的可读性。不过恰当的注释对于一份好代码起到的是锦上添花的作用,使代码的可读性更高,这也意味着不要为了写注释而写多余的注释。

pair<int,int> s1[maxn], s2[maxn], s3[maxn]; //Roll mark of "pre, nex, cnt".
int t1,t2,t3;

void Rollback() { //Clear mark.
    for(;t1;--t1)
        pre[s1[t1].first]=s1[t1].second;
    for(;t2;--t2)
        nex[s2[t2].first]=s2[t2].second;
    for(;t3;--t3) {
        cnt[s3[t3].first]-=s3[t3].second;
        bcnt[bel[s3[t3].first]]-=s3[t3].second;
    }
    return;
}
           

-

代码风格

引战雷区,不写!

大家自便不要打架,伤不到人伤到花花草草也是不好的。

-

异常处理

  1. 适当使用assert(),很大一部分竞赛选手不知道assert,或是只是在codeforces上读程序的时候见过几次,不会用的选手还是占很大比例的。不过,在程序中适当地加入assert能够大大地提高排查错误的速度,在有限时间的考试之内,这种异常处理的技巧对长代码题、模拟题等题目都有明显的增益效果。
  2. 记得删除assert() ,当代码debug完毕后,记得删除assert函数或是在assert头文件前define一下ndebug。否则程序中的assert语句将会一定程度上影响程序的运行效率!
//#define NDEBUG
#include <assert.h>
int C(int n,int m) {
    assert(n>=m);
    return C(n-,m-)+C(n-,m);
}
           

-

代码结构

  1. 代码块间加入空行,功能不同的代码块间加入空行,执行步骤不连贯的代码块间加入空行。
  2. 每个类/结构体只完成一种功能/结构。
  3. 变量即开即用,变量即开即用、即用即开,这样写代码的时候对变量类型有一个直观的印象。
  4. 干净的代码,去掉调试完成的代码中的调试语句,做到没有冗余的注释。

-

常数管理

  1. 函数的参数为class/struct/vector/set/map/queue……时,如果可以的话传这种参一定要加上引用,否则传参的速度就够你的常数喝一壶的了。对于不能修改其中值的还要加上const保护下传的引用,见示例。
  2. 对于循环控制变量或者需要在函数内进行多次迭代的变量,最好定义为寄存器变量,因为寄存器变量不是存储在内存上,而是直接存储在CPU的寄存器上。所以频繁访问时效率会高很多,但是也要注意寄存器的空间有限,寄存器变量的数量要有节制,见示例。
  3. 巧用位运算,例如“a^b”等效于“a!=b”,“!(a^b)”等效于“a==b”,解析字符串的时候用“(v<<1)+(v<<3)”代替“v*10”,哈希的时候base设为131就可以转化为“(v<<7)+(val<<1)+v”,还有很多小技巧这里不做详细的介绍了,下面会用一个我手写的哈希表做示例。
  4. 多乘几次,少模几次。因为取模运算很慢,所以在乘(加)不爆的情况下尽量多乘(加)几次再统一取模,有些题目可能会有奇效。
  5. 初始化变量的方式,用括号初始化的时候只调用了一次构造函数,而定义后再用等号赋值则会调用析构函数和构造函数,这一点在初始化结构体的时候比较明显,这个也见示例即可。
  6. 对于循环控制变量的“i++”和“++i”,因为前者要存贮一个临时的值,所以后者更快一筹。
  7. 开数组的姿势, 如a[10][100000]和a[100000][10],请自觉选择后者,把大的放在前面会快。
  8. 适当inline,把代码块较小、调用十分频繁、非递归的函数inline处理。
  9. 读入优化, 读入量很大的要注意读入优化,别把读入优化写挂了啊……
typedef unsigned long long Ull;
struct Hashset {
    int Cache_size;
    int Header[<<], Next[int()], siz;
    Ull val[int()];
    void Init() {
        Cache_size=(<<)-;
        siz=;
        return;
    }
    inline void Insert(const Ull &v) {
        register int key(v&Cache_size), cur(Header[key]);
        if(!cur) {
            val[Header[key]=++siz]=v;
            return;
        }
        while(Next[cur] && val[cur]^v) cur=Next[cur];
        if(!(val[cur]^v)) return;
        val[Next[cur]=++siz]=v;
        return;
    }
}Hset;
           

为何要让代码变得整洁

因为看着挺高雅的。

继续阅读