初心者にCポインター(宣言と単項演算子)を説明する方法は?


141

私は最近、Cプログラミングの初心者へのポインタを説明することができて、次の困難に遭遇しました。ポインタの使用方法をすでに知っている場合は、まったく問題に見えないかもしれませんが、次の例を明確に考えてみてください。

int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);

まったくの初心者にとって、出力は意外かもしれません。2行目では、* barを&fooと宣言したばかりですが、4行目では、* barは実際には&fooではなくfooであることがわかります。

混乱は、*シンボルのあいまいさから生じていると言えるかもしれません。2行目では、ポインターの宣言に使用されています。4行目では、ポインターが指す値をフェッチする単項演算子として使用されています。二つの違うことですよね?

ただし、この「説明」は初心者にはまったく役立ちません。微妙な食い違いを指摘することで、新しいコンセプトを紹介しています。これはそれを教えるための正しい方法ではありません。

それで、カーニハンとリッチーはそれをどのように説明しましたか?

単項演算子*は間接演算子または逆参照演算子です。ポインターに適用されると、ポインターが指すオブジェクトにアクセスします。[…]

ポインターipの宣言は、int *ipニーモニックとして意図されています。それは式*ipがint であると言います。変数の宣言の構文は、変数が出現する可能性のある式の構文を模倣しています

int *ip*ipが返されますint」のように読む必要がありますか?しかし、なぜ宣言後の割り当てがそのパターンに従っていないのですか?初心者が変数を初期化したい場合はどうなりますか?int *ip = 1(読み:*ip返されるintintある1)期待通りに動作しません。概念モデルは首尾一貫していないようです。ここで何か不足していますか?


編集:それはここで答え要約しようとしました


15
一番良い説明は、紙に物を描いて矢印でつなぐことです;)
Maroun

16
ポインターの構文を説明する必要があったとき*、宣言では「ポインターを宣言する」を意味するトークンであり、式では逆参照演算子であり、これら2つは偶然同じシンボルを持つ異なるものを表すという事実を常に主張しました(乗算演算子と同じ-同じ記号、異なる意味)。混乱を招きますが、実際の状況と異なるものはさらに悪化します。
Matteo Italia

40
int* barスターを実際には型の一部であり、識別子の一部ではないことをより明確にするようにそれを書くかもしれません。もちろん、これはのような直感的でないものでさまざまな問題に遭遇しint* a, bます。
Niklas B. 14

9
K&Rの説明はばかげて不必要だといつも思っていました。言語は、2つの異なるものに同じ記号を使用し、それを処理する必要があります。*コンテキストに応じて2つの異なる意味を持つことができます。同じ文字でも、単語によって発音が異なるのと同じように、多くの言語を話すことを学ぶのは難しいです。すべての概念/操作に独自の記号がある場合、より大きなキーボードが必要になるため、意味がある場合は記号をリサイクルします。
Art

8
私はCを他の人に教えるときに何度も同じ問題に遭遇しましたが、私の経験では、ほとんどの人が提案した方法で解決できます。最初に、C構文のないポインターの概念を説明します。次に、構文を教え、タイプ(の一部としてアスタリスクを重視int* pあなたの学生を警告しながら、)に対するポインタが関与しているときに、同じ行に複数の宣言を使用しました。生徒がポインタの概念を完全に理解したら、int *pis構文は同等であることを生徒に説明し、複数の宣言の問題を説明します。
Theodoros Chatzigiannakis 2014

回答:


43

生徒*が異なるコンテキストでのシンボルの意味を理解するには、まずコンテキストが実際に異なることを理解する必要があります。コンテキストが異なること(つまり、割り当ての左側と一般的な表現の違い)を理解すると、違いが何であるかを理解するのにそれほど大きくはなりません。

まず、変数の宣言に演算子を含めることはできないことを説明します(変数の宣言に-or +記号を置くとエラーが発生するだけであることを示して、これを示します)。次に、式(つまり、割り当ての右側)に演算子を含めることができることを示します。式と変数宣言は2つのまったく異なるコンテキストであることを生徒が理解していることを確認してください。

コンテキストが異なることを理解したら、*シンボルが変数識別子の前の変数宣言にある場合、「この変数をポインターとして宣言する」ことを意味することを説明できます。次に、式で使用した場合(単項演算子として)、*記号は「逆参照演算子」であり、以前の意味ではなく「アドレスの値」を意味することを説明できます。

生徒を本当に納得させるために、Cの作成者は逆参照演算子を意味するために任意の記号を使用できた(つまり、@代わりに使用できた)が、何らかの理由で設計の決定を行ったと説明してください*

