C言語のアロー演算子の詳しい使い方
- 作成日: 2020-08-08
- 更新日: 2024-03-24
- カテゴリ: C言語
アロー演算子とは?
アロー演算子とは、構造体などのポインタ変数から、構造体のメンバにアクセスするときに使う演算子です。
「アロー」は->
で表現します。この矢印みたいなものが「矢」みたいに見えることから「アロー演算子」と呼ばれます。
本記事では最初におおざっぱな解説を行い、その後に詳しい解説を書きたいと思います。
おおざっぱな解説のために構造体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
のメンバ変数age
に10
を代入しています。
この記事では構造体についても簡単に解説します。
また、関連深い演算子にドット演算子もありますので、これとあわせて解説します。
C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。
関連記事
C言語の構造体のポインタの使い方
C言語で構造体を初期化する方法
C言語の構造体の配列の使い方
構造体とはなんなのか?
そもそも構造体とはなんなのかという話です。
構造体とは、複数のデータをまとめて管理する構造のことを言います。
たとえば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を作成
簡単でスッキリしていますね。
このように構造体を使うと構造を抽象化して表現することが可能です。
C言語では非常によく行われるので覚えておいた方が得でしょう。
🦝 < 構造体はかっこいい
この構造体のメンバ変数にアクセスしたい場合はどうすればいいのでしょうか?
関連記事
明快!C言語の構造体の使い方~私が構造体を愛する3つの理由~
C言語で構造体を初期化する方法
C言語の構造体のポインタの使い方
構造体のメンバ変数にアクセスする
構造体のメンバにアクセスする手段としてドット演算子があります。
これは構造体変数にドット(.
)をつなげてメンバにアクセスします。
ドット演算子はポインタでない構造体変数で使われる演算子です。
役割的にはアロー演算子と同様に構造体変数のメンバ変数にアクセスしたい時に使われます。
名前的にはドット(.
)なのでそのままですね。
アロー演算子といい昔の人はなかなかいいセンスをしています。
こういったネーミングセンスは見習いたいですね。
たとえば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
変数のメンバage
に10
を代入する処理になっているので、結果は同じになります。
結果は同じなわけですが、その手段が違うということですね。
プログラミングの世界では結果は同じだけど違う手段を複数提供するというのも一般的な話です。
関連記事
C言語の構造体のポインタの使い方
複数のアロー演算子
構造体にはメンバ変数にポインタ変数を含めることが出来ます。
その場合、アロー演算子を複数繋げてアクセスすることが出来ます。
数珠つなぎのように繋げるわけですね。
メンバ変数にポインタ変数を含める構造体とはたとえば↓のような構造です。
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
でないかプログラム的にチェックするとか、そういう手間が必要になる場合もあります。
if (ptr != NULL) {
ptr->age = 20;
}
ロジック的にNULL
になる可能性がない場合はチェックを省略する場合もありますが、基本的にはポインタ変数はNULL
かどうかチェックしたほうが無難です。
ポインタ変数とアロー演算子を扱う場合はポインタ変数がNULL
でないか念頭に置くようにしておきましょう。
関連記事
C言語の終端を表すEOF, NULL, ナル文字について
アロー演算子でよく起こるバグ
アロー演算子でよく起こるバグと言えばやはり不正なメモリへのアクセスです。
NULLポインタへのアクセスならまだかわいいもんです。
しかし解放済みのアドレスとかそういうものになってくると厄介です。
ここまでくるとメモリチェックツールなどを使わないと解決が難しくなります。
有名なメモリチェックツールにはValgrindなどがあります。
こういったツールを活用してC言語のコードのバグを見つけましょう。
- ツールを活用しよう
ツールを使うといかにC言語がバグを発生させやすい言語かわかると思います。
アロー演算子はポインタの友達
アロー演算子はポインタの友達とも言える便利機能です。
思う存分使いまくってしごいてあげましょう!
最後におさらいです。
アロー演算子の使い方は、
struct animal {
int age;
};
int main(void) {
struct animal cat = {0};
struct animal *p = &cat;
p->age = 10;
return 0;
}
です。
アロー(->
)を覚えておいてください。