web-dev-qa-db-ja.com

なぜ変数を1行で宣言し、次の行に割り当てるのですか?

CおよびC++コードでは、次の規則がよく見られます。

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

の代わりに

some_type val = something;
some_type *ptr = &something_else;

これは、すべてのローカル変数をスコープの最上位で宣言する必要があった時代から残っている習慣だと当初は思っていました。しかし、私はベテラン開発者の習慣をそんなにすぐに却下しないことを学びました。では、1行で宣言し、後で割り当てることには十分な理由がありますか?

102

C

C89では、すべての宣言ある必要がありましたはスコープ({ ... })の先頭にありますが、この要件はすぐに削除されました(最初はコンパイラー拡張で、後で標準で)。

C++

これらの例は同じではありません。 some_type val = something;はコピーコンストラクターを呼び出し、val = something;はデフォルトコンストラクターを呼び出してから、operator=関数を呼び出します。この違いはしばしば重要です。

習慣

一部の人々は、最初に変数を宣言し、後でそれらを定義することを好みます。これらは、ある場所での宣言と別の場所での定義でコードを後で再フォーマットする場合です。

ポインターについては、そのポインターをどのように処理するかに関係なく、NULLまたはnullptrへのすべてのポインターを初期化する癖がある人もいます。

92
orlp

質問CとC++に同時にタグを付けましたが、これらの言語では回答が大幅に異なります。

まず、質問のタイトルの文言が正しくありません(正確には、質問自体とは無関係です)。どちらの例でも、変数は宣言および定義が同時に、1行にあります。例の違いは、最初の例では変数がninitializedまたはinitializedのダミー値で残されており、次にassigned aであることです。後で意味のある値。 2番目の例では、変数はinitializedです。

次に、C++言語では、@ nightcrackerが彼の回答で述べたように、これらの2つの構成は意味的に異なります。最初のものは初期化に依存し、2番目のものは割り当てに依存します。 C++では、これらの操作はオーバーロード可能であるため、異なる結果をもたらす可能性があります(ただし、初期化と割り当ての同等ではないオーバーロードを生成することはお勧めできません)。

元の標準C言語(C89/90)では、ブロックの途中で変数を宣言することは違法です。そのため、変数がブロックの先頭で初期化されていない(またはダミー値で初期化された)と宣言され、意味のある変数が割り当てられます。後で、これらの意味のある値が使用可能になったときに値を返します。

C99言語では、(C++の場合と同様に)ブロックの途中で変数を宣言しても問題ありません。つまり、最初のアプローチが必要なのは、宣言の時点で初期化子が不明な特定の状況のみです。 (これはC++にも適用されます)。

27
AnT

「地方宣言」時代から残った古い習慣だと思います。したがって、あなたの質問への答えとして:いいえ、正当な理由があるとは思いません。私は自分でやることはありません。

13
CornflakesDK

私の答え から Helium3による質問 でそれについて何か言いました。

基本的には、何が変更されたかを簡単に確認できるようにするための視覚的な支援だと思います。

if (a == 0) {
    struct whatever *myobject = 0;
    /* did `myobject` (the pointer) get assigned?
    ** or was it `*myobject` (the struct)? */
}

そして

if (a == 0) {
    struct whatever *myobject;
    myobject = 0;
    /* `myobject` (the pointer) got assigned */
}
4
pmg

他の答えはかなり良いです。 Cにはこれに関するいくつかの歴史があります。C++では、コンストラクターと代入演算子の違いがあります。

誰も追加のポイントについて言及していないことに驚いています。変数の使用と宣言を分離しておくと、読みやすくなる場合があります。

視覚的に言えば、コードを読み取るときに、変数のタイプや名前などのありふれたアーティファクトは、すぐに飛び出すものではありません。それは ステートメント 通常、最も関心があり、じっくりとじっと時間を費やしているため、残りの部分に目を向ける傾向があります。

いくつかのタイプ、名前、および割り当てがすべて同じ狭いスペースで発生している場合、それは情報過多のビットです。また、普段私が見ている空間でも、何か重要なことが起こっているということです。

直感に反するように思えるかもしれませんが、これは、ソースを垂直方向のスペースをより多く使用することで改善できる場合の1つの例です。私はこれを、狭い垂直空間でクレイジーな量のポインター演算と代入を行う詰まった行を記述してはならない理由に似ていると考えています。それはいつも。 :-)

4
asveikau

Cでは、これは標準的な方法でした。C++とは異なり、変数は関数の最初に宣言する必要があり、その後で使用するために関数本体の任意の場所で宣言できるからです。ポインターがガベージを指していないことを確認しただけなので、ポインターは0またはNULLに設定されました。そうでなければ、私が考えることができる重要な利点はありません、それは誰もがそのようにすることを強います。

2
Vite Falcon

変数の定義とその意味のある初期化をローカライズする利点:

  • 変数がコードに最初に表示されるときに習慣的に意味のある値が割り当てられている場合(同じことについての別の見方:意味のある値が使用可能になるまでその出現を遅らせる)、次に可能性なしそれらが誤って使用される無意味な値または初期化されていない値(条件付きステートメント、短絡評価、例外などが原因で、初期化が誤ってバイパスされている場合に簡単に発生する可能性があります)

  • より多くすることができます効率的

    • 初期値を設定するオーバーヘッドを回避します(デフォルトの構築またはNULLなどのセンチネル値への初期化)
    • operator=は効率が低下し、一時オブジェクトが必要になる場合があります
    • sometimes(特にインライン関数の場合)オプティマイザは一部/すべての非効率を削除できます

  • 変数のスコープを最小化すると、averageの数が最小になりますスコープ内の変数:this

    • 精神的に追跡しやすいスコープ内の変数、それらの変数に影響を与える可能性のある実行フローとステートメント、およびそれらの値のインポート
    • 少なくともいくつかの複雑で不透明なオブジェクトの場合、これはプログラムのリソース使用量を減らします(ヒープ、スレッド、共有メモリ、記述子)
  • 定義で変数名を繰り返していないため、最初の意味のある割り当てで繰り返しているため、場合によってはより簡潔になります

  • 参照などの特定のタイプ、およびオブジェクトをconstにしたい場合に必要

変数定義をグループ化するための引数:

  • いくつかの変数の型を除外するのは便利かつ/または簡潔の場合があります。

    the_same_type v1, v2, v3;

    (理由がタイプ名が長すぎるか複雑であることだけの場合は、typedefの方が良い場合があります)

  • 一部の操作に含まれる変数のセット(およびタイプ)を強調するために、使用方法とは無関係に変数をグループ化することが望ましい場合があります。

    type v1;
    type v2;type v3;

    これは、タイプの共通性を強調し、それらを変更するのが少し簡単になりますが、コピー/貼り付けを容易にする行ごとの変数に固執します//コメントなど.

プログラミングの場合はよくあることですが、ほとんどの状況で1つのプラクティスに明確な経験的メリットがある可能性がありますが、他のプラクティスはいくつかのケースでは圧倒的に優れている場合があります。

2
Tony