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 のようにアロー演算子を複数回連続で使用することも可能です

まとめ

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

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です