Cでの矢印演算子(->)の使用法


257

「Teach Yourself C in 21 Days」という本を読んでいます(すでにJavaとC#を習得しているので、ずっと速いペースで動いています)。ポインタの章を読んでいたところ、->(矢印)演算子が説明なしに表示されました。メンバーと関数を呼び出すために使用されていると思います.((ドット)演算子と同等ですが、メンバーではなくポインター用です)。しかし、私は完全にはわかりません。

説明とコードサンプルを入手できますか?


90
より良い本を入手してください。norvig.com/21-days.html
joshperry 2010

9
qrdlは正解です。「Y日間でXを学ぶ」の本は一般にゴミです。K&Rに加えて、K&Rよりもさらに奥行きのあるプラタの「C Primer Plus」もオススメします。
J.テイラー

3
@Steveその質問はC ++を扱っています。私はC.に関連性がないこと、他の答えは、で演算子オーバーロードについて読み始めたとき、私のためにいくつかの混乱を引き起こし、それを呼び出す
ヨハン

1
@Belton難しい方法シリーズは悪いです、彼は彼が本を書いたとき関連さえしなかったものを言って、彼は良い習慣について気にしません。
バリント

1
Peter Norvigの「Teach Yourself Programming in 10 Years」へのリンクは、私のお気に入りの1つです。これは21日間でこれを行う方法を説明するコミックバージョンです。残念ながらXKCDとして覚えていましたが、間違っていました。abstrusegoose.com/ 249
Ted

回答:


462

foo->barはと同じです。つまり、指す構造体から(*foo).bar呼び出されるメンバーを取得します。barfoo


51
逆参照演算子がPascalのように後置で作成され->ていた場合は、はるかに読みやすいと同等であるため、演算子はまったく必要ありませんでしたfoo*.bar。余分な括弧をすべて含むtypedef-ing関数の全体的な混乱も回避されます。
ローンの侯爵2015年

1
それでfoo*.bar(*foo).bar両方とも同等foo->barですか?どうFoo myFoo = *foo; myFoo.barですか?
アーロンフランケ

9
いいえ、彼は言っているのIF Cのクリエイターはそれがより簡単だっただろう、代わりに接頭辞の後置演算子として間接参照演算子を作っただろう。しかし、これはCの接頭辞演算子です
reichhart

@ user207421 Coulfdあなたはあなたが言及する「余分な括弧をすべて持つtypedef-ing関数」への短い説明またはリンクを提供してください?ありがとう。
RoG、

1
@ user207421いや、それはより多くの親を引き起こします..今のところ、()と[]の優先順位は、右上*左にあります。彼らがすべて片側にいる場合、あなたはより多くの親を置くでしょう。乗算演算子と競合するため、式でも同じです。Pascal ^はオプションになる可能性がありますが、ビット操作用に予約されていて、さらに多くの親がいます。
Swift-

129

はい、それだけです。

参照ではなくポインタである構造体/クラスの要素にアクセスする場合、それは単なるドットバージョンです。

struct foo
{
  int x;
  float y;
};

struct foo var;
struct foo* pvar;
pvar = malloc(sizeof(pvar));

var.x = 5;
(&var)->y = 14.3;
pvar->y = 22.4;
(*pvar).x = 6;

それでおしまい!


3
pvarが初期化されていないので、pvarに新しい構造体を指すようにしたい場合、どのように初期化しますpvar = &varか?
CMCDragonkai 2015

問題は特にクラスや参照変数を持たないCに関するものでした。
オーク

1
hmmは、pvar struct foo * pvarに書き込む前にmallocを実行すべきではありません。?? pvar-> y未割り当てスペースに書き込みます!
ジブリ

pvar初期化:必要なデフォルトにすべてのメンバーを手動で初期化するか、ゼロの充填で十分な場合はcalloc()などを使用します。
ライハート

2
それはすべきではありません:pvar = malloc(sizeof(struct foo))またはmalloc(sizeof(* pvar))??
Yuri Aps


28

答えに「なぜ?」を追加します。

.は、*ポインター演算子よりも優先順位が高い標準のメンバーアクセス演算子です。

構造体の内部にアクセスしようとしていて*foo.bar、それを書き込んだとき、コンパイラは「foo」(メモリ内のアドレス)の「bar」要素が必要だと考え、明らかに、単なるアドレスにはメンバーがありません。

したがって、まずwhith (*foo)を逆参照し、次にメンバー要素にアクセスするようコンパイラーに要求する必要があります(*foo).bar。これは書くのが少し扱いにくいので、優れた人々は簡略版を考え出しました。foo->barこれはポインター演算子によるメンバーアクセスの一種です。



10
struct Node {
    int i;
    int j;
};
struct Node a, *p = &a;

ここでの値にアクセスするij、私たちは、変数を使用することができますaし、ポインタをp次のようにa.i(*p).ip->iすべて同じです。

これ.が「直接セレクター」と->「間接セレクター」です。


2

まあ私も何かを追加する必要があります。配列はポインタであり、構造はそうではないため、構造は配列とは少し異なります。ので注意してください!

私がこの役に立たないコードを書いたとしましょう:

#include <stdio.h>

typedef struct{
        int km;
        int kph;
        int kg;
    } car;

int main(void){

    car audi = {12000, 230, 760};
    car *ptr = &audi;

}

ここで、ポインターは構造体変数ptrのアドレス()を指していますaudiが、アドレス構造体の横にもデータのチャンク)があります。データチャンクの最初のメンバーは構造体自体と同じアドレスを持ち、このようなポインターを逆参照するだけでデータを取得できます*ptr (中括弧は不要)