全体として、コンテキストが異なることを説明する方法はありません。コンテキストが異なることを生徒が理解していないと、*シンボルが異なる意味を持っている理由を理解できません。


80

略記の理由:

int *bar = &foo;

あなたの例では混乱する可能性がありますが、それを以下と同等であると誤解するのは簡単です:

int *bar;
*bar = &foo;    // error: use of uninitialized pointer bar!

それが実際に意味するとき:

int *bar;
bar = &foo;

このように記述され、変数の宣言と代入が分離されているので、そのような混乱の可能性はなく、K&Rの引用で説明されているuse↔宣言の並列処理は完全に機能します。

  • 最初の行はbar、のような変数を宣言しています。*barint

  • 2行目は、アドレスの割り当てfoobar、製造*barint)のエイリアスfoo(別名int)。

初心者にCポインター構文を導入する場合、最初にこのスタイルのポインター宣言を割り当てから分離することに固執し、ポインターの基本的な概念が使用されたときに、組み合わせの省略構文(混乱の可能性についての適切な警告付き)のみを導入することが役立つ場合があります。 Cは適切に内部化されています。


4
に誘惑されtypedefます。 typedef int *p_int;型の変数があることを意味p_int性質がある*p_intですint。次に、p_int bar = &foo;ます。初期化されていないデータを作成し、後でそれをデフォルトの習慣の問題として割り当てることをだれかに奨励することは、悪い考えのように思われます。
Yakk-Adam Nevraumont 2014

6
これは、脳が損傷したC宣言のスタイルです。ポインタに固有ではありません。検討するint a[2] = {47,11};。これは、(存在しない)要素の初期化ではありませんa[2]
Marc van Leeuwen

5
@MarcvanLeeuwen脳​​の損傷に同意します。理想的に*は、変数にバインドされていない型の一部である必要があります。そうすれば、int* foo_ptr, bar_ptr2つのポインタを宣言するように記述できます。しかし、実際にはポインタと整数を宣言しています。
Barmar 14

1
それは単に「速記」の宣言/割り当てだけではありません。関数の引数としてポインタを使用したい瞬間に、問題全体が再び出てきます。
armin 2014

30

宣言不足

宣言と初期化の違いを知っておくと便利です。変数を型として宣言し、値で初期化します。両方を同時に行う場合、それを定義と呼ぶことがよくあります。

1. int a; a = 42;

int a;
a = 42;

私たちは宣言しますintの名前Aを。次に、値を指定して初期化します42

2. int a = 42;

私たちは宣言し、inta名前を付け、値を42にし42ます。これはで初期化されます。定義。

3. a = 43;

変数を使用するときはそれらを操作すると言います。a = 43割り当て操作です。変数aに番号43を割り当てます。

言うことによって

int *bar;

barをintへのポインタとして宣言します。言うことによって

int *bar = &foo;

私たちは宣言します barし、それをfooのアドレスで初期化ます

初期化した後 barを、同じ演算子であるアスタリスクを使用して、fooの値にアクセスして操作できます。オペレーターなしでは、ポインターが指しているアドレスにアクセスして操作します。

それに、私はその絵を語らせました。

何が起こっているかについての簡略化されたASCIIMATION。(そして、一時停止したい場合はここでプレーヤーバージョンなど)

          ASCIIMATION


22

2番目のステートメントint *bar = &foo;は、メモリ内で次のように絵で見ることができます。

   bar           foo
  +-----+      +-----+
  |0x100| ---> |  1  |
  +-----+      +-----+ 
   0x200        0x100

これbarはのintアドレス&を含む型のポインタですfoo。単項演算子*を使用して、ポインタを使用して「foo」に含まれる値を取得することを参照しますbar

編集:初心者の私のアプローチmemory addressは変数の説明です

Memory Address:すべての変数には、OSによって提供されるアドレスが関連付けられています。ではint a;&a変数のアドレスですa

Cas で変数の基本的なタイプを説明し続け、

Types of variables: 変数はそれぞれの型の値を保持できますが、アドレスは保持できません。

int a = 10; float b = 10.8; char ch = 'c'; `a, b, c` are variables. 

Introducing pointers: 上記の変数のように、例えば

 int a = 10; // a contains value 10
 int b; 
 b = &a;      // ERROR

変数は値を保持できるがアドレスは保持できb = aないためb = &a、割り当てることはできますbが、可能ではありません。したがって、ポインタが必要です。

