C言語のアロー演算子(->)を分かりやすく、そして深く解説

このページにはプロモーションが含まれています

C言語プログラムで度々見かける「->」。これアロー演算子と言います。このページでは、このアロー演算子の意味、「*」「.」「->」の関係性、使い方をわかりやすく、そして深く解説していきたいと思います。

アロー演算子とは

アロー演算子とは「->」のことです。ポインタが指す構造体(クラス)のメンバへアクセスするために使用します。例えば下記のように記述することで、構造体のポインタpdからメンバaにアクセスすることができます。

pd->a;

アロー演算子の左側は構造体のポインタである必要があります。構造体だとしてもポインタでなければコンパイルエラーです。

でも、ポインタを習った時に、ポインタが指すデータへのアクセスには「*」を使うって教えてもらいましたよね?なぜ構造体の時だけポインタなのにアロー演算子を使うのでしょうか?実際のところアロー演算子ってどんな動きをする演算子なのでしょうか?この辺りを下記で深掘りしていきたいと思います。

アロー演算子「->」と「*」「.」との関係

続いて「*」「.」「->」の関係について解説します。これが分かるとアロー演算子がどういうものかがすっきり分かると思います。

スポンサーリンク

ポインタの指すデータへのアクセスには「*」を使う

まずはおさらいで、ポインタの指すデータへのアクセス方法について考えましょう。ポインタについては下のページで解説していますが、要はポインタ自体はアドレスを格納する矢印のようなものです。

ポインタの解説ページアイキャッチ 【C言語】ポインタを初心者向けに分かりやすく解説

そして、そのアドレス(矢印の先)にある値(データ)へアクセス(代入や参照)するためには、「*」を使います。

「*」の使い方は下記の通りです。

*ポインタ型変数

ポインタと「*」の関係を確認するためのプログラムは、例えば下記のようになります。

#include <stdio.h>
  
int main(void){
    int a;
    int *pa;

    pa = &a;
    a = 100;

    printf("pa = %p\n", pa);
    printf("*pa = %d\n", *pa);

    return 0;
}

実行結果は下記の通りになりました。

pa = 0x7ffeed2a6ae8
*pa = 100

ポインタ pa はそのままだと単なるアドレスですが、*pa のように「*」を用いることで pa ポインタの指す領域のデータにアクセスすることができます。

ポインタの指す先のデータにアクセスする様子

構造体のメンバへのアクセスには「.」を使う

C言語では構造体の各メンバに「.」を用いてアクセスすることができます。

「.」の使い方は下記の通りです。

構造体型変数.メンバ名

構造体と「.」の関係を確認するためのプログラムは、例えば下記のようになります。

#include <stdio.h>
  
struct data {
   int x;
   int y;
};

int main(void){
    struct data d;

    d.x = 1;
    d.y = 2;

    printf("d.x = %d\n", d.x);
    printf("d.y = %d\n", d.y);

    return 0;
}

実行結果については省略しますが、data 構造体型の変数 d のメンバ x、メンバ y にアクセスするために「.」を使用していることが確認していただけると思います。

構造体の各メンバにアクセスする様子

ポインタが指す構造体のメンバへのアクセスには「*」と「.」を使う

ポインタが指す構造体のメンバには下記の2つによりアクセスすることが可能です。

  • ポインタが指す構造体へアクセス(「*」を使用)
  • 構造体のメンバへアクセス(「.」を使用)

「*」はポインタが指す先のデータへアクセスするための演算子であり、そのデータが構造体であっても同様に使うことが可能です。ですので、int型などと同様に、ポインタが指す構造体へのアクセスは

*構造体ポインタ型変数

で行うことができます。さらに、メンバも通常通り「.」を使うことでアクセスできます。したがってポインタが指す構造体のメンバは下記によりアクセスすることができます。

(*構造体ポインタ型変数).メンバ名

括弧をつけたのは、演算順序の優先順位のためです。

下記のように括弧なしで記述するとコンパイルエラーになります。

*構造体ポインタ型変数.メンバ名

実際にポインタが指す構造体のメンバへアクセスするプログラムの例は下記の通りです。

#include <stdio.h>
  
struct data {
   int x;
   int y;
   int *z;
};

int main(void){
    struct data d;
    struct data *pd;
    int a;

    a= 3;

    d.x = 1;
    d.y = 2;
    d.z = &a;

    pd = &d;

    printf("d.x = %d\n", (*pd).x);
    printf("d.y = %d\n", (*pd).y);
    printf("*(d.z) = %d\n", *((*pd).z));

    return 0;
}

実行結果は下記のようになります。

d.x = 1
d.y = 2
*(d.z) = 3

ポインタ変数 pd で struct data 型の変数 d を指しておき、このポインタ変数 pd から「.」を用いて構造体の各メンバにアクセスしています。メンバ z に関してはポインタ型ですので、最後の printf 関数では、「ポインタで指した先の構造体」のポインタのメンバにアクセスしていることになります。ちょっとややこしいですが、

(*構造体ポインタ型変数).メンバ名

により、ポインタから構造体のメンバにアクセスし、各メンバの値を取得できていることが確認できると思います。

ポインタの指す構造体のメンバへアクセスする様子

でも、上のプログラム、すごく書きにくいし読みにくいですよね…。

特に構造体のメンバにポインタがあるとアクセスするのに括弧や「*」が複数あって非常に読みにくいです。この構造体のポインタを用いた時のプログラムの書きにくさ、読みにくさを解決してくれるのが、アロー演算子「->」なのです!!

スポンサーリンク

アロー演算子「->」は「*」と「.」を一つにまとめた演算子

アロー演算子「->」とはまさに、ここまで説明してきた、ポインタから構造体のメンバへアクセスする演算子です。

