C言語のアロー演算子の詳しい使い方

9, 2020-08-09

目次

アロー演算子とは?

アロー演算子とは、構造体などのポインタ変数から、構造体のメンバにアクセスするときに使う演算子です。
「アロー」は->で表現します。この矢印みたいなものが「矢」みたいに見えることから「アロー演算子」と呼ばれます。

本記事では最初におおざっぱな解説を行い、その後に詳しい解説を書きたいと思います。

おおざっぱな解説のために構造体struct animalを作ってみます。
struct animalは動物のデータのための構造体です。

これは

struct animal {
    int age;
};

↑のように作ります。
メンバ変数にはageという動物の年齢を表す変数を作っておきます。

このstruct animalとアロー演算子は例えば↓のように使います。

struct animal {
    int age;
};

int main(void) {
    struct animal cat = {0};  // 構造体変数catを作成
    struct animal *p = &cat;  // catのアドレスをポインタpに代入

    p->age = 10;  // ポインタpからアロー演算子でcatのメンバ変数にアクセス

    return 0;
}

catというstruct animal構造体の構造体変数を作ります。
そして、struct animalのポインタ変数pも作っておきます。
catのアドレスをポインタ変数pに代入します。
そしてポインタ変数pからアロー演算子でcatのメンバ変数age10を代入しています。

この記事では構造体についても簡単に解説します。
また、関連深い演算子にドット演算子もありますので、これとあわせて解説します。

構造体とはなんなのか?

そもそも構造体とはなんなのかという話です。
構造体とは、複数のデータをまとめて管理する構造のことを言います。

たとえばSNSのユーザーの構造体を考えてみましょう。
ユーザーにはユーザー名、メールアドレス、それから実名の名前、実名の名字、年齢、体重や身長などのデータがあります。
これらのデータを単純な変数で管理する場合はたとえば↓のようになるでしょう。

char username[100];
char email[100];
char firstname[100];
char lastname[100];
int age;
float weight;
float height;

これはこれで管理できていますが、しかしユーザーが増えた場合はどうでしょうか?
ユーザーが増えた場合は↑と同じような変数を続けて作らないといけません。
たとえばusername2とかemail2とかです。
なんかかっこわるいですよね。

こういう時に構造体を使うと、スッキリとかっこよくデータを管理することができます。
たとえば↑のデータを構造体にすると↓のようになります。

struct user {
    char username[100];
    char email[100];
    char firstname[100];
    char lastname[100];
    int age;
    float weight;
    float height;
};

このときuserが構造体名、usernameなどが構造体userのメンバ変数といいます。
あとは↑のstruct userを使えば複数のユーザーを簡単に作成することができます。
たとえば↓のようにです。

struct user user1;  // 構造体変数user1を作成
struct user user2;  // 構造体変数user2を作成

簡単でスッキリしていますね。

構造体はかっこいい

この構造体のメンバ変数にアクセスしたい場合はどうすればいいのでしょうか?

構造体のメンバ変数にアクセスする

構造体のメンバにアクセスする手段としてドット演算子があります。
これは構造体変数にドット(.)をつなげてメンバにアクセスします。

ドット演算子はポインタでない構造体変数で使われる演算子です。
役割的にはアロー演算子と同様に構造体変数のメンバ変数にアクセスしたい時に使われます。

名前的にはドット(.)なのでそのままですね。
アロー演算子といい昔の人はなかなかいいセンスをしています。

たとえばstruct animal構造体のメンバageに、値を代入したい場合は↓のようにします。

struct animal {
    int age;
};

int main(void) {
    struct animal cat = {0};  // 構造体変数catを作成

    cat.age = 10;  // ここでドット演算子でメンバ変数ageに10を代入

    return 0;
}

↑の場合、構造体変数catのメンバ変数ageに値10を代入しています。

これは構造体変数がポインタでない場合ですが、構造体変数がポインタになるとアクセス方法が変わります。
つまりポインタではドット演算子のかわりにアロー演算子を使います。

アロー演算子は構造体をポインタで扱う時に、メンバにアクセスする時に使います。
先ほどのドット演算子のコードを書き替えて、ポインタを使うようにすると↓のようなコードになります。

struct animal {
    int age;
};