Pointer or Pointer variables :変数にアドレスが含まれている場合、それはポインター変数と呼ばれます。使用し*、それがポインタであることを知らせるための宣言に。

 Pointer can hold address but not value
 Pointer contains the address of an existing variable.
 Pointer points to an existing variable

3
問題は、int *ip「ipはint型のポインタ(*)」として読み取ると、のようなものを読み取るときに問題が発生することですx = (int) *ip
アーミン14

2
@abwこれは完全に異なるものなので、かっこです。宣言とキャスティングの違いを理解するのに苦労する人はいないと思います。
bzeaman 2014

@abwではx = (int) *ip;、ポインターipを逆参照して値を取得し、値intをどんな型からでもキャストしますip
Sunil Bojanapally 2014

1
@BennoZeeman正解です。キャストと宣言は2つの異なるものです。私はアスタリスクの異なる役割をヒントにしようとしました:1つ目は「これはintではなく、intへのポインターです」2つ目は「これはintを提供しますが、intへのポインターは提供しません」。
Armin、2014

2
@abw:だから、教えるint* bar = &foo;ことは負荷をより意味のあるものにします。はい、1つの宣言で複数のポインターを宣言すると問題が発生することはわかっています。いいえ、それは重要ではないと思います。
2014

17

ここでの回答とコメントを見ると、問題の構文は初心者を混乱させる可能性があるという一般的な合意があるようです。それらのほとんどはこれらの線に沿って何かを提案します:

  • コードを表示する前に、図、スケッチ、またはアニメーションを使用して、ポインターの動作を説明します。
  • 構文を示す場合は、アスタリスク記号の2つの異なる役割を説明してください。多くのチュートリアルが欠落しているか、その部分を回避しています。混乱が生じる(「初期化されたポインター宣言を宣言と後で割り当てに分割するときは、必ず*を削除する必要がある」– comp.lang.c FAQ別のアプローチを見つけたかったのですが、これは行く方法。

違いを強調するint* bar代わりに書くことができますint *bar。これは、K&Rの「宣言を模倣する使用」アプローチではなく、Stroustrup C ++アプローチに従うことを意味します。

*bar整数であることを宣言しません。であることを宣言barしますint*。新しく作成した変数を同じ行で初期化する場合はbar、ではなくを扱っていることは明らかです*barint* bar = &foo;

欠点:

  • 複数ポインター宣言の問題(int* foo, barvs int *foo, *bar)について生徒に警告する必要があります。
  • 傷つい世界に備える必要があります。多くのプログラマーは、変数の名前の隣にアスタリスクを表示することを望んでおり、スタイルを正当化するために非常に長い時間がかかります。そして、多くのスタイルガイドはこの表記法を明示的に強制します(Linuxカーネルコーディングスタイル、NASA Cスタイルガイドなど)。

編集:提案されている別のアプローチは、K&Rの「模倣」方法を使用することですが、「省略形」構文を使用しません(ここを参照)。同じ行で宣言と割り当て省略するとすぐに、すべてがより一貫性のあるものに見えます。

ただし、遅かれ早かれ、生徒は関数の引数としてポインタを扱わなければなりません。戻り型としてのポインタ。そして関数へのポインタ。との違いを説明する必要がint *func();ありint (*func)();ます。遅かれ早かれ、バラバラになると思います。そして、多分もっと早い方が遅いより良いです。


16

K&Rスタイルint *pとStroustrupスタイルが支持する理由がありint* pます。どちらも各言語で有効です(同じことを意味します)が、Stroustrupは次のように述べています。

「int * p;」からの選択 そして「int * p;」正誤についてではなく、スタイルと強調についてです。C強調表現; 多くの場合、宣言は必要な悪に過ぎないと考えられていました。一方、C ++は型に重点を置いています。

さて、ここでCを教えようとしているので、それは型よりも表現を強調する必要があることを示唆していますが、一部の人々は一方の強調をもう一方よりもすばやく簡単に理解でき、それは言語ではなくそれらについてです。

したがって、一部の人は、はとint*は異なるものであるという考えから始めて、intそこから移動する方が簡単だと感じるでしょう。

誰かがintではなくへのポインタとしてint* bar持っbarているものとして使用していた見方をすぐに理解した場合int、彼らはそれ*barがに何かをしていることをすぐに理解bar、残りは続くでしょう。それが終わったら、Cプログラマーが好む傾向がある理由を後で説明できます。int *bar

か否か。誰もが最初にコンセプトを理解する1つの方法があったとしても、そもそも問題はなかったでしょう。ある人にそれを説明する最良の方法は、必ずしも別の人にそれを説明する最良の方法とは限りません。


1
Stroustrupの議論は好きですが、なぜ彼が&記号を参照を示すために選んだのでしょうか-もう一つの落とし穴。
armin

1
@abw彼が対称性を見たのは、私たちができるなら私たちができるということだと思いint* p = &aますint* r = *p。彼がThe Design and Evolution of C ++で取り上げたと確信していますが、私がそれを読んでから長い時間が経っています。
Jon Hanna 2014

3
そうだねint& r = *p。そして借り手はまだ本を消化しようとしているに違いない。
armin

@abwはい、それはまさに私が意図したものでした。残念ながら、コメントにタイプミスがあってもコンパイルエラーは発生しません。その本は実際にはかなり活発に読まれている。
Jon Hanna 2014

4
CよりもPascalの構文(一般的に拡張されている)を優先する理由の1つは、Var A, B: ^Integer;「整数へのポインタ」の型が両方に適用されることを明確にしていることです。ABK&Rスタイルを使用することint *a, *bも可能です。しかし、宣言のようにint* a,b;、しかし、あたかもルックスabの両方が宣言されているが通りint*、現実にはそれが宣言しaint*bとしてint
スーパーキャット2014

9

tl; dr:

Q:Cポインター(宣言と単項演算子)を初心者に説明するにはどうすればよいですか?

A:しないでください。初心者へのポインタを説明し、その後、ポインタの概念をC構文で表す方法を示します。


私は最近、Cプログラミングの初心者へのポインタを説明することができて、次の困難に遭遇しました。

IMO Cの構文はひどいものではありませんが、すばらしいものでもありません。すでにポインターを理解している場合でも、大きな障害にはならず、ポインターを学習する上での助けにもなりません。

したがって、まずポインタについて説明し、彼らがそれらを本当に理解していることを確認してください:

  • ボックスと矢印の図で説明します。16進アドレスなしでそれを行うことができます。関連がない場合は、別のボックスまたはNULシンボルを指す矢印を表示するだけです。

  • 擬似コードで説明:ちょうど書き込みのfooのアドレスバーで格納された値

  • 次に、初心者がポインタとは何か、その理由、およびその使用方法を理解すると、次に、C構文へのマッピングを示します。

K&Rのテキストが概念モデルを提供していない理由は、彼らがすでにポインターを理解しいるためだと思います。ニーモニックは、よく理解されている概念から構文へのマッピングを思い出させるものにすぎません。


確かに; 最初に理論から始め、構文は後で来ます(重要ではありません)。メモリ使用量の理論は言語に依存しないことに注意してください。このボックスと矢印のモデルは、あらゆるプログラミング言語でのタスクに役立ちます。
oɔɯǝɹ

(グーグルも役立つでしょうが)いくつかの例についてはこちらをご覧くださいeskimo.com/~scs/cclass/notes/sx10a.htmlを
oɔɯǝɹ

7

この問題は、Cの学習を開始するときに多少混乱します。

始めるのに役立つ基本的な原則は次のとおりです。

  1. Cにはいくつかの基本的なタイプしかありません。

    • char:サイズが1バイトの整数値。

    • short:サイズが2バイトの整数値。

    • long:サイズが4バイトの整数値。

    • long long:サイズが8バイトの整数値。

    • float:サイズが4バイトの非整数値。

    • double:サイズが8バイトの非整数値。

    各タイプのサイズは通常、標準ではなくコンパイラによって定義されることに注意してください

    整数型shortlonglong long通常はが続きintます。

    ただし、これは必須ではなく、なしで使用できますint

    代わりに、単に状態を示すこともできますがint、コンパイラによって解釈が異なる場合があります。

    これを要約すると:

    • shortと同じですが、short int必ずしも同じである必要はありませんint

    • longと同じですが、long int必ずしも同じである必要はありませんint

    • long longと同じですが、long long int必ずしも同じである必要はありませんint

    • 特定のコンパイラでintは、short intまたはlong intまたはlong long intです。

  2. あるタイプの変数を宣言する場合、それを指す別の変数を宣言することもできます。

    例えば:

    int a;

    int* b = &a;

    つまり、基本的には、各基本型ごとに、対応するポインター型があります。

    例:shortおよびshort*

    変数を「見る」には2つの方法がありますb (これが、おそらくほとんどの初心者を混乱させるものです)

    • bタイプの変数と見なすことができますint*

    • *bタイプの変数と見なすことができますint

    このため、一部の人々は宣言しint* b他の人が宣言し、一方int *b

    しかし、問題の事実は、これらの2つの宣言が同一であることです(スペースは無意味です)。

    b整数値へのポインタとして、または*b実際に指す整数値として使用できます。

    指摘された値を取得(読み取り)できますint c = *b

    また、ポイントされた値を設定(書き込み)できます*b = 5

  3. ポインタは、以前に宣言した変数のアドレスだけでなく、任意のメモリアドレスを指すことができます。ただし、ポイントされたメモリアドレスにある値を取得または設定するためにポインタを使用する場合は注意が必要です。

    例えば:

    int* a = (int*)0x8000000;

    ここではa、メモリアドレス0x8000000を指す変数があります。

    このメモリアドレスがプログラムのメモリスペース内にマップされていない場合、を使用した読み取りまたは書き込み操作*aにより、メモリアクセス違反のためにプログラムがクラッシュする可能性が高くなります。

    の値は安全に変更できますが、の値を変更するaときは十分に注意してください*a

  4. タイプvoid*は、使用できる対応する「値タイプ」がない(つまり、宣言できないvoid a)という点で例外的です。このタイプは、メモリアドレスへの一般的なポインタとしてのみ使用され、そのアドレスに存在するデータのタイプを指定しません。


7

たぶん、もう少しステップを踏むだけで簡単になります。

#include <stdio.h>

int main()
{
    int foo = 1;
    int *bar = &foo;
    printf("%i\n", foo);
    printf("%p\n", &foo);
    printf("%p\n", (void *)&foo);
    printf("%p\n", &bar);
    printf("%p\n", bar);
    printf("%i\n", *bar);
    return 0;
}

各行に出力されるはずの内容を説明してもらい、プログラムを実行して何が表示されるかを見てもらいます。彼らの質問を説明してください(そこにあるネイキッドバージョンは確かにいくつかのプロンプトを表示しますが、後でスタイル、厳密性、および移植性について心配することができます)。それから、彼らの考えが過剰な考えからドロドロになる前、または彼らが昼食後のゾンビになる前に、値を取る関数と、ポインターを取る同じ関数を書きます。

私の経験では、「なぜこれがそのように印刷されるのですか?」こぶ、それからすぐに、これが機能パラメーターで便利な理由を示す(ハンズオントイイング(文字列の解析/配列処理などの基本的なK&Rマテリアルへの前置きとして))。これは、レッスンが意味をなすだけでなく、こだわります。

次のステップは、彼らにあなたがどのようにi[0]関連して&iいるかを説明するようにさせることです。彼らがそれを行うことができれば、彼らはそれを忘れることはなく、少し前から構造体について話し始めることができます。

上記のボックスと矢印に関する推奨事項も適切ですが、記憶がどのように機能するかについての本格的な議論に抜け出すこともできます。 :Cでのポインタ表記の解釈方法


これは良い練習です。しかし、私が取り上げたかった問題は、生徒が構築するメンタルモデルに影響を与える可能性がある特定の構文問題です。これを考慮してください:int foo = 1;。これで問題ありませんint *bar; *bar = foo;。これはOKではありません:int *bar = foo;
armin

1
@abw理にかなっている唯一のことは、学生が自分自身に語りかけることです。それは、「見て、やって、教えて」という意味です。ジャングルの中でどのような構文やスタイルが表示されるのかを保護または予測することはできません(古いレポジトリでさえも!)次に、特定のスタイルが決定された理由を彼らに教え始めます。英語を教えるように:基本的な表現、イディオム、スタイル、特定の文脈における特定のスタイル。残念ながら、簡単ではありません。とにかく頑張ってね!
zxq9 2014

6

タイプの式が *barありますint。したがって、変数(および式) のタイプbarint *です。変数にはポインター型があるため、その初期化子にもポインター型が必要です。

ポインター変数の初期化と割り当ての間に矛盾があります。それは難しい方法で学ばなければならないものです。


3
ここで答えを見ると、経験豊富なプログラマーの多くはもう問題を見ることすらできないような気がします。それは「矛盾して生きることを学ぶ」の副産物だと思います。
Armin、2014

3
@abw:初期化のルールは割り当てのルールとは異なります。スカラー算術型の場合、違いはごくわずかですが、ポインター型と集約型の場合は重要です。それはあなたが他のすべてと一緒に説明する必要があるものです。
John Bode 14

5

以上に最初の*適用としてそれを読みたいintですbar

int  foo = 1;           // foo is an integer (int) with the value 1
int* bar = &foo;        // bar is a pointer on an integer (int*). it points on foo. 
                        // bar value is foo address
                        // *bar value is foo value = 1

printf("%p\n", &foo);   // print the address of foo
printf("%p\n", bar);    // print the address of foo
printf("%i\n", foo);    // print foo value
printf("%i\n", *bar);   // print foo value

2
次に、なぜint* a, b彼らが考えていることをしないのかを説明する必要があります。
Pharap、2014

4
本当ですが、それをint* a,b使うべきではないと思います。信頼性の向上、更新などのために、変数の宣言は1行に1つだけで、それ以上はありません。たとえコンパイラがそれを処理できるとしても、それは初心者にも説明するべきものです。
グローレル2014

それは一人の男の意見ですが。何百万人ものプログラマーがいて、1行に複数の変数を宣言して、仕事の一部として毎日それを実行することに完全に満足しています。生徒を別のやり方で隠すことはできません。生徒にすべての選択肢を見せ、どの方法でやりたいかを生徒に決めさせるのがよいでしょう。彼らは快適かもしれないし、そうでないかもしれません。プログラマにとって、汎用性は非常に優れた特性です。
Pharap 14

1
@grorelに同意します。*タイプの一部と考える方が簡単であり、単に思いとどまらせる方が簡単int* a, bです。あなたがそれ*aがタイプへのポインタintaはなくタイプであると言うのを好むのでない限りint...
Kevin Ushey

@grorelは正しい:int *a, b;使用しないでください。同じステートメントで異なるタイプの 2つの変数宣言することは、かなり貧弱なプラクティスであり、将来のメンテナンス問題の有力な候補です。おそらく、埋め込みフィールドで作業する私たちにとって、an int*とan intはしばしば異なるサイズであり、時には完全に異なるメモリ位置に格納される場合とは異なります。これはC言語の多くの側面の1つであり、「許可されていますが、実行しないでください」と最もよく教えられています。
Evil Dog Pie

5
int *bar = &foo;

Question 1:とはbar

Ans:これは(型へのint)ポインタ変数です。ポインターは、有効なメモリ位置を指している必要があり、後で*その位置に格納されている値を読み取るために、単項演算子を使用して逆参照(* bar)する必要があります。

Question 2:とは&foo

Ans:fooは型の変数ですint。これは有効なメモリロケーションに格納されており、そのロケーションはオペレータから取得する&ため、有効なメモリロケーションがあり&fooます。

つまり、両方を組み合わせると、ポインタに必要なのは有効なメモリ位置であり、それによって取得される&fooため、初期化は適切です。

今ポインタbarは有効なメモリ位置を指しており、そこに格納された値はそれを逆参照することで取得できます。*bar


5

宣言と式では*の意味が異なることを初心者に指摘する必要があります。ご存じのとおり、式の*は単項演算子であり、宣言の*は演算子ではなく、一種の構文と組み合わせて、コンパイラーにポインター型であることを知らせます。初心者は、「*には異なる意味があります。*の意味を理解するには、*が使用されている場所を見つける必要があります」と言う方が良いでしょう。


4

悪魔は宇宙にいると思います。

私は(初心者のためだけでなく、自分のためにも)次のように書きます:int * bar =&foo; int * bar =&foo;の代わりに

これにより、構文とセマンティクスの関係が明らかになります。


4

*には複数の役割があることはすでに述べました。

初心者が物事を把握するのに役立つかもしれない別の簡単なアイデアがあります:

「=」にも複数の役割があると考えてください。

割り当てが宣言と同じ行で使用される場合、それは任意の割り当てではなく、コンストラクター呼び出しと考えてください。

あなたが見るとき:

int *bar = &foo;

以下とほぼ同等であると考えてください。

int *bar(&foo);

括弧はアスタリスクよりも優先されるため、「&foo」は「* bar」ではなく「bar」に直感的に関連付けられます。


4

私は数日前にこの質問を見て、たまたまGoブログGoの型宣言の説明を読んでいました。Cの型宣言のアカウントを提供することから始めます。これは、このスレッドに追加するのに役立つリソースのようですが、すでにより完全な回答が提供されていると思います。

Cは、宣言構文に対して異常で巧妙なアプローチを採用しました。特別な構文で型を記述する代わりに、宣言されている項目を含む式を記述し、その式が持つ型を示します。したがって

int x;

xをintとして宣言します。式 'x'の型はintになります。一般に、新しい変数の型を記述する方法を理解するには、その変数を含み、基本型に評価される式を記述してから、左側に基本型、右側に式を配置します。

したがって、宣言

int *p;
int a[3];

'* p'にはint型があるため、pはintへのポインターであり、a [3](配列のサイズになるようにパンニングされている特定のインデックス値は無視)には型があるため、aはintの配列であることを示します。 int。

(この理解を関数ポインタなどに拡張する方法を説明します)

これは私がこれまで考えたことのない方法ですが、構文の過負荷を説明する非常に簡単な方法のようです。


3

問題が構文である場合は、テンプレート/使用で同等のコードを表示すると役立つ場合があります。

template<typename T>
using ptr = T*;

これは次のように使用できます

ptr<int> bar = &foo;

その後、通常の/ C構文をこのC ++のみのアプローチと比較します。これはconstポインタを説明するのにも役立ちます。


2
初心者にとっては、かなり混乱するでしょう。
Karsten 2014

私のは、あなたはptrの定義を示さないだろうということでした。ポインタ宣言に使用するだけです。
MI3Guy 2014

3

混乱の原因は*、Cで使用されている事実に応じて、Cではシンボルの意味が異なる可能性があることです。初心者へのポインタを説明するために、の意味*異なる文脈記号説明する必要があります。

宣言で

int *bar = &foo;  

*シンボルは、間接演算子ません。代わりに、barコンパイラへの通知のタイプを指定するのに役立ちます。これはへbarポインタintです。一方、それがステートメントに現れる場合、*記号(単項演算子として使用される場合)は間接参照を実行します。したがって、ステートメント

*bar = &foo;

それ自体foobar指すのではなく、指すオブジェクトにのアドレスを割り当てるため、これは誤りbarです。


3

「おそらく、それをint *バーとして記述すると、スターが実際には型の一部であり、識別子の一部ではないことがより明確になります。」私もです。そして私は、それはTypeのようなものですが、1つのポインター名に対してのみです。

「もちろん、これはint * a、bのような直感的でないものでさまざまな問題に遭遇します。」


2

ここでは、人間のロジックではなく、コンパイラロジックを使用、理解、説明する必要があります(私は知っていますあなたは人間ですが、ここではコンピュータを模倣する必要があります...)。

あなたが書くとき

int *bar = &foo;

コンパイラはそれを

{ int * } bar = &foo;

つまり、ここに新しい変数があり、その名前はbar、型はintへのポインタ、初期値は&fooです。

そして追加する必要があります:=上記は影響ではなく初期化を示しますが、以下の式*bar = 2;影響です

コメントごとに編集:

注意:複数の宣言の場合、これ*は次の変数にのみ関連しています:

int *bar = &foo, b = 2;

barはfooのアドレスによって初期化されたintへのポインタ、bは2に初期化されたint、そしてin

int *bar=&foo, **p = &bar;

bar in int intへのポインタ、pはアドレスまたはバーに初期化されたintへのポインタへのポインタです。


2
実際、コンパイラはそれをそのようにグループ化していませんint* a, b;。aをへのポインタとして宣言しますintが、bをとして宣言しますint*シンボルは、ちょうど2つの別個の意味を有する:宣言では、ポインタ型を示し、式の中では、単項逆参照演算子です。
tmlen 2014

@tmlen:私が意味したことは、初期化では*、型に割り当てられ、ポインタが初期化されるのに対し、actionactionでは、指定された値が影響を受けるということです。しかし、少なくともあなたは私に素敵な帽子をくれました:-)
セルジュバレスタ