使用方法は下記のように変数名とメンバ名の間に「->」を入れ込む形になります

構造体ポインタ型変数->メンバ名

実は、前のプログラムで用いた(*構造体ポインタ型変数).メンバ名とアロー演算子を用いた構造体ポインタ型変数->メンバ名は全く同じ動作をします。

なので、今まで解説してきた「*」と「.」による動作をアロー演算子「->」一つだけで実現することができますし、括弧の数も減らせますので、アロー演算子を用いることでプログラムも書きやすくプログラムも直感的に読めるようになります。先ほどのプログラムをアロー演算子を用いたプログラムに書き直してみましょう。

#include <stdio.h>
  
struct data {
   int x;
   int y;
};

int main(void){
    struct data d;
    struct data *pd;

    pd = &d;
    pd->x = 1;
    pd->y = 2;

    printf("d.x = %d\n", pd->x);
    printf("d.y = %d\n", pd->y);
    printf("*(d.z) = %d\n", *(pd->z));

    return 0;
}

最後の printf 関数のところを一つ上のプログラムと比べてみてください。かなりスッキリしていることが分かると思います。

実行結果は下記です。この結果からも、アロー演算子「->」が「*」と「.」を用いた時と同じ動きをしているのが確認できると思います。

d.x = 1
d.y = 2
*(d.z) = 3

アロー演算子によりポインタの指す構造体のメンバに直接アクセスするイメージですね。

アロー演算子を用いてポインタの指す構造体のメンバへアクセスする様子

構造体のポインタを習ったときに、いきなりアロー演算子という新しい演算子が出てきて戸惑った方もいるかと思いますが、構造体のポインタにおいても基本的な考え方は今まで通りです。

つまりポインタの指すデータにアクセスするときは「*」を使用し、構造体のメンバへアクセスするときは「.」を使用するです。

ただプログラムの書きやすさや読みやすさのために、簡潔に一つの演算子で記述できるアロー演算子「->」を用いることが推奨されているというだけです。この辺りを理解していると頭の中がスッキリすると思います。

アロー演算子の使い方

構造体のメンバにアクセスする場合に「.」を用いるか「->」を用いるかで迷うこともあると思います。私もよく迷います。そんなときは下記でどちらを使えば良いかを判断すれば良いです。

演算子の左側の変数がポインタであるかどうか

演算子の左側の変数がポインタである場合は「->」を用いれば良いですし、演算子の左側の変数がポインタでない(構造体データの実体である)場合は「.」を用いれば良いです。

下のソースコードでは d がポインタではなく構造体データの実体ですので「.」を用います。pd はポインタですので「->」を用いていますが、(*pd) はポインタの指す先のデータ、つまり構造体の実体ですので「.」を用います。

#include <stdio.h>
  
struct data {
   int x;
   int y;
};

int main(void){
    struct data d;
    struct data *pd;
    pd = &d;

    /* d はポインタではない */
    d.x = 1;
    d.y = 2;

    /* pd はポインタ */
    pd->x = 3;
    pd->y = 4;

    /* *pd はポインタでない */
    (*pd).x = 5;
    (*pd).y = 6;

    return 0;
}

アロー演算子を使いこなす

いろいろなプログラムを見てアロー演算子の理解を深め、アロー演算子を使いこなせるようになっていきましょう!

まずは下記プログラムです。

#include <stdio.h>
  
struct data {
   int x;
   int y;
};

int main(void){
    struct data d;

    d->x = 1;

    printf("d.x = %d\n", d.x);

    return 0;
}

このプログラムはコンパイルエラーになります。なぜなら d はポインタではないからです。基本ですね。ポインタでない変数に「*」を付けるのと同じようなものです。

下記のプログラムではコンパイラが通り、上手く動作してくれます。

#include <stdio.h>
  
struct data {
   int x;
   int y;
};

int main(void){
    struct data d;

    (&d)->x = 1;

    printf("d.x = %d\n", d.x);

    return 0;
}

なぜコンパイルが成功するか分かりますか?

「&」はその変数のアドレスを取得するための演算子です。なので、&d は構造体のポインタと同様に扱われ、上記のプログラムではコンパイルが成功します。

次は構造体のメンバに他の構造体が含まれる場合のプログラムです。

#include <stdio.h>
  
struct memb {
   int m;
};

struct data {
   struct memb x;
   struct memb *y;
};

int main(void){
    struct data d;
    struct data *pd;

    pd = &d;

    d.x.m = 1;
    d.y->m = 2;

    pd->x.m = 3;
    pd->y->m = 4;

    return 0;
}

pd->y->m のようにアロー演算子を複数回連続で使用することも可能です

スポンサーリンク

まとめ

  • アロー演算子とは、ポインタから構造体のメンバへアクセスするための演算子
  • (*構造体ポインタ型変数).メンバ名 = 構造体ポインタ型変数->メンバ名
  • 可読性を高めるためにもアロー演算子を活用した方が良い

オススメの参考書(PR)

C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!

まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。

  • 参考書によって、解説の仕方は異なる
  • 読み手によって、理解しやすい解説の仕方は異なる

ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?

それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。

なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。

特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。

もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!

入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。

https://daeudaeu.com/c_reference_book/

同じカテゴリのページ一覧を表示

2 COMMENTS

Tsubasa Hamada

画像の中の
(*(*pd).z)は本来は
*((*pd).z)ではないですか??

daeu

Tsubasa Hamada さん

コメントありがとうございます!

おしゃる通りで、*((*pd).z) の方が、そこまでの説明とも辻褄が合っていて正しいです。
なので、画像修正させていただきました。誤解を招くような画像になってしまっていて申し訳ございません。
また、ご指摘していただいて大変助かりました。ありがとうございます!

現在コメントは受け付けておりません。