読者です 読者をやめる 読者になる 読者になる

プログラミング雑記

 コーディングのテクニック的なものをメモする必要がある。箇条書きでいいか。ちなみに、「雑記」などのカテゴリー分類は基本的にサイト内検索に引っ掛かるようにして済ませるつもりなので、プログラミングの技術については(日記の内容が混ざらないように)programmingなどと併記しておく。まだコンテンツとしては少ないからあとで何とでもなるはずだし、そこまで気にしなくてもいいか。雑記で検索していちいちタグをつける作業をした。あとは検索すれば探せる。

・intで桁があふれる可能性を常に警戒する
 intは4Byteで値の範囲は-2147486948 ~ 2147483647だから、10桁超える計算では8Byteの整数型にしてあげたほうが良い、long longは値の範囲気にする必要なさそう。あるのかな。
 typedef long long を小文字のllにするか大文字のLLにするかが悩ましいけど、視認性としてはLLのほうが圧倒的に良い。ただし、C/C++にはリテラルを型明記するような接尾修飾があって[参考:データ型の修飾]、たとえば1や2という数字をlong long として表記したい場合は1LLや2LLといった書き方をする。これと混同しないように(しないだろうけど)する必要はありそう。
 atcoder上でいろいろと提出されたコードを見ていたらtypedef long long lint;というのを書いてるひとを見つけて、こっちのほうが良さそうだと少し思った。でも型変数っぽさがないのでエイリアスとしては不適当かもしれない。競技で自分だけが読むぶんには問題ないけど、他人が見るような作業には向かない、ってくらいかなぁ。

・for文
 引数の1つめが初期状態の指定、2つめがループの継続条件、3つめがループ内の処理、ということは大前提としてそりゃ知っているわけで、それぞれ省略可能なことも一応知ってはいたけど、省略を便利に使いこなすということができていなかった。1つめを省略してループ内での変数をループの外で定義すればbreakさせたりしてカウントを取り出すことが可能だし、2つめを省略すれば任意処理を挟んだ条件文でbreakの判定できるし(と言っても2つめの省略はあんまり実用性なさそう)、3つめを省略すればより自由にループ変数を操作することができる(等差数列にならないような推移とか)。きちんと説明できていないけど処理の自由度は上がるよね、という話。あとfor(略);として反復処理は3項目だけ行うような処理もありです。
 つまるところ、実現したいループ処理があって、でも定型文的なfor (int i = 0; i < n; i++)の処理に当てはめて書き下そうとして詰まっているようでは大きな損になってしまう。初期条件も継続条件も反復処理も自由に変えられるのだから、実現したい処理に合わせて書ける(可能性は高まる)よねということです。さらに、コンマ演算子複数の式が書けるので拡張性はまだ上がる。
 for (f1,f2;f3,f4;f5,f6);
なら、f1,f2が初期条件として逐次評価、f3はtrue/falseにかかわらず評価だけされる(から記述が無意味?)、でf4が継続条件として評価、f5,f6が反復処理として逐次評価。
 一般論になってしまうけど、なるべくなら自分が思いついた処理をそのままコードに落したいのにもかかわらずコーディングの幅の狭さによって制限されてしまう、というのが一番よろしくないのだと思う。解法が思いつく限りはそれをそのままの形で実現できるようなコードを探るべきであって、そのためには自分が書いたことのないような表現を他人のコードを読んで見つけるべきだよねということです。

・for文とchar配列
 for文の継続条件にstr[i]とだけ突っ込んでもOKらしい。str[i]=='\0'と同一視されて、終端記号を引いたらfalse返してループを終わらせてくれる、つまり
 string s; cin >> s;
 for (int i = 0; s[i]; s++) hogehoge;
というのがOKらしいです。i < s.length()と書かなくてもいいということね。ちなみにstringのlengthはsizeのエイリアスらしいです。

・main関数を例外処理で挟む
 デバッガがしっかりしていないときなど、main関数を別名にして例外処理で挟めるようにしておくことはメリットが大きそう。デバッグ処理の効率化が望めるかもしれない、でももともとIDEで書けばいいかもしれない。Windows上ならC++Visual Studioで書くのがいいと思い始めている。Linuxのときは知らない。私はgdbをまともに使ったことがない。そのうち必要になりそうだから触れておかないと。

・一行に複数の文を置くことについて広い心をもつ
 int n;
 cin >> n;
じゃなくて、
 int n; cin >> n;
でもいいじゃんという話で、役割としては2文で1処理みたいなものなのだから、一行にまとめてしまうことに何のデメリットもない。

・線形的な文字列走査の問題で末尾にダミーの文字を足す(番兵法の一種)
 とくに同時に複数文字見るような問題では威力が高い。つまりループの終了の判定をi < str.length()にしているとstr[i+1]みたいな処理が出てきた瞬間に死んでしまうので、引っかからないように必要な分だけstr += "$"をしておくなど。これは技術概念としては番兵法と呼ばれているものの応用例。番兵法は、線形探索において条件文を開放的にしないで等式で抑えられるのが安心という話(データが可変長のときに便利)。開放的って言葉の使い方が怪しい。

・for文のrepマクロは必要なのかどうか
 正直repのこと考えるの面倒くさいし要らないんじゃないかなと思う。というのは、たとえばfor文の各条件式を増やしたりいじったりするような書き方をする方針のとき、もっともシンプルな場合はrepにする一方で問題として特殊な処理をするときはforを使う、という使い分けが生じてしまうことになるから。そうなると処理方針の変更で関数名書き換えるのに手間がかかったり、どっち使うかの判断に時間が要したりして、最終的には愚直に書き下す速さに劣るんじゃないかと思う。
 単純な関数を自由自在に変化させて使う、というのが理想的なわけで(本当か?)、そのための関数としてfor文ないしwhile文はとても優秀だし、わざわざマクロを作る必要はないんじゃないかなと。個人的な感覚なので判断の早さに慣れてる人なら全然問題ないだろうけども。もっと慣れた後では変わるのかもしれない。

・substrと逐次比較
 少なくとも2文字の比較なら
 s.substr(pos,2) == "ab"
よりも
 s[pos] == 'a' && s[pos+1] == 'b'
のほうが速い。けっこう処理時間変わる。

・stringの動的確保について
 stringのプロパティのcapacityを見ればそのときに確保されている配列のサイズがわかる。もしオーバーしたら倍に増やしていく、という挙動をするらしいので、reserve(1024)のように一気に確保しておくとreallocationによる遅延を軽減できる。[参考:C++ の string と vector の reserve() の挙動 - bkブログ]

vector
 コンテナに関する分類が要る気がした。頭の中で整理できていないので。
 - STL コンテナの特徴一覧 - C++ STL | ++C++; // 未確認飛行 C
 - [C++] STLの型の使い分け - Qiita
こんな感じでいいんじゃないでしょうか。

・main関数のreturn
 C++はreturn書かなかったらreturn 0として扱ってくれるらしい。いちいち書かなくてもよいということ。どっちでもいいか。テンプレートに足しておけばいいだけの話だし。