0

基本的に、ポインタは配列を示すものではありません。初心者はポインタが配列のように見えると簡単に思います。ほとんどの文字列の例は

「char * pstr」のように見えます

「char str [80]」

しかし、重要なことは、ポインタはコンパイラの下位レベルでは単なる整数として扱われることです。

例を見てみましょう::

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv, char **env)
{
    char str[] = "This is Pointer examples!"; // if we assume str[] is located in 0x80001000 address

    char *pstr0 = str;   // or this will be using with
    // or
    char *pstr1 = &str[0];

    unsigned int straddr = (unsigned int)pstr0;

    printf("Pointer examples: pstr0 = %08x\n", pstr0);
    printf("Pointer examples: &str[0] = %08x\n", &str[0]);
    printf("Pointer examples: str = %08x\n", str);
    printf("Pointer examples: straddr = %08x\n", straddr);
    printf("Pointer examples: str[0] = %c\n", str[0]);

    return 0;
}

結果は次のようになります0x2a6b7ed0はstr []のアドレスです

~/work/test_c_code$ ./testptr
Pointer examples: pstr0 = 2a6b7ed0
Pointer examples: &str[0] = 2a6b7ed0
Pointer examples: str = 2a6b7ed0
Pointer examples: straddr = 2a6b7ed0
Pointer examples: str[0] = T

