#概要
C++でコーディングする上で知っているとコーディングやデバッグが捗るテクニック、そして使う上で注意したほうがいいことをまとめてみました。
#環境
C++11
検証環境:visual studio code
#知っていると便利なテクニック
#ifdef #ifndef
長いプログラムになってくると、コメントアウトがだんだん汚くなってきます。その上、何をしたときのコメントアウトだかわからなくなってきます。そして、コメントアウトを外し忘れてバグが発生するなど様々な問題が発生してきます。#ifdefを使うとマクロの定義未定義だけでコンパイラにプログラムとして認識させるかさせないかを瞬時に切り替えることができます。その上、名前がついているので何のためのコードなのか一目で分かりデバッグがはかどります。下にあるコードだと#define DEBUG
のコメントアウトをするしないで瞬時に切り替えることができます。
#include <stdio.h>
#define DEBUG
int main()
{
#ifdef DEBUG
printf("debug mode!");
#endif // DEBUG
#ifndef DEBUG
printf("run mode!");
#endif // !DEBUG
}
#define
「define」はconstと違い、プリプロセッサが名前の置き換えを行うものです。なので、関数やインスタンスの置き換えもできます。
マクロ関数
defineマクロには関数と同じく引数を与えることができます。処理の中身は様々な演算を行うことができます。ちなみにdefineで定義した関数は型を指定する必要がありません。詳しい使い方は、【C言語入門】define(マクロ)の使い方(関数や条件付きマクロも解説)や[c言語]関数マクロ(#define)で判定文を作るが参考になると思います。
##エイリアス(using)
typdefと同じく型の名前を置き換えることができます。テンプレート化することもできるので便利です。詳しくは以下が参考になると思います。
https://qiita.com/Linda_pp/items/44a67c64c14cba00eef1
##関数の返り値に複数の変数を返す
通常、関数が返すことができる変数は一つです。ただし、それはintやfloatなどの組み込み型の話です。構造体で独自の型を作ってしまえば二つ以上の変数を返すことが可能です。書き方はこちらを参考にしてください。
##関数の返り値に配列を返す
関数の返り値型にstd::array
を用いれば、配列を返すことができるようになります。
#include <array>
using array3 = std::array<int, 3>;
array3 GetDate()
{
return { 2020, 5, 16 };
}
int main()
{
auto date = GetDate();
printf("%d年%d月%d日", date[0], date[1], date[2]);
}
##簡単にコンストラクタを継承する
子クラスでusing クラス名::コンストラクタ名
とするだけでコンストラクタが継承できます。非常に簡単で可読性が上がります。
#include <stdio.h>
class Mother {
int m_x;
public:
Mother(int x) : m_x(x)
{
}
void Puts()
{
printf("%d",m_x);
}
};
class Child : public Mother {
public:
using Mother::Mother;//Mother classの継承
};
int main()
{
Child inst(2);
inst.Puts();
}
##型推論(auto)
C++の面倒な場面の一つとして変数の宣言時に型を作らないといけないところではないでしょうか。ただ受け取るだけの変数を作るときには、既に受け取る型がわかっているので短縮のためにauto型を使うと便利です。安全のため、型が明確なときもしくは型が関係ないときだけ使いましょう。
##変数の分割術
組み込みプログラムの場合、外部機器と通信するときは8bitずつ送ることが多いため、float型を送信する場合は共用体を使ってアドレスを共有し8bit×4回に分けて送信します。しかし、共用体は関数の外に書かなければならず、スコープの範囲を限定できません。じつは、関数内だけで8bitずつに分ける方法が存在します。以下は8bitポインタでキャストするとデータ領域を8bitずつに分けることができる(配列に変わる)ことを利用して分割する方法です。共用体と比べて、同じ場所に全てのコードが収まるのでスッキリと見やすくなり、デバッグもはかどります。個人的に気に入っている分割方法です。
#include <stdio.h>
float temp_data = 3;
float result = 0;
unsigned char FIFO_Data[4];
int main()
{
FIFO_Data[0] = ((unsigned char *)&temp_data)[0];//8bit分割
FIFO_Data[1] = ((unsigned char *)&temp_data)[1];
FIFO_Data[2] = ((unsigned char *)&temp_data)[2];
FIFO_Data[3] = ((unsigned char *)&temp_data)[3];
((unsigned char *)&result)[0] = FIFO_Data[0];//値の復元
((unsigned char *)&result)[1] = FIFO_Data[1];
((unsigned char *)&result)[2] = FIFO_Data[2];
((unsigned char *)&result)[3] = FIFO_Data[3];
printf("result:%f", result);
return 0;
}
##.補完(エディタ依存)
高級エディタには(インスタンス).
まで打つとpublicなメンバ一覧を表示してくれる機能があります。これを使うといちいちコピペしなくていいのでコーディングが非常に楽になります。
visual studio community、visual studio code、Eclipseで対応を確認しています。
##瞬間コメントアウト(エディタ依存なし)
長い範囲をコメントアウトする際に、/**/
で囲うと思います。しかし、コメントアウトを外す場合に両方消さないといけないのでスピーディーにできません。そのため以下のように書くと、先頭に「/」を追加する、消すだけで瞬時に全体のコメントアウトを変えることができます。
説明すると、一番上の/を消した状態では/**/
の認識になり、/をつけた状態では、一行ごとのコメントアウトとして認識されるからです。
##瞬間コメントアウト(エディタ依存)
コメントアウトしたい範囲を選択した状態で「Control」+「/」を押すと、選択した範囲の全ての行をコメントアウトします。/**/
と違い、一行一行コメントアウトしてくれるので意図しないコメントアウトの結合がなくなり、バグが出にくくなります。ちなみに他の言語でも使えます。
visual studio code、Atom、Eclipseで対応を確認しています。他にもできるエディタがあったら教えて下さい。
#コーディングの際に気をつけたほうがいいこと
##extern
通常、外部ファイルの変数を使いたいときにexternで呼び出だすと思います。しかし、万が一ファイル間で変数名がかぶってしまったときに意図しない外部結合が発生ししまう可能性があります。そのためexternはあまり多用はせず、変数はclassやstructでカプセル化し、ポインタで呼び出すほうが安全です。
##Goto文
Goto文はコードを追うのが大変になるので、デバッグの速度を早めるという観点では使わないほうがいいでしょう。
##グローバル変数
特に規模が大きくなるとグローバルに書いてある変数は何に使うものなのかわからなくなってきます。そのため、デバッグをしやすくするためにも必要最低限を心がけましょう。
##意図しない変数かぶり
別のファイルで使われていると警告を発してくれる優秀なコンパイラもありますが、基本的にグローバル変数はやめたほうがいいです。意図せず他のファイルで使った変数とかぶってしまうことがあるので、なるべくクラスや構造体でカプセル化することを勧めます。
##変数の未初期化バグ
変数は初期化をしないと勝手に値が入ってしまいます。初期化していなかったために計算結果が狂うバグに見舞われることがあるので、すべての変数を必ず初期化しましょう。
##異なる型の混在
double型とint型の演算を行った場合は暗黙的にdoubleにキャストされます。そのため、意図しない動作を防ぎたい場合は明示的にキャストを行いましょう。以下のコードの場合はキャストを行わないと整数型で計算されるので、結果は0となってしまいます。
※C++の場合はCのようなキャストよりもstatic_cast<型>を使うことを推奨されていrます。
#include <stdio.h>
int main(void)
{
int num = 3;
double result=0;
// キャストを行う場合
result = 1 / (double)num;
printf("double型変数resultの値は: %lf\n", result);
// キャストを行わない場合
result = 1 / num;
printf("double型変数resultの値は: %lf\n", result);
return 0;
}
##未定義の参照
特にポインタなどでよくあるミスです。ポインタ変数の初期化が終わっていない状態で参照すると破壊的な結果を招くことになります。初期化の順序には気をつけましょう。より安全なのはnullチェックを入れておくことです。
#最後に
私は高校時代はプログラミングが嫌いでしたが、エディタをいいものに変えたり、バグがでにくいプログラミングのテクニックを知ってからはプログラミングが楽しくなりました。
こうしたちょっとしたテクニックを知っているだけでもプログラミングが楽しいと思えるきっかけになれたら嬉しいです。
#参考
https://it-ojisan.tokyo/c-define-macro/
https://marycore.jp/prog/c-lang/return-array/
https://qiita.com/Linda_pp/items/44a67c64c14cba00eef1
https://qiita.com/k2ymg/items/d4405d2f000688263aab
https://www.sejuku.net/blog/25927
ハーバート・シルト (著), 神林 靖 (監修), トップスタジオ (翻訳) "独習C++ 第4版"