web-dev-qa-db-ja.com

型の後ではなく、変数名の前にアスタリスクが付いているのはなぜですか?

ほとんどのCプログラマが次のように変数に名前を付けるのはなぜですか。

int *myVariable;

こうではなく:

int* myVariable;

両方とも有効です。アスタリスクは変数名の一部ではなく、タイプの一部であるように思えます。誰でもこのロジックを説明できますか?

170
WBlasko

それらはまったく同じです。ただし、

int *myVariable, myVariable2;

MyVariableのタイプがint *であるのに対し、myVariable2のタイプはintであることが明らかです。に

int* myVariable, myVariable2;

両方ともint *型であることが明らかなように思えるかもしれませんが、myVariable2のタイプはintです。

したがって、最初のプログラミングスタイルはより直感的です。

227
luiscubal

別の方法で見ると、*myVariableint型であり、意味があります。

111
biozinc

その行の*は、型よりも変数により密接にバインドするためです。

int* varA, varB; // This is misleading

@Lundinが以下で指摘しているように、constはさらに多くの微妙な点を考慮に入れています。行ごとに1つの変数を宣言することで、これを完全に回避できます。

int* varA;
int varB;

明確なコードと簡潔なコードのバランスをとることは困難です— int a;も良くありません。それでも、1行につき1つの宣言がデフォルトであり、後でコードを結合することを心配しています。

39
ojrac

ここで誰も言及していないことは、このアスタリスクが実際にはCの「間接参照演算子」であるということです。

*a = 10;

上記の行は、10aに割り当てたいという意味ではなく、aが指すメモリ位置に10を割り当てたいという意味です。そして、誰も書いていない

* a = 10;

持ってる?したがって、dereference operatorは、ほとんど常にスペースなしで記述されます。これはおそらく、複数の行にまたがる乗算と区別するためです。

x = a * b * c * d
  * e * f * g;

ここで*eは誤解を招きやすいでしょう。

さて、次の行の実際の意味は次のとおりです。

int *a;

ほとんどの人は言うでしょう:

これは、aint値へのポインタであることを意味します。

これは技術的には正しいです。ほとんどの人はそのように見たり読んだりするのが好きで、それが現代のC標準が定義する方法です(言語C自体はすべてのANSIおよびISO標準に先行することに注意してください)。しかし、それを見る唯一の方法ではありません。この行は次のように読むこともできます。

aの逆参照された値はint型です。

そのため、実際には、この宣言のアスタリスクは逆参照演算子と見なすこともできます。これは、その配置も説明しています。 aがポインターであることは実際にはまったく宣言されていません。実際に間接参照できるのはポインターだけであるという事実によって暗示されています。

C標準では、*演算子に対して2つの意味のみを定義しています。

  • 間接演算子
  • 乗算演算子

そして、間接指定はただ一つの意味であり、ポインタを宣言するための特別な意味はありません。間接参照があります。間接参照は間接参照を実行します。したがって、int *a;のようなステートメント内でこれは間接access*は間接アクセスを意味します)。したがって、上記の2番目のステートメントは最初のステートメントよりも標準にはるかに近くなります。

30
Mecki

私はここで手足に出て、この質問に対する直接的な答えがあると言います、変数宣言とパラメータと戻り値の両方についてタイプ、つまり、アスタリスクは名前の横にある必要があります:_int *myVariable;_。理由を理解するには、Cで他のタイプのシンボルを宣言する方法を見てください。

int my_function(int arg);関数の場合;

_float my_array[3]_配列の場合。

宣言に続く使用と呼ばれる一般的なパターンは、シンボルのタイプが名前の前の部分と、around名前、および名前の前後のこれらの部分は、左側の型の値を取得するために使用する構文を模倣します。

int a_return_value = my_function(729);

_float an_element = my_array[2];_

および:_int copy_of_value = *myVariable;_。

参照を使用する時点での構文は値型の構文と同じであるため、C++は参照の動作にスパナをスローします。したがって、C++はCに対して異なるアプローチをとると主張できます。ポインターの場合のCの振る舞い。したがって、参照はこの点で奇妙なものとして実際に立ちます。

17
Injektilo

それは好みの問題です。

コードを読むとき、2番目のケースでは変数とポインターを区別するのが簡単ですが、変数とポインターの両方を共通のタイプの1行に入れると混乱を招く可能性があります(それ自体はプロジェクトガイドラインで推奨されていないため、可読性が低下するため)。