したがって、基本的に、ポインタはある種の整数であることに注意してください。アドレスを提示します。


-1

floatと同様に、intはオブジェクトであると説明します。ポインターは、値がメモリ内のアドレスを表すオブジェクトのタイプです(そのため、ポインターのデフォルトがNULLになっているのはなぜですか)。

ポインタを最初に宣言するときは、type-pointer-name構文を使用します。これは、「任意の整数オブジェクトのアドレスを指すことができる名前と呼ばれる整数ポインター」として読み取られます。incleを「int num1」として宣言するのと同様に、この構文はdecleration中にのみ使用しますが、「int num1」ではなく、その変数を使用する場合にのみ「num1」を使用します。

int x = 5; //値が5の整数オブジェクト

int * ptr; //デフォルトで値がNULLの整数

オブジェクトのアドレスを指すポインターを作成するには、「アドレス」として読み取ることができる「&」記号を使用します。

ptr =&x; //値は 'x'のアドレスになります

ポインタはオブジェクトのアドレスにすぎないため、そのアドレスに保持されている実際の値を取得するには、「*」記号を使用する必要があります。これは、ポインタの前に使用すると「指すアドレスの値」を意味します。

std :: cout << * ptr; //アドレスの値を出力します

' 'は、オブジェクトの種類によって異なる結果を返す '演算子'であると簡単に説明できます。ポインターと共に使用すると、 ' '演算子は「乗算」を意味しなくなります。

