ハンガリアン記法とは変数名などを命名するときに、先頭に変数型を示すプレフィックスを書くようにする記法です。考案者がハンガリー人だったのでハンガリアン記法とかハンガリアン・ノーテーションというようです。
C++ は、変数には変数型が厳格に決まっているので、ハンガリアン記法を使うことは、プログラムの見通しをよくするために大いに意味があります。ハンガリアン記法は、その命名法が厳格に決まっているわけではなく、プログラマ毎に記述の方法が異なるというのが現状です。ここでは、このサイトで私が使用している記法について解説します。一つの例としてご覧いただいて、気に入ったらそのまま使ってみてください。
また、システムハンガリアン記法の応用として、変数をプリントするコードの自動生成が可能なので、そのためのツールを公開します。使ってみてください。
また、ハンガリアン記法以外にも、プログラミング上好ましい書き方というものがあるので、これに関する私なりの考え方や提案などについても記述します。
ハンガリアン記法は、冒頭でも述べたように、変数名の先頭に変数型を示す文字列を書く記法です。この記法を導入した当のマイクロソフトは、現在この記法を推奨していないという記述をよく見かけますが、おそらく、統合環境での開発言語の種類が増えたので、必ずしも型付けが厳格で無い C# や VB などの言語では、ハンガリアン記法を使う意味が無いということなのでしょう。C++ の次に、私の好きな Perl 言語などでは、変数がすべていわゆるバリアント型(整数にも、浮動少数点数にも文字列にもなる)なので、ハンガリアン記法を使う意味は、プログラマが変数をどんな型のつもりで使っているかを表す以外に、あまり無いといえます。
しかし、C++ は、非常に(異常に?)変数型について厳格な言語なので、ハンガリアン記法を使う意味が大いにあります。というよりも、C++ では、これが無くては、さくさくコンパイルが通るプログラムを作成することは殆ど不可能と言っていいでしょう。
ポリモーフィズムを使ってると、ハンガリアン記法はほとんど無意味だという人もいますが、ポリモーフィズムを使っていても、その概念をくくる適切な接頭語は考え得るものであり、基本型以外の変数型に対しても接頭語をつけるということは、私の経験では明らかに有効です。今、流行らない、などという理由で、簡単に捨ててしまうのは、IDE にどっぷり浸かっているか(まぁ、私も浸かっているのですが)、実際には使っていなくて有効性が見えていないか、使い方を誤っているかのどれかで、愚かというものでしょう。
最近、このページ(=間違ったコードは間違って見えるようにする)を見つけました。こちらもあわせて、ぜひご覧ください。これによると、ハンガリアン記法のプレフィックスは、元々は変数型を示すのではなく、変数の種類を示すのだということで、なるほど納得の内容です。まさに上に書いた、概念をくくる接頭語というものであることが分かります。ただ、私は、変数型を表すだけのシステムハンガリアンも、これ単体で十分実用的な意味があると考えています。C++ では、システムハンガリアンとアプリケーションハンガリアンの両方を使うことが、極めて有効です。
私は、システムハンガリアン記法として、次のような記法を採用しています。
まずは、C++ の組み込み型から始めましょう。これらは変数の型を表す文字列を変数の接頭語として付けるようにしています。
型名 型を表す文字列 使用例 解説 -------------------------------------------------------------------------------- char c char cValue = 0; Value は何かの値を意図しています。 unsigned char uc unsigned char ucValue = 0; int i int iValue = 0; unsigned int ui unsigned int uiValue = 0; long l long lValue = 0; unsigned long ul unsigned long ulValue = 0; float f float fValue = 0; double d double dValue = 0;
上記基本型に対するポインタ型は、更に前に p をつけます。
型名 型を表す文字列 使用例 解説 -------------------------------------------------------------------------------- char* pc char* pcValue = 0; Value は何かの値を意図しています。 unsigned char* puc unsigned char* pucValue = 0; int* pi int* piValue = 0; unsigned int* pui unsigned int* puiValue = 0; long* pl long* plValue = 0; unsigned long* pul unsigned long* pulValue = 0; float* pf float* pfValue = 0; double* pd double* pdValue = 0;
これで、プログラム中に突然 puiXCoordinate などと出てきても、これは unsigned int 型の X 座標へのポインタであることがすぐに分かってしまいます。
基本型の配列には、前に a をつけます。a は 配列(array)の意味で使用しています。
型名 型を表す文字列 使用例 解説 -------------------------------------------------------------------------------- char [] ac char acTable [] = 0; Table 何かの表を意図しています。 unsigned char[] auc unsigned char aucTable[] = 0; int [] ai int aiTable [] = 0; unsigned int [] aui unsigned int auiTable[] = 0; long [] al long alTable [] = 0; unsigned long[] aul unsigned long aulTable[] = 0; float [] af float afTable [] = 0; double [] ad double adTable [] = 0;
これで、プログラム中に突然 afAngleRadian などと出てきても、これはラジアンで表された角度を float 型で収めた配列であることがすぐに分かってしまいます。
その他、STL で定義されている各種クラスやイテレータなどでは、次のような記法を使用しています。
型名 型を表す文字列 使用例 解説 -------------------------------------------------------------------------------- string str string strText = ""; const string kstr const string kstrName = "StlAlv"; vector vec vector<string> vecstrName; list list list<string> liststrName; map map map<int, str> mapistrKeyName; pair pair map<int, pair<int, str> mapipairistrKeyElement; iterator it... vector<int>::iterator itiLength it の後ろには、更に変数型 i をつけています。 const_iterator kit... vector<string>::const_ietarator kitstrName const は c でなく k にしています。
iterator はポインタに近いので上記のように、例えば int 型を指すポインタの場合には pi と書くように、iterator の場合には iti と書くようにしています。const_iterator が cit でなく kit であるのは c は char で既に使用していて紛らわしいからで、このときだけ突然ドイツ人になることにしています(ドイツ語では konstant と書きます)。mapipairistrKeyElement などは、始めて見るとぎょっとするかもしれませんが、慣れると、int 型をキーとして pair<int, string> を値とする map に キーと要素が入っているのだということが変数名を見るだけでわかるようになります。
その他、参照型には r をつけることにしています。例えば関数の引数などで次のような表現を良く使います。
型名 型を表す文字列 使用例 解説 -------------------------------------------------------------------------------- const string& rkstr void Func(const string& rkstrText);
最後に、テンプレート引数の場合には t で始めるようにしています。また、関数の戻り値の型を表す場合には Get の後ろにハンガリアン記法の文字列を続けるようにしています。
型名 型を表す文字列 使用例 解説 -------------------------------------------------------------------------------- T_float tf T_float tfXLength = 0; テンプレート型の浮動小数点数である X 方向の長さ T_float& rtf T_float& rtfAngle = GetrtfAngle(); テンプレート型の浮動小数点数への参照を返す関数
大体の気持ちは分かっていただけたでしょうか。私は、C++ で、分かりやすいプログラムを書くには、ハンガリアン記法が必須であると考えています。
冒頭に書いたように、システムハンガリアン記法を使って、C++ の変数をプリントするコードを自動生成するツールを公開します。
下の窓に変数名を入れて、「変数プリント用コード作成」ボタンを押すと、プリント用コードを別窓に表示します。
前節に記述したプレフィクスに加えて、クラスのメンバを表す m_ を変数名の冒頭に含んでいても処理が可能なようになっています。
ポインタ系のプレフィクスを持った変数は、全て、その変数のアドレスをプリントするコードを出力します。ポインタの指している先が正しいとは限らないので、これは安全のためにそのような仕様にしています。ポインタの指している先の変数の値をプリントしたいのであれば、手動でコードの修正を行なってください。
8 割程度の仕事はこなしてくれますので、後の 2 割は手動でどうぞ、というのが前提です。
当サイトでは、入力された文字列の保存は一切行っていません。しかし、平文がネットワークを通りますので、そういう意味で差し障りの無い文字列を入力してください。また、出力されたコードによって生じる利益、損害などあらゆるものについて、何も保証はいたしませんので予めそのような条件を納得された上でご使用ください。念のため。
これも上記同様に、システムハンガリアン記法を使って、C++ の関数のエントリで、関数名、変数名などをプリントするコードを自動生成するです。
下の窓に関数の冒頭部分を入れて、「関数の冒頭プリント用コード作成」ボタンを押すと、プリント用コードを別窓に表示します。
当サイトでは、入力された文字列の保存は一切行っていません。しかし、平文がネットワークを通りますので、そういう意味で差し障りの無い文字列を入力してください。また、出力されたコードによって生じる利益、損害などあらゆるものについて、何も保証はいたしませんので予めそのような条件を納得された上でご使用ください。念のため。
前項までのシステムハンガリアン記法は、主に変数型を扱うものでした。
これに対し、アプリケーションハンガリアン記法は、変数型ではなく、変数の意味を扱うものという位置づけになるでしょう。
例を上げれば分かりやすいでしょう
例えば、長さを表す変数 Length を考えます。これを整数型(Ingeter)の変数に入れると、システムハンガリアンでは iLength となります。
これだけだと、長さの単位が不明なので、メートルだったか?、ミリメートルだったか?、インチだったか? iLength を見るたびに悩むという状況が発生します。
そこで、ミリメートルを表す mm を接頭語に追加して、immLength とします。これで、前項までのツールとも競合せずに、Length は整数型で、単位は mm の長さを表す変数だということが丸分かりですね。
これだけのことですが、これを徹底してやるかやらないかは、プログラミングの効率、品質、面白さにかなり影響します。
店主 : それでもあなたは Length を使い続けますか? なーんてね。
ナオ : ふむ、ふむ。マイクロメートルは imicronLength なのかなー。ちょっと長いかも。
店主 : まぁ、そういう問題はあるね。トレードオフだァね。
ブレース {} は、プログラミング上、ほぼ、変数などのスコープを決定するものです。C++ や C 言語では、if 文や for 文では、statement が 1 個である場合、ブレースを省略可能ですが、私は、この場合でも省略しないで必ず書くことにしています。また、ブレースを置く位置は、開くブレース { は行末に、閉じるブレース } は改行後に書くことにしています。こうする方が、経験的に間違いが少なくなります。例えば次のようになります。
if (bFlag) { // 文が 1 個だけでも {} を省略しない。{ は行末に置く。 iValue = 0; } else { iValue = 1; } // } は行を改めて書く。
上記のように、内容が簡単な場合には、例外的に、{} を同じ行に書くこともあります。
if (bFlag) { iValue = 0; } else { iValue = 1; }
また、上記のように、ブレースの左右の位置はなるべく合わせるようにします。その方が見やすいし、矩形カット&ペーストが簡単に出来るというメリットがあります。
if 文の書き方は、特に else if で条件をつないでいく場合、最初の行に false を入れて、次のようにしています。
if (false) { } else if (条件1) { ... } else if (条件2) { ... } else if (条件3) ... } else { // どの条件も成立しなかった。 }
このようにすると、条件の行の入れ替えが簡単にできるというメリットがあります。
また、長めの条件を OR でつないでいく場合にも同じような書き方をします。
if (false || 長めの_____________条件1 || 長めの_____________条件2 || 長めの_____________条件3 ) { ... }
これも、条件の行の入れ替えが簡単にできるというメリットがあります。AND でつないでいく場合には最初の false を trure にすればよいわけです。