型名の横に対応する記号でポインターを宣言することを好みます。

int* pMyPointer;
11
macbirdie

偉大な教祖はかつて「コンパイラのやり方で読んでください」と言っていました。

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

これはconst配置のトピックに関するものでしたが、ここでも同じルールが適用されます。

コンパイラは次のように読み取ります。

int (*a);

としてではなく:

(int*) a;

変数の隣に星を置く習慣を身に付けると、宣言が読みやすくなります。また、次のような目障りなものも避けます。

int* a[10];

-編集-

int (*a)として解析されたという意味を正確に説明すると、式*4 + 3 * 7がより厳密にバインドする方法で、aよりも3intに強くバインドされることを意味します7の優先順位が高いため、4よりも*が優先されます。

アスキーアートの謝罪、A.S.Tの概要int *aの解析は、おおよそ次のようになります。

      Declaration
      /         \
     /           \
Declaration-      Init-
Secifiers       Declarator-
    |             List
    |               |
    |              ...
  "int"             |
                Declarator
                /       \
               /        ...
           Pointer        \
              |        Identifier
              |            |
             "*"           |
                          "a"

明らかに示されているように、*は、共通の祖先がaであるため、Declaratorにより緊密にバインドされますが、Declarationを含む共通の祖先を見つけるには、ツリーの最後までintに移動する必要があります。

7
dgnuff

K&Rでは、タイプではなく変数名の隣にポインター演算子を配置します。個人的には、その本は究極のCの権威/聖書であると考えています。また、Objective-Cのバックグラウンドから来て、私は*名前の規則に従います。

6
The_Androctonus

なぜなら、次のような宣言があると、より理にかなっているからです。

int *a, *b;
3
Greg Rogers

1行で複数のポインターを宣言するには、int* a, * b;より直感的に "a"を整数へのポインタとして宣言し、同様に "b"を宣言するときにスタイルを混在させません。誰かが言ったように、私はとにかく同じステートメントで2つの異なる型を宣言しません。

2
heyitsluke

私はすでにCPで同様の質問に回答しましたが、誰も言及していないので、ここでもCが自由形式言語であることを指摘する必要があります。パーサーが各トークンを区別できる間、どのスタイルを選択してもOKです。このCの特性は、 C難読化コンテスト と呼ばれる非常に特殊なタイプのコンテストにつながります。

1
Frankie_C

_int* x;_を好む人は、型が左側にあり、識別子(名前)が右側にある架空の世界にコードを強制しようとしています。

私は「架空の」と言います。なぜなら:

CおよびC++では、一般的な場合、宣言された識別子は型情報に囲まれています。

それはクレイジーに聞こえるかもしれませんが、あなたはそれが真実であることを知っています。ここではいくつかの例を示します。

  • int main(int argc, char *argv[])は、「mainintcharへのポインターの配列を取り、intを返す関数です」という意味です。つまり、タイプ情報のmostが右側にあります。一部の人々は、関数宣言は何らかの形で「特別」であるため、カウントしないと考えています。 OK、変数を試してみましょう。

  • void (*fn)(int)は、fnintを取り、何も返さない関数へのポインタであることを意味します。

  • _int a[10]_は、10個のintsの配列として 'a'を宣言します。

  • _pixel bitmap[height][width]_。

  • 明らかに、私が主張する権利の多くのタイプ情報を持っているチェリーピックされた例があります。 _struct { int x; int y; } center_のように、ほとんどではないがすべての型が左側にある多くの宣言があります。

この宣言構文は、宣言に使用法を反映させたいというK&Rの欲求から生まれました。単純な宣言を読むことは直感的であり、より複雑なものを読むことは、左右規則を学ぶことで習得できます(スパイラル規則または単に左右規則と呼ぶこともあります)。

Cは非常に単純なので、多くのCプログラマーがこのスタイルを採用し、単純な宣言を_int *p_として記述します。

C++では、構文が(クラス、参照、テンプレート、列挙クラスを使用して)少し複雑になり、その複雑さへの反応として、多くの宣言で型を識別子から分離するための労力が増えます。つまり、大量のC++コードをチェックアウトすると、_int* p_- style宣言が多く表示される場合があります。

