これは素晴らしい練習です。
ループ内に変数を作成することにより、それらのスコープがループ内に制限されていることを確認します。ループの外で参照したり呼び出したりすることはできません。
こちらです:
変数の名前が少し「汎用」(「i」など)である場合、コード内のどこかで同じ名前の別の変数と混合するリスクはありません(-Wshadow
GCCの警告命令を使用して軽減することもできます)
コンパイラは、変数のスコープがループ内に限定されていることを認識しているため、変数が誤って他の場所で参照されている場合、適切なエラーメッセージを発行します。
最後に重要なことですが、変数はループの外では使用できないことがわかっているため、コンパイラー(最も重要なのはレジスターの割り当て)によっていくつかの専用最適化をより効率的に実行できます。たとえば、後で再利用するために結果を保存する必要はありません。
要するに、あなたはそれをする権利があります。
ただし、変数は各ループ間でその値を保持することになっているわけではないことに注意してください。そのような場合、毎回初期化する必要があるかもしれません。ループを取り囲む大きなブロックを作成することもできます。その唯一の目的は、ループ間で値を保持する必要がある変数を宣言することです。これには通常、ループカウンター自体が含まれます。
{
int i, retainValue;
for (i=0; i<N; i++)
{
int tmpValue;
/* tmpValue is uninitialized */
/* retainValue still has its previous value from previous loop */
/* Do some stuff here */
}
/* Here, retainValue is still valid; tmpValue no longer */
}
質問2の場合:関数が呼び出されると、変数は一度だけ割り当てられます。実際、割り当ての観点からは、関数の最初で変数を宣言することと(ほぼ)同じです。唯一の違いはスコープです。変数はループの外では使用できません。(スコープが終了した他の変数からの)空きスロットを再利用するだけで、変数が割り当てられない可能性もあります。
スコープが制限され、より正確になると、より正確な最適化が実現します。しかし、より重要なのは、コードの他の部分を読み取るときに心配する状態(変数)が少ないため、コードが安全になることです。
これは、if(){...}
ブロックの外側でも同じです。通常、代わりに:
int result;
(...)
result = f1();
if (result) then { (...) }
(...)
result = f2();
if (result) then { (...) }
書く方が安全です:
(...)
{
int const result = f1();
if (result) then { (...) }
}
(...)
{
int const result = f2();
if (result) then { (...) }
}
特にそのような小さな例では、違いはわずかに見えるかもしれません。しかし、より大きなコードベースでは、それが役立ちます。これで、ブロックからブロックにresult
値を転送するリスクがなくなりました。それぞれが独自のスコープに厳密に制限されているため、役割がより正確になります。レビューアーの観点から見ると、心配して追跡する必要がある長距離状態変数が少ないため、はるかに優れています。f1()
f2()
result
コンパイラーでさえもより効果的になります。将来、コードの誤った変更の後で、result
で適切に初期化されないと仮定しf2()
ます。2番目のバージョンは単に動作を拒否し、コンパイル時に明確なエラーメッセージを示します(実行時よりも優れています)。最初のバージョンは何も見つけられず、の結果f1()
は単純に2回テストされ、の結果と混同されf2()
ます。
補足情報
オープンソースのツールCppCheck(C / C ++コードの静的分析ツール)は、変数の最適なスコープに関する優れたヒントを提供します。
割り当てに関するコメントへの回答:上記のルールはCでは当てはまりますが、一部のC ++クラスには当てはまらない場合があります。
標準の型と構造の場合、変数のサイズはコンパイル時にわかります。Cには「構築」などはありません。そのため、関数が呼び出されると、変数のスペースは単にスタックに割り当てられます(初期化なし)。これが、ループ内で変数を宣言するときに「ゼロ」のコストがかかる理由です。
ただし、C ++クラスの場合、このコンストラクタについてはあまり詳しくありません。コンパイラーは同じスペースを再利用するのに十分賢いはずなので、割り当てはおそらく問題にならないと思いますが、初期化は各ループ反復で行われる可能性があります。