int main(void) {
    struct animal cat = {0};  // 構造体変数catを作成
    struct animal *p = &cat;  // 構造体ポインタ変数*pにcatのアドレスを代入

    p->age = 10;  // ポインタ変数*pからアロー演算子でcatのメンバ変数ageにアクセス

    return 0;
}

ポインタ変数pには構造体変数catのアドレスが代入されています。
つまり、ポインタ変数pから構造体変数catのメンバにアクセスできることになります。
ポインタ変数pから構造体変数catのメンバageに値10を代入するには

p->age = 10;

のようにします。
この->の部分がアロー演算子です。
ドット(.)がアロー(->)に変わったわけですね。

ドット演算子のコードと、アロー演算子のコードでは、どちらもcat変数のメンバage10を代入する処理になっているので、結果は同じになります。

複数のアロー演算子

構造体にはメンバ変数にポインタ変数を含めることが出来ます
その場合、アロー演算子を複数繋げてアクセスすることが出来ます。

メンバ変数にポインタ変数を含める構造体とはたとえば↓のような構造です。

struct head {
    int eyes;
};

struct animal {
    struct head *atama;  // struct headの構造体ポインタ変数*atamaを宣言
};

↑のstruct animal構造体のメンバ変数にstruct head構造体のポインタ変数*atamaが宣言されています。
このように構造体の中には普通のポインタ変数や、異なる構造体のポインタ変数をもたせることができます。

↑のような構造体struct animalからstruct headのメンバ変数eyesにアクセスしたいとします。
その場合は複数のアロー演算子を経由してメンバ変数eyesにアクセスします。
たとえば↓のようなコードです。

struct head {
    int eyes;
};

struct animal {
    struct head *atama;
};

int main(void) {
    // 構造体headの構造体変数atamaを作成
    // メンバ変数eyesを2で初期化
    struct head atama = {2};

    // 構造体animalの構造体変数catを作成
    // メンバ変数*atamaを構造体変数atamaのアドレス値で初期化
    struct animal cat = {&atama};

    // 構造体変数catのアドレスを構造体のポインタ変数*pに代入
    struct animal *p = &cat;

    // 複数のアロー演算子を使って最終的に構造体headのメンバ変数eyesを初期化
    p->atama->eyes = 3;

    return 0;
}

少しわかりづらいですが、↑のコードではまずatama変数を定義しています。
そしてcat変数のメンバにatama変数のアドレスを入れています。
そのあとcat変数のアドレスをポインタ変数pに代入しています。

こうした場合、ポインタ変数pからatama変数のメンバeyesにアクセスするには

p->atama->eyes = 3;

のように書きます。
これはつまり、ポインタ変数を辿ってメンバ変数eyesにアクセスしてるわけですね。
これはポインタ変数の便利なところです。

アロー演算子を使えば、このようにポインタ変数から変数の実体、構造体のメンバ変数などにアクセスすることが可能です。
ポインタ変数が入れ子になっていても、アロー演算子で辿っていけばいつかはメンバ変数にアクセスできるということになりますね。

注意したい点として、途中のメンバ変数がNULLや不正なアドレス値になっていた場合は、プログラムが落ちる場合があります。

NULLとアロー演算子

ポインタ変数にはNULLを代入することができます。
ポインタ変数に詳しい人は知っていると思いますが、これは「NULLポインタ」と呼ばれます。

NULLポインタにアロー演算子でアクセスすると、プログラムが不正終了することがあります。
これはアロー演算子を扱う場合に付いて回る問題です。

ですのでポインタ変数がNULLでないかプログラム的にチェックするとか、そういう手間が必要になる場合もあります。
ロジック的にNULLになる可能性がない場合はチェックを省略する場合もありますが、基本的にはポインタ変数はNULLかどうかチェックしたほうが無難です。

ポインタ変数とアロー演算子を扱う場合はポインタ変数がNULLでないか念頭に置くようにしておきましょう。

アロー演算子はポインタの友達

アロー演算子はポインタの友達とも言える便利機能です。
思う存分使いまくってしごいてあげましょう!

最後におさらいです。
アロー演算子の使い方は、

struct animal {
    int age;
};

int main(void) {
    struct animal cat = {0};
    struct animal *p = &cat;

    p->age = 10;

    return 0;
}

です。
アロー(->)を覚えておいてください。