どちらの言語でも、canは常にvariable宣言の左側に型を持ち、(1)同じステートメントで複数の変数を宣言しないこと、および(2)使用することにより、 of typedefs(または、皮肉なことに、エイリアス識別子を型の左側に置くエイリアス宣言)。例えば:

_typedef int array_of_10_ints[10];
array_of_10_ints a;
_
1
Adrian McCarthy

このトピックの議論の多くは単純な主観的であり、「星が変数名に結び付く」という議論は素朴です。以下は、単なる意見ではないいくつかの議論です。


忘れられたポインター型修飾子

正式には、「星」は型にも変数名にも属さず、pointerという名前の独自の文法項目の一部です。正式なC構文(ISO 9899:2018)は次のとおりです。

(6.7)宣言:
declaration-specifiersinit-declarator-listopt _;_

declaration-specifiersにはタイプ(およびストレージ)が含まれ、init-declarator-listにはポインターと変数名が含まれます。この宣言子リストの構文をさらに分析すると、次のようになります。

(6.7.6)宣言子:
ポインターopt 直接宣言子
...
(6.7.6)ポインター:
_*_ type-qualifier-listopt
_*_ type-qualifier-listopt ポインター

宣言子が宣言全体である場合、直接宣言子は識別子(変数名)であり、ポインターは、ポインター自体に属するオプションの型修飾子リストが後に続くスターです。

「星が変数に属している」というさまざまなスタイルの議論の一貫性を損なうのは、これらのポインター型修飾子を忘れているからです。 _int* const x_、_int *const x_または_int*const x_?

_int *const a, b;_を考えてください。abのタイプは何ですか? 「星が変数に属している」ことはもはや明白ではありません。むしろ、constがどこに属するかを熟考し始めるでしょう。

あなたは間違いなく、星がポインタ型修飾子に属しているという説得力のある議論をすることができますが、それ以上のものはありません。

ポインターの型修飾子リストは、_int *a_スタイルを使用しているユーザーに問題を引き起こす可能性があります。 typedef内でポインターを使用し(これは非常に悪い習慣です)、「星は変数名に属する」と考える人は、この非常に微妙なバグを書く傾向があります。

_    /*** bad code, don't do this ***/
    typedef int *bad_idea_t; 
    ...
    void func (const bad_idea_t *foo);
_

これはきれいにコンパイルされます。これで、コードはconst constになったと思うかもしれません。そうではありません!このコードは、誤って偽造されたconstの正当性です。

fooのタイプは、実際は_int*const*_です-最も外側のポインターは読み取り専用になり、データを指すポインターではありません。したがって、この関数内では_**foo = n;_を実行でき、呼び出し元の変数値を変更します。

これは、式_const bad_idea_t *foo_では、ここで_*_が変数名に属していないためです!擬似コードでは、このパラメーター宣言はconst (bad_idea_t *) fooおよびnotとして_(const bad_idea_t) *foo_として読み取られます。この場合、星は隠しポインター型に属します-型はポインターであり、const修飾ポインターは_*const_と記述されます。

しかし、上記の例の問題の根本は、_*_スタイルではなく、typedefの後ろにポインターを隠す方法です。


1行の複数の変数の宣言について

単一行で複数の変数を宣言することは、悪い習慣として広く認識されています1)。 CERT-Cは次のようにうまくまとめています。

DCL04-C。宣言ごとに複数の変数を宣言しないでください

英語を読むだけで、常識はa宣言はone宣言でなければならないことに同意します。

また、変数がポインターであるかどうかは関係ありません。各変数を1行で宣言すると、ほとんどすべての場合にコードが明確になります。

したがって、プログラマが_int* a, b_で混乱するという議論は悪いです。問題の根本は、_*_の配置ではなく、複数の宣言子の使用です。スタイルに関係なく、代わりにこれを書く必要があります。

_    int* a; // or int *a
    int b;
_

別の健全だが主観的な議論は、_int* a_が与えられた場合、aの型は疑いなく_int*_であるため、星は型修飾子に属するということです。

しかし基本的に私の結論は、ここに投稿された議論の多くは単なる主観的で素朴なものだということです。どちらのスタイルについても、実際に有効な議論をすることはできません。それは、主観的な個人的な好みの問題です。


1) CERT-C DCL04-C .

0
Lundin