あなたが最初のものよりも他のメンバーをアセスしたい場合しかし、あなたは次のように指示を追加する必要があり.km.kph.kgより多くのベースアドレスにオフセットよりも何もないそのデータのチャンク ...

ただし、優先順位が高いため、*ptr.kgアクセス演算子.が逆参照演算子よりも先に評価されるため、書き込むことはできません。ポインタにはメンバーがない*ため、*(ptr.kg)これは不可能です。そしてコンパイラはこれを知っているため、エラーを発行します。例:

error: ptr is a pointer; did you mean to use ‘->’?
  printf("%d\n", *ptr.km);

代わりに、これを使用する(*ptr).kgと、あなたはにコンパイラを強制する第一のポインタデリファレンスとにアセスを可能にするデータのチャンク第二メンバーを選択するためにあなたがオフセット(指示)を追加します。

私が作ったこの画像を確認してください:

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

ただし、メンバーをネストすると、この構文は判読できなくなり、->導入されました。私は読みやすさは、このようにそれを使用するための唯一の正当な理由だと思うptr->kgよりも書き込みにはるかに簡単です(*ptr).kg

接続をより明確に理解できるように、これを別の方法で記述しましょう。(*ptr).kg(*&audi).kgaudi.kg。ここで私は最初、実際に使用ptrされる「のアドレスaudi、すなわち&audiと事実「参照」 &「間接参照」 *事業者がお互いアウトをキャンセルします。


1

実行するには、ジャックのプログラムを少し変更する必要がありました。構造体ポインタpvarを宣言した後、それをvarのアドレスにポイントします。この解決策は、Stephen Kochanのプログラミングin Cの242ページで見つかりました。

#include <stdio.h>

int main()
{
  struct foo
  {
    int x;
    float y;
  };

  struct foo var;
  struct foo* pvar;
  pvar = &var;

  var.x = 5;
  (&var)->y = 14.3;
  printf("%i - %.02f\n", var.x, (&var)->y);
  pvar->x = 6;
  pvar->y = 22.4;
  printf("%i - %.02f\n", pvar->x, pvar->y);
  return 0;
}

これをvimで次のコマンドを使用して実行します。

:!gcc -o var var.c && ./var

出力されます:

5 - 14.30
6 - 22.40

3
vimのヒント:%現在のファイル名を表すために使用します。このように:!gcc % && ./a.out
jibberia

1
#include<stdio.h>

int main()
{
    struct foo
    {
        int x;
        float y;
    } var1;
    struct foo var;
    struct foo* pvar;

    pvar = &var1;
    /* if pvar = &var; it directly 
       takes values stored in var, and if give  
       new > values like pvar->x = 6; pvar->y = 22.4; 
       it modifies the values of var  
       object..so better to give new reference. */
    var.x = 5;
    (&var)->y = 14.3;
    printf("%i - %.02f\n", var.x, (&var)->y);

    pvar->x = 6;
    pvar->y = 22.4;
    printf("%i - %.02f\n", pvar->x, pvar->y);

    return 0;
}

1

->オペレータは、よりコードが読みやすくなり*、いくつかの状況でオペレータ。

例:(EDK IIプロジェクトから引用)

typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_READ)(
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN UINT32                         MediaId,
  IN EFI_LBA                        Lba,
  IN UINTN                          BufferSize,
  OUT VOID                          *Buffer
  );


struct _EFI_BLOCK_IO_PROTOCOL {
  ///
  /// The revision to which the block IO interface adheres. All future
  /// revisions must be backwards compatible. If a future version is not
  /// back wards compatible, it is not the same GUID.
  ///
  UINT64              Revision;
  ///
  /// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.
  ///
  EFI_BLOCK_IO_MEDIA  *Media;

  EFI_BLOCK_RESET     Reset;
  EFI_BLOCK_READ      ReadBlocks;
  EFI_BLOCK_WRITE     WriteBlocks;
  EFI_BLOCK_FLUSH     FlushBlocks;

};

_EFI_BLOCK_IO_PROTOCOL構造体は、4人の関数ポインタメンバーが含まれています。

変数がありstruct _EFI_BLOCK_IO_PROTOCOL * pStruct、古き良き*演算子を使用してそのメンバー関数のポインターを呼び出すとします。次のようなコードになります。

(*pStruct).ReadBlocks(...arguments...)

しかし、->演算子を使用すると、次のように書くことができます。

pStruct->ReadBlocks(...arguments...)

どちらが良く見えますか?


1
90年代のAOLチャットで10代の若者が入力するように、すべて大文字にしないとコードが読みやすくなると思います。
2018年

1
#include<stdio.h>
struct examp{
int number;
};
struct examp a,*b=&a;`enter code here`
main()
{
a.number=5;
/* a.number,b->number,(*b).number produces same output. b->number is mostly used in linked list*/
   printf("%d \n %d \n %d",a.number,b->number,(*b).number);
}

出力は5 5 5


0

ドットは間接参照演算子であり、構造の特定のレコードの構造変数を接続するために使用されます。例:

struct student
    {
      int s.no;
      Char name [];
      int age;
    } s1,s2;

main()
    {
      s1.name;
      s2.name;
    }

このようにして、ドット演算子を使用して構造体変数にアクセスできます


6
これはどのような価値をもたらしますか?この例は、実際にそれを比較する他の回答と比較すると少し劣ってい->ます。また、この質問は4.5年前から回答されています。
EWit 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.