これは、変数に名前と値があり、ポインターにアドレス(名前)と値があることを示す図を描画し、ポインターの値がintのアドレスになることを示すのに役立ちます。


-1

ポインタは、アドレスを格納するために使用される単なる変数です。

コンピュータのメモリは、バイト(1バイトは8ビットで構成)が順番に並べられて構成されています。各バイトには、配列のインデックスまたは添え字と同様に、バイトのアドレスと呼ばれる番号が関連付けられています。バイトのアドレスは0から始まり、メモリのサイズより1つ小さくなります。たとえば、64MBのRAMに64 * 2 ^ 20 = 67108864バイトがあるとしましょう。したがって、これらのバイトのアドレスは0から67108863で始まります。

ここに画像の説明を入力してください

変数を宣言するとどうなるか見てみましょう。

intマーク;

私たちが知っているように、intは4バイトのデータを占有します(32ビットコンパイラを使用していると想定)、コンパイラは整数値を格納するためにメモリから4つの連続したバイトを予約します。割り当てられた4バイトの最初のバイトのアドレスは、変数マークのアドレスと呼ばれます。連続する4バイトのアドレスが5004、5005、5006、5007であるとすると、可変マークのアドレスは5004になります。 ここに画像の説明を入力してください

ポインター変数の宣言

