代码风格变迁史

Posted by wyj on October 2, 2018 / Edited on August 21, 2020

深夜发现今年年初写的程序现在已经觉得丑陋异常了压根无法维护,移植到linux下时一直段错误。所以有了这篇。

//不保证以下的pascal符合规范。N久没写过了


long long ago

当时大概14年。写的pascal。对FPC只会当成TP用。不怎么懂函数。缩进是TP风格的和上一行结尾对齐。

//当时刚会random,非常高兴。
begin
     randomize;
     b:step:=0;
     r:=random(1000);
     write('Shu ru yi ge 1000 yi nei de shu: ');
     a:readln(n);
     if n=r
        then
            begin
                 writeln('You are right!');
                 writeln('step: ',step+1);
                 writeln('Try again?   (0:yes   1:no)');
                 readln(s);
                 if s='0'
                    then goto b
                    else exit;
            end
        else
            begin
                 inc(step);
                 if n<r
                    then writeln(#24)
                    else writeln(#25);
                 goto a;
            end;
     readln
end.

七上的时候

15年。差不太多。丑陋的goto。但是有写很长的程序的能力了。缩进变成了两格。懂得面向过程了。六子棋就主要是这时候写的。全是拼音。这个程序因为跨越了两年多,所以风格非常不统一。

begin
   {$ifndef Win32} //这就不用说了,是七下的
     exit;  //缩进也千奇百怪,从TP的一直到C的,混乱不堪。
   {$endif}
   f:=0;
start:init(f);  //各种goto是早期的风格
   if not jmp
     then first;
   repeat
  l1:bj:=true;draw(0);
 lt1:SetFillStyle(1,black); //这是很晚的时候才出现的PascalCase
     bar(WMaxY+50,40,WMaxY+120,56);
     repeat
        if KeyPressed then
...........

七下的时候

16年。懂得了pascal的很多高端特性。算\(\pi\),五子棋,俄罗斯方块是这时候写的。(当时闲的发慌)我的第一个C++程序(Visual C++除外)就是在这个时候写的。

E1:(现在我的FFT比三年前的优美一万倍)

procedure fft(var a:cmparr;lg:longint);
var h:array[0..dmaxlen-1]of longint;
    tmp:cmp;t,i,j,x,len:longint;
begin
    fillchar(h,sizeof(h),0);
    len:=1<<lg;
    for i:=0 to len-1 do
      if h[i]=0 then
        begin
          x:=rev[lg,i];
          tmp:=a[i];a[i]:=a[x];a[x]:=tmp;
          h[x]:=1;
        end;
    t:=1;
    for i:=1 to lg do
      begin
        fillchar(h,sizeof(h),0);
        for j:=0 to len-1 do
          if h[j]=0 then
            begin
              x:=t xor j;
              a[x]:=a[x]*w[lg,(x and (1<<(i-1)-1))<<(lg-i)]; 
              //不过这一行从未被改变,因为我现在已经忘了它的原理
              tmp:=a[j]+a[x];
              a[x]:=a[j]-a[x];
              a[j]:=tmp;h[x]:=1;
            end;
        t:=t<<1;
      end;
end;

E2:(后期,各种模仿fpc的source,面向过程登峰造极)

{$APPTYPE GUI}
{$INLINE ON}
program Tetris;

uses Windows,Graph,WinMouse,DOS,WinCRT;

const pixel=25;
      colors:array[1..7]of integer
            =(LightGray,green,brown,LightRed,LightBlue,magenta,LightMagenta);
      base:array[1..7,1..4,1..4]of integer=
       (((1,0,0,0),(1,0,0,0),(1,0,0,0),(1,0,0,0)),
        ((1,1,0,0),(1,0,0,0),(1,0,0,0),(0,0,0,0)),
        ((1,0,0,0),(1,0,0,0),(1,1,0,0),(0,0,0,0)),
        ((1,0,0,0),(1,1,0,0),(1,0,0,0),(0,0,0,0)),
        ((1,0,0,0),(1,1,0,0),(0,1,0,0),(0,0,0,0)),
        ((1,1,0,0),(0,1,1,0),(0,0,0,0),(0,0,0,0)),
        ((1,1,0,0),(1,1,0,0),(0,0,0,0),(0,0,0,0)));

type RkRec=record sc,y,m,d:longint;end;

var x,y,dy:integer;
    MaxLv,level,score,row,TRow,TotRank:longint;
    mx,my,ms:longint;
    LTime,CTime:dword;
    blocks:array[1..7,1..4,1..4,1..4]of integer;
    PlayField:array[1..10,-3..15]of integer;
    RankList:array[1..10]of RkRec;
    tm:array[1..20]of dword;
    IsLine:boolean;
    NextBlock,CurBlock:record k,r:integer;end;
//and 1000+ more lines

八上的时候

学习了短码的C。pascal改成了C风格。同时学会了FP的4格Tab,使用另一个控制台调试,Ctrl+C,V复制粘贴,括号自动补全等等高端技巧。主要写了一些小程序,改进了connect5(现在蒟蒻的我已经打不过它了)。另外Lazarus入门。那时的pascal风格保持至今。开始转C,在CodeVS上的提交pascal和C平分秋色。

procedure a();
begin
    for i:=1 to n do begin
        if a=1 then begin
            writeln(a)
        end else begin
            b[i][a]+=1
        end
    end //这才叫P++
end;
main(i,n){
	scanf("%d",&n);
	n&&main(0,n&~-n)*
	printf("%.f%c",log(n&-n)/log(2),i?13:32);
}
//我的C语言入门程序。。。当时我很菜不会log2
//当时写的蛇形矩阵。我的极早期的C++程序。强烈的C与短码痕迹。
#include<cstdio>
int a[110][110],i,j,t,ti,tj,ui,uj,n;
int main(){
	scanf("%d",&n);
	i=j=n+1>>1;ti=tj=i-1;ui=uj=i+1;
	for(a[i][j++]=t=1;t<n*n;){
		for(;i>=ti;a[i--][j]=++t);
		ti=i;
		for(i++,j--;j>=tj;a[i][j--]=++t);
		tj=j;
		for(j++,i++;i<=ui;a[i++][j]=++t);
		ui=i;
		for(i--,j++;j<=uj;a[i][j++]=++t);
		uj=j;
	}
	for(i=1;i<=n;i++){
		for(j=1;j<n;j++)printf("%d ",a[i][j]);
		printf("%d\n",a[i][n]);
	}
	for(t=0,i=1;i<=n;t+=a[i][i++]);
	for(i=1;i<=n;t+=a[n+1-i][i++]);
	printf("%.f\n",2./3*n*n*n+1./2*n*n+4./3*n-5./3);
    //这一行很神。
	return 0;
}
procedure TForm1.drawboard;
var i,bx,by:integer;s:string;
begin
  with board.Canvas do begin
       Brush.Color:=$c0c0c0;
       FillRect(0,0,470,470);
       Brush.Color:=clBlack;
       rectangle(28,18,pix*(bdsize-1)+33,pix*(bdsize-1)+23);
       Brush.Color:=$c0c0c0;
       rectangle(29,19,pix*(bdsize-1)+32,pix*(bdsize-1)+22);
       Font.Color:=clBlack;
       for i:=1 to bdsize do begin
            Brush.Color:=$c0c0c0;
            str(16-i:2,s);
            textout(0,pix*(i-1)+13,s);
            moveto(30,pix*(i-1)+20);
            Brush.Color:=clBlack;
            lineto(pix*(bdsize-1)+30,pix*(i-1)+20);
       end;
//lazarus代码。觉得和现在的Qt真的太像了。

八下的时候

从(pascal+C)尝试转C++的过渡态选手,提交总是使用C++尽管遇到了各种问题。当时的C++就是用pascal直译的标准的C(连STL都不会)(UPD:现在发现记忆错误,那时的C++风格一直保持到停课前,并且已经会STL了)。pascal近乎完美,看起来和那些source一模一样,学会使用windows单元。当时是CodeVS的忠实用户,现在这个网站都打不开了,所以C++代码记录一去不复返。(10.08UPD:现在能打开了,刷新了很多认知。)

//当时写LCA都是用的ST表。现在发现好像有一些小小的偏差,并且不用这么烦。
void rinit(){
	int i,j;
	memset(st,1,sizeof st);
	for(i=1;i<=cnt;i++)st[0][i]=dep[i],stn[0][i]=i;
	for(i=1;(1<<i)<=cnt;i++)
		for(j=1;j+(1<<i)<=cnt;j++)
			if(st[i-1][j]<st[i-1][j+(1<<i-1)])
				st[i][j]=st[i-1][j],stn[i][j]=stn[i-1][j];else
				st[i][j]=st[i-1][j+(1<<i-1)],stn[i][j]=stn[i-1][j+(1<<i-1)];
}
//格式就是那样,再也没有改过。只是内容一直在升级。
procedure DrawMandel(wnd:HWnd);
var c,i,j,color,k,maxx,maxy:longint;
    a,b,x,y,tx,m:extended;
    handle:hbitmap;dc,bdc:hdc;
    rec:rect;ps:paintstruct;
begin
   dc:=BeginPaint(wnd,@ps);
   GetClientRect(wnd,@rec);
   maxx:=rec.right;maxy:=rec.bottom;
   bdc:=0;handle:=0;
   while bdc=0 do bdc:=CreateCompatibleDC(dc);
   while handle=0 do handle:=CreateCompatibleBitmap(dc,maxx,maxy);
   SelectObject(bdc,handle);

九上的时候

基本掌握了C+STL这门语言。还是pascal直译水平。任何涉及GUI的程序都仍然是pascal写的,在OI方面成为了C+STL选手。

(下面的大概是缺省源的地位,尽管当时不会用缺省源。)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<utility>
using namespace std;
int n,m,i,j,k,vis[100001]; //所有变量都是全局的
int main(){
	scanf("%d%d",&n,&m);
    return 0;
}
//2017.11.1写的动态逆序对,人生第一道紫题。
//当时不怎么压行,但是逗号还是用的,总之代码比现在更加接近短码。
int main(){
    memset(cz,0,sizeof cz);
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
        scanf("%d",a+i),b[i]=a[i];
    sort(b+1,b+1+n);
    memset(c,127,sizeof c);
    for(i=n;i>=1;i--){
        c[i]=lower_bound(b+1,b+1+n,a[i])-b;
        ans+=qry(cz,c[i]-1);
        ins(cz,c[i],1);
    }
    printf("%d",ans);
    initbl();
    for(i=1;i<=m;i++){
        int x;
        scanf("%d",&x);
        ans-=q(x);
        printf(" %d",ans);
    }
    puts("");
    return 0;
}

九下的时候

变化很大。

第一步:使用bits库,大幅增加编译时间; -Wall (2018.3)

第二步:基本会用C++11 (2018.4)

第三步:使用局部变量 (2018.4)

第四步:精通C++11(我现在不会写C++了)(2018.5-6)

第五步:封装。连Dinic也是 (2018.5-6)

第六步:for循环中也使用局部变量 (2018.7)

越来越像真正的C++选手

    while(q--){
        int op,x,y,z;
        scanf("%d%d%d",&op,&x,&y);
        if(op==1){
            printf("%d\n",dfn.qry({ {rtd[pos[x]-1],-1},{rtd[pos[x]+siz[x]-1],1}},y));
        }else{
            scanf("%d",&z);int l=lca(x,y);
            printf("%d\n",tr.qry({ {rtt[x],1},{rtt[y],1},{rtt[l],-1},{rtt[fa[l][0]],-1}},z));
        }
    }
// 六月份的,现在也差不多这么写
void FFT(cmp *a){
    for(int i=0;i<N;i++)
        if(i<rev[i])swap(a[i],a[rev[i]]);
    for(int i=0;i<lg;i++)
        for(int j=0;j<N;j++)if(!(j&1<<i)){
            int x=j^1<<i;
            a[x]*=w[(j&(1<<i)-1) << lg-1-i];
            //熟悉的配方,熟悉的味道。
            cmp tmp=a[j]+a[x];
            a[x]=a[j]-a[x];a[j]=tmp;
        }
}

高一上

和当前风格没什么区别,除了使用#define int64 long long等各种define;还没有使用C++17结构化绑定的习惯;没有(a*=b)%=mod的习惯之外。

//线段树2
    int64 qry(int l,int r,int64 k=1,int64 b=0,int p=1,int tl=1,int tr=n){
        if(l<=tl && tr<=r)return (a[p].sum*k%mod+b*(tr-tl+1))%mod;
        int64 ret=0,newk=k*a[p].mulv%mod,newb=(b+k*a[p].addv)%mod;
        int mid=tl+tr>>1; //不加括号
        if(l<=mid)ret+=qry(l,r,newk,newb,L(p),tl,mid);
        if(r>mid)ret=(ret+qry(l,r,newk,newb,R(p),mid+1,tr))%mod;
        return ret;
    } //qry不用pushdown
    void ins(int l,int r,int tp,int64 vl,int p=1,int tl=1,int tr=n){
        if(l<=tl && tr<=r){
            if(tp==1)a[p].mulv=(a[p].mulv*vl)%mod,a[p].addv=(a[p].addv*vl)%mod;
            else a[p].addv=(a[p].addv+vl)%mod;
        }else{
            pushdown(p);
            int mid=tl+tr>>1;
            if(l<=mid)ins(l,r,tp,vl,L(p),tl,mid);else maintain(L(p),tl,mid);
            if(r>mid)ins(l,r,tp,vl,R(p),mid+1,tr);else maintain(R(p),mid+1,tr);
            //L(x),R(x)两个宏
        }
        maintain(p,tl,tr);
    }

当前的风格

现在好像保持基本稳定了。除了Qt带来了PascalCase变成camelCase并且彻底弃用了pascal。偶尔使用C++17。

Pascal : 它死了

使用extended。函数名PascalCase。喜欢graph甚于windows。C++风格缩进(begin、then不换行,不打空格)。多使用函数和record。C风格运算符。有着type int=longint的习惯。一般不打program。

C++

camelCase。大括号不换行。除了##(宏中)、&&、||之外不打空格。越短越好。

尽量少使用#define,使用gedit的片段替代。

变量为了区分时会大写。部分全局变量会下划线开头。

万能头(偶尔改成bits/extc++.h)。

使用C风格输入输出、C风格字符数组(毕竟我是先学的C)。真的超强大。

在for比while短的绝大多数地方使用for,即使三个语句毫无干系。仅有的两个使用do-while之处是随机生成简单图和next_permutation()

邻接表使用vector。数组只要长度不能直觉得到就开vector。大量使用STL。sort传lambda。几乎所有图、树的题都少不了range-based for。手写的结构体、max、min、insert等等基本使用initializer_list。哈希表使用unordered_map(必要时重载hash)。if中声明变量。总之C++11是不可分割的一部分,能够大量简化代码。

一般会using ll=long longusing pa=pair<int,int>。pair是我仅有的较少使用的STL(因为太长了)。如果一定要用,我一般会使用C++17,并且使用结构化绑定,推导指引等等高端操作来代替pair<int,int>这个冗长的词。

不使用rand(),一般使用std::default_random_engine

封装一切东西。有时连最短路都封装。喜欢使用一个struct node胜过一堆数组(同时定义L(x),R(x),LCT还会有F(x))。

如果硬要我写C++98,我会这么写:

#pragma GCC diagnostic error "-std=c++11"

编译时一定开-Wall,-ferror-limit=3,-Wno-unused-result(linux下)。消除除了添加括号之外的所有warning(我从来不加不必要的括号)。(int)v.size()。 下面是我的插头DP中的一段。可以看到不加括号多么简短。(位运算优先级和pascal是完全反的,+ \(>\) » \(>\) | )

inline int Set(int k,int p,int v){return (k>>p+2<<2|v)<<p|k&(1<<p)-1;}
inline int Set(int k,int p,int v1,int v2){return Set(Set(k,p,v1),p+2,v2);}

能开int的尽量不开long long。几乎从不define int ll。很少inline,从不register(register在C++17中被删除了,所以本地无法编译。并且我测过,开O2时毫无卵用)

短的语句用逗号,不能用逗号时会把if换成&&和||(多年短码习惯),return换成exit。这样对于输出调试来讲特别方便,可以规避大括号,但是对于gdb来说是噩梦。比如我的输出调试中经常有这样的语句(_D一直是调试专用的变量):

正常语句,a==b&&c-d&&(_D=1,printf("%d%d",x,y),assert(~z),exit(0));

还有一个奇怪的习惯是if和for永远在同一行。

for(int i:v[p])if(!vis[i]){
	//do sth
}else{
	//do sth else
}

线段树、Dinic、MCMF、Kosaraju、Minimax、二分、RMQ、exgcd、计算几何等等等等全部都是刘汝佳的板子。(我不用Tarjan求SCC)FFT、FWT是两重循环。

一个C++17的例子,体现了我的一些代码风格:

        for(int i=1,j;i<=q;i=j){
            vector<st> v{ {a[i].l,1,i},{a[i].r+1,-1,i}};
            for(j=i+1;j<=q && a[j].m==a[j-1].m;j++)
                v.insert(v.end(),{ {a[j].l,1,j},{a[j].r+1,-1,j}});
            sort(v.begin(),v.end(),[&aa=a](st a,st b){
                if(a.l!=b.l || a.r!=b.r)return a.l<b.l||a.l==b.l&&a.r<b.r;
                return b.r==-1?aa[a.m].l>aa[b.m].l:aa[a.m].r>aa[b.m].r;
            });
            int ls=0;vector<int> n;
            for(auto&[p,tp,id]:v){
                if(p>ls && n.size()){
                    u[a[i].m].push_back({ls,tr.qry(ls,p-1),~tp?n[0]:id});
                    for(int i:n)t[i]=ls;
                    tr.ins(ls,p-1,0,tr.rt);
                }
                if(~tp)n.push_back(id);else n.erase(find(n.begin(),n.end(),id));
                ls=p;
            }
        }

高一下的更新

了解了basic_string,并且用来替代vector的几乎所有应用。

//从前的
vector<int> a;
a.push_back(x);
a.insert(a.end(),{1,2,3});  //我加入多个元素的写法
a.clear();

//现在的
using bs=basic_string<int>;
...
bs a;
a+=x;
a+={1,2,3};
a={};

Python

蒟蒻刚入门,没有什么风格,除了缩进用tab之外。正在努力习惯列表生成式。喜欢用lambda代替def。

为了说明python的优越性,我们可以尝试一下\(\sum_{x=1}^{\infty}\frac{\sin{x}}{x}\)(等于\(\frac{\pi-1}{2}\))。

double s=0;
for(i=1;i<100000;i++)
	s+=sin(i)/i;

然而python就相当接近人类的思维习惯,并且很简短:

sum(sin(x)/x for x in range(1,100000))

喜欢from numpy import *等等各种from xxx import *,类似于C++的using namespace std;

一个标准的main是这样的:

#!/usr/bin/env python3
import sys

def main():
	return 0

if __name__ == "__main__":
	sys.exit(main())

一般的iffor后如果语句很短就会写在同一行。

和C++一样,多条短语句用分号隔开放在同一行

高二的更新

JavaScript

稍微了解了一下JavaScript。还是比较习惯于沿用C++的写法。比如说使用==!=,尽管我知道这么做很不安全,但是===看着太丑了。又比如使用++--运算符,虽然我到现在还不懂为什么用JS的人弃之如敝履。

定义函数使用和bash一样的function F(){}而不是F=function(){}。如果声明的数组是整型,用new Int32Array而不是new Array。字符串转整数使用+"123"这样的写法。习惯于"A = "+A而不是`A = ${A}`

应用的ES6特性主要是class=>let。高精度偶尔用用。声明变量全部用let。能用一行描述清楚的函数以及回调函数使用=>声明。

一个代码片段,是我的2048游戏中的最核心的函数,可以看出和C++几乎是一模一样的:

function goUp(){
	let sc=0;
	for(let i=0;i<4;i++){
		let _=0;
		for(let j=0;j<4;j++)if(a[j][i]){
			if(_!=j)sc=1;
			a[_][i]=a[j][i];id[_++][i]=id[j][i];
		}
		for(let j=_;j<4;j++)a[j][i]=0,id[j][i]=-1;
		for(let j=0;j<3;j++)if(a[j][i]==a[j+1][i] && a[j][i]){
			flg[j][i]=1;a[j][i]++;sc=1;tot+=1<<a[j][i];id[j][i]=id[j+1][i];
			for(let k=j+1;k<3;k++)a[k][i]=a[k+1][i],id[k][i]=id[k+1][i];
			a[3][i]=0,id[3][i]=-1;
		}
	}
	return sc;
}

Rust

Rust和Python类似,实现一个目标基本只有一种写法,玩不出什么花来。

像C++的using namespace std;和Python的from X import *一样,喜欢导入全部内容。并且多个有公共前缀的导入用大括号括在一起,比如:

use std::{io::*,collections::*};

由于Result类型的返回值不使用就会有warning,然而我不想使用这个返回值,就直接用.ok()了,反正丢弃Option不会有事。

fn只声明全局函数;用||只声明局部的函数。其实Rust的匿名函数也是可以递归的,参见我Min25筛模板的实现:

struct Dfs<'a>{
	a:&'a dyn Fn(i64,usize,&Dfs)->i64
}
let dfs=Dfs{
	a:&|n,d,dfs_stt|{
		// Do Something
		rs=(rs+x%MOD*((x-1)%MOD)%MOD*((dfs_stt.a)(n/x,k,dfs_stt)+1))%MOD;
		// Do Something
	}
};
println!("{}",(((dfs.a)(n,1,&dfs)+1)%MOD+MOD)%MOD);

快读一般用extern"C"{fn getchar()->i32;},因为我实在是找不到什么原生的快速读入方式。快速输出用BufWriter::new(stdout())。声明多个变量时习惯使用let (a,b)=(X,Y);这样的写法。其他码风其实和C++差不太多。

补充:变量命名规则

有闲情逸致研究一番是因为知乎上刷到了这篇文章

上篇忘说了,现在我写了个gedit的代码片段来创建新博客,真的很好用,大概是这样的:

---
layout:		post
title:		$1
date:		$<import datetime; return datetime.date.today().isoformat()>
author:		wyj
catalog:	true
tags:
    - $2
---
$0

为了进行统计,我特地修改了一下初中时写的分词程序。当时写的版本只能在Windows上用,现在我让它可以处理Linux中的英文数据(中文编码问题不会解决)。

首先筛选出所有我写过的OI程序:

find -type f -wholename "*weiyijun/*.cpp" -exec cat {} \; >> ~/1.txt 

然后运行分词程序,剔除一些无意义结果以及关键字、常数等等。我用过的几乎所有变量名都在下面列举出来了。可以看到三字母/单字母占到绝大多数。

循环变量

这是最常见的变量声明了。按顺序依次为$i,j,k,l\dots$,如果k被占用了就改成$i,j,t,u\dots$

统计变量

$t, \underline{\space},$$

双字母:$sm$

三字母:$tot,cnt,top,ans$

成对的变量

$l/r(只用于区间),p/q(多用于数学),s/t(多用于源/汇),u/v(多用于边的起点/终点),x/y(泛指),n/m(多用于二维数组)$

三字母:$stt/end$

数组名

一般数组(正常人都是这样):$a,b,c\dots v(邻接表),w(权),e(边集数组),x,y(坐标)\dots$

dp数组和多项式:$f,g,h,\dots$

特殊意义的数组(两字母,极少):$fa(双亲),vl(权值)$

特殊意义的数组(三字母):$vis,dis,que,sta,ind(入度),dep,dfn,pos,siz,len,inv,fac(阶乘),cur(当前弧),ban\dots$

函数名

三字母:$ins,qry(泛指数据结构修改/查询,后可加1/2/3区分),sol,bfs,dfs(后加1/2/3),chk,rmq,tri(三分法)\dots$

四字母:$getf,Init,calc$

结构体

二字母:$st(泛指各种结构体,除非和st表重名),ev(事件),po(点)$

数据结构/算法名:$edge(边),segTree(线段树),Splay,LCT,Dinic,MCMF\dots$