競プロにおける俺的C++コーディングルール・気をつけることリスト

これは何

僕がC++を用いて競プロをする上で守っているルールや大事だと思っていることについて軽くまとめてみた記事です。

C++で競プロをする

C++で競プロをして1年以上経ちました。
実は最初の何回かはPythonで提出していたのですが、C++に乗り換えるなら早めの方がいいかなと思ってかなり早いタイミングでC++に移行しました。
もともと授業でCを書いていたので導入は比較的すんなりとすることができました。

この記事では普段競プロをC++で実装していく上で僕が大事だなと思っていることをまとめていこうと思います。僕のレートはAtCoder青の真ん中程度なのであまり高度な話はできませんが、バグを埋め込みにくいスムーズな実装をするために心がけていることを語っていこうと思います。決してこれが正しいというものではなく反対意見はいくらでも出てくる話だと思うのであくまで一意見として読んでいただけると嬉しいです。

俺的コーディングルール一覧

bits/stdc++.hを使う

C++の標準ライブラリを一挙にincludeしているやつですね。全て明示的にincludeしないと嫌だという話もよく聞きますが、僕はそれよりもコードのシンプルさを求めているのでこれは使います。大手のコンテストサイトではほぼ全て使えると思うので安心です。ローカルでこれをつけて実行ができない場合は以下のリンクからstdc++.hを取ってきて自分のPCの /usr/local/include/bits/ 以下に置くことで動くと思います(Mac OSの場合)。
Competitive-programming/stdc++.h at master · ysugiyama12/Competitive-programming · GitHub

using namespace std;

有名なn575ですね。開発するならよろしくないですが競プロするならこれつけといていいでしょう。ただし登録語は把握しておきましょう。

typedef long long ll

オーバーフローでいつまでも消耗するのは辞めましょう。どこが間違っていますかって初心者が出してくるコードのある程度の割合はオーバーフローしてます。intで足りるところをlong longにしたところでメモリが溢れることはほとんどないので競プロでは全てlong longで行きましょう。
さすがに #define int long longはちょっと極端なのでは?と僕は思ってます笑

typedef pair lp

あんまり単純な省略はしないんですが、かなり使う上に長いのでこれは導入しています。

#define rep(i,m,n) for(ll i = (m); i < (n); i++)

よく見るfor文の省略ですね。これをやるのは書くのがめんどくさいというのもありますが、変数ミスをしないというのが最大の目的です。iとかjとか3回も書いてたら混同する確率がだいぶ上がってしまうのでミスを減らすという意味でもマクロは大切です。
僕は逆順のrrepも登録しています。イコールがつく・つかないとかで全部マクロ作るのはやりすぎでしょう。
あと、#defineするときは変数をカッコでくくっておくことをお勧めします。#defineはそのまま該当部分を置換するので意図していない区切られ方になることがあります。

#define print(x) cout << (x) << endl;

cout毎回書くのはめんどくさいのでこれは大事です。llと並んでもっともよく使うマクロですね。

#define printa(x,n) for(ll i = 0; i < n; i++){ cout << (x[i]) << " \n"[i==n-1];};

いまだにprintデバッグで凌いできている人間なので配列をパッと見たい時に使っています。そろそろデバッガを導入した方が良いなーと思いつつ愛用させてもらってます。

cin.tie(0); ios::sync_with_stdio(false);

詳しくは理解していません。どうやら入出力が速くなるらしいです。touristが書いていたので僕もおまじないとして書いています。

if(not ok){ print("No");}

否定を表す時に"!"をよく使いますが、見落としやすいので僕は"not"を使うようにしています。シンタックスハイライトもされるので可読性がグッとあがります。

否定のフラグを作らない

「~してない = true」みたいにフラグとして変数を作ると頭がこんがらがって死ぬので必ず肯定の意味を持ったフラグを立てるようにしています。

gotoを使わない

完璧に使いこなせれば良いのですが僕は怖いので使ったことがありません。

rep(i,0,N) cout << ans[i] << " \n"[i==N-1];

最後の部分です。普段はスペースでi=N-1の時だけ改行してくれるので末尾に空白を入れたくない配列の出力とかに使えます。なんかゴルフみがありますね。

変数をmainで宣言するかグローバルに書くか

これは決めていません。あまり外には書きたくないので基本mainに書いて他の関数でも使いたい時とか関数内だとメモリが溢れる時とかにその都度外出ししてます。

S.size() - 1

size_t型に対して引き算をすることに敏感になってください。unsigned型なのでやばい挙動をします。なんかループを抜けてくれないとかいう時の原因になることがあります。long longなどにキャストをする癖をつけましょう。

ライブラリは使うやつだけ貼る

常にすごい量貼ってる人たまにいますよね。やりづらくないのでしょうか。

スペースとかしっかり入れる

この辺は好みが分かれると思います。速く書く目的で詰めて書く人もいますが、僕はとにかく可読性重視なのでスペースとかかなりちゃんと入れてます。人にバグ見つけてもらう時とかもあるので綺麗に越したことはないでしょう。

コードの先頭に署名

touristがやっていたので最近入れてみました。人によっては可愛いAA書いてたりしますよね。

わかりやすい変数名とは

変数名はどれくらいわかりやすい方がいいんでしょうかね。僕は割と2文字程度で作ってしまいますが、数字変えるだけとかだと書き間違いの元になるんですよね。かといって長い変数名もなんかだるいし...

変数名は大文字か小文字か

これも好みの問題ですが、僕は与えられた変数は大文字にしておこうかななどとざっくり決めています。これらの変数は基本的に書き換えないようにします。

bit演算で優先順序を間違える

XORをカッコでくくらなかったからどうのってやつですね。僕はVSCodeのlinterが緑波線を出してくれるのでやったことはないです。

(1 << 60)

これもよくあるオーバーフローです。ビットシフトの時は1LLにしておきましょう。これもVSCodeだと緑波線出してくれます。

二次元配列の定義

vectorでやる人と生配列でやる人がいますね。僕は生配列でやっています。これは書きやすい方でいいかと。

dp.at(i)

範囲外参照が防げるというやつですね。これから入った人は慣れていてやりやすいかもしれないですが僕は最初からdp[i]とやってしまっていたのでつかいませんでした。

setとかmultisetのerase

eraseしたイテレータをそのまま使うと壊れるらしいので別の変数にコピーして再代入してやりましょう。

過去の提出コード

過去作ったコードはとりあえず作業ディレクトリに取っておくとキーワード検索とかで昔にやった類題とかが爆速で見つかるのでオススメです。

さいごに

また思いつき次第追加していきます。オレオレのルールでも解くスピードが上がってミスが減ればそれが正しいので自分なりのコーディングルールを少しずつ固めていくことをオススメします!