すでに述べたように、ポインタはメモリアドレスを格納する変数です。他の変数と同様に、ポインター変数を使用するには、まずポインター変数を宣言する必要があります。以下は、ポインタ変数を宣言する方法です。

構文: data_type *pointer_name;

data_typeは、ポインターのタイプです(ポインターの基本タイプとも呼ばれます)。pointer_nameは変数の名前であり、任意の有効なC識別子にすることができます。

いくつかの例を見てみましょう:

int *ip;

float *fp;

int * ipは、ipがint型の変数を指すことができるポインター変数であることを意味します。つまり、ポインタ変数ipには、int型の変数のアドレスのみを格納できます。同様に、ポインター変数fpは、float型の変数のアドレスのみを格納できます。変数のタイプ(基本タイプとも呼ばれます)ipはintへのポインターで、fpのタイプはfloatへのポインターです。intへのポインタ型のポインタ変数は、記号で(int *)として表すことができます。同様に、floatへのポインター型のポインター変数は、(float *)として表すことができます。

ポインタ変数を宣言した後の次のステップは、有効なメモリアドレスを割り当てることです。有効なメモリアドレスを割り当てずにポインタ変数を使用しないでください。宣言の直後にガベージ値が含まれ、メモリ内の任意の場所を指している可能性があるためです。割り当てられていないポインタを使用すると、予期しない結果が生じる可能性があります。プログラムがクラッシュする可能性さえあります。

int *ip, i = 10;
float *fp, f = 12.2;

ip = &i;
fp = &f;

出典:thecguruは、私がこれまでに見つけた中で最も単純で詳細な説明です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.