閉じる

【C言語】構造体ポインタの-> アロー演算子とは?使い方と例

C言語で構造体をポインタ経由で扱うときに登場するのが「->(アロー演算子)」です。

ドット演算子(.)との使い分けに最初は戸惑いがちですが、仕組みはとてもシンプルです。

本記事では、アロー演算子の基本と書き方、ドット演算子との違い、実践的なコード例、そしてよくある間違いを丁寧に解説します。

構造体ポインタと->(アロー演算子)とは

構造体ポインタのメンバーにアクセスする演算子

構造体のメンバーにアクセスするには通常ドット演算子を使いますが、構造体の「ポインタ」からメンバーにアクセスする場合はアロー演算子(->)を使います

例えば、Person *pが構造体Personを指すとき、p->ageのように書いてメンバーへアクセスします。

アロー演算子は「ポインタを間接参照して、その結果のメンバーにアクセスする」ことを1つにまとめた記法です。

機能的には(*p).ageと同じ意味ですが、p->ageのほうが簡潔で読みやすいです。

p->member と (*p).member は同じ意味

p->member(p).memberは等価です。

アロー演算子は「間接参照()とメンバーアクセス(.)」を合成した糖衣構文(syntactic sugar)です。

演算子の優先順位の都合で(*p).memberのように括弧が必要になります。

括弧を省くと別の意味に解釈されるため注意してください。

-> の使い方と.ドット演算子の違い

基本構文 p->member

基本は次の形です。

  • pointer_to_struct->member

たとえばPerson *pPersonを指すなら、p->namep->ageのように書けます。

左辺が「ポインタ」であることが重要です。

変数には . 、ポインタには ->

実体(値そのもの)にはドット、ポインタにはアローというルールで覚えると混乱しにくくなります。

以下に使い分けを表で整理します。

対象使う演算子説明
構造体の実体(変数).s.agesstruct変数であるとき
構造体のポインタ->p->agepstruct *のとき
ポインタの先の配列メンバー-> + 添字p->arr[i]配列メンバーarrにアクセス
ネストしたポインタ->の連鎖p->child->namechildもポインタのとき

(&s)->member の書き方

構造体変数sのアドレスを取ってすぐアローでアクセスしたい場合は(&s)->memberと書きます。

括弧を省いて&s->memberとすると&(s->member)に解釈される(演算子優先順位のため)ので、必ず括弧で&sを囲むようにしてください。

初心者向けコード例

構造体の宣言とポインタ取得

以下の例では、親子のPerson構造体を用意し、ポインタでアクセスします。

配列メンバーscoresも用意し、p->scores[i]の形も合わせて示します。

C言語
#include <stdio.h>
#include <string.h>

// Person 構造体の定義
typedef struct Person {
    char name[32];
    int age;
    struct Person *child; // 子どもを指すポインタ(なければ NULL)
    int scores[3];        // 配列メンバーの例
} Person;

// NULL安全に表示する補助関数
void print_person(const Person *p) {
    if (p == NULL) {
        puts("[print_person] person は NULL です");
        return;
    }
    printf("Person{name=\"%s\", age=%d}\n", p->name, p->age);
}

int main(void) {
    // 子の実体(構造体変数)
    Person child = {"Taro", 10, NULL, {80, 85, 90}};

    // 親の実体(構造体変数)。あとでポインタ経由で埋める
    Person parent;

    // 親を指すポインタ p を取得
    Person *p = &parent;

    // ここから p->member の形でメンバーを書き込み(代入)
    // 文字列の代入は snprintf を使うと安全
    snprintf(p->name, sizeof(p->name), "%s", "Hanako");
    p->age = 35;
    p->child = &child; // 連鎖アクセスのために子へのポインタをセット

    // 配列メンバー scores への書き込み
    p->scores[0] = 70;
    p->scores[1] = 75;
    p->scores[2] = 80;

    // ここから読み出し(アクセス)
    printf("親: %s (%d歳)\n", p->name, p->age);

    // ドット演算子とアロー演算子の比較
    printf("ドット: parent.age=%d, アロー: p->age=%d\n", parent.age, p->age);

    // (*p).member と p->member は同じ
    printf("(*p).age=%d, p->age=%d\n", (*p).age, p->age);

    // (&s)->member の例(括弧が必要)
    printf("(&parent)->age=%d\n", (&parent)->age);

    // 連鎖アクセス: p->child->name
    printf("子: %s (%d歳)\n", p->child->name, p->child->age);

    // 配列メンバーのアクセスと出力
    printf("親の scores: %d, %d, %d\n", p->scores[0], p->scores[1], p->scores[2]);

    // NULL ポインタを扱うときはチェックしてから
    print_person(&parent);
    print_person(NULL); // これは安全に何もしない

    return 0;
}

実行結果の一例は次のようになります。

実行結果
親: Hanako (35歳)
ドット: parent.age=35, アロー: p->age=35
(*p).age=35, p->age=35
(&parent)->age=35
子: Taro (10歳)
親の scores: 70, 75, 80
Person{name="Hanako", age=35}
[print_person] person は NULL です

p->member で値を読み書き

上のコードのとおり、p->namep->ageと書くことで、代入と参照の両方ができます

文字列は配列なのでstrcpysnprintfなどを使って代入し、整数は通常の代入で問題ありません。

p->child->name の連鎖アクセス

child自体がPerson *であるため、p->child->nameのように-> を連鎖して辿ることができます。

途中のどのポインタもNULLでないことの確認が重要です。

配列メンバー p->arr[i] のアクセス

配列メンバーscoresへのアクセスはp->scores[i]の形で行います。

これは(*p).scores[i]と同じ意味です。

添字の範囲外アクセスは未定義動作になるため、配列サイズに注意してください。

よくある間違いと注意点

*p.member は誤り

*p.memberは誤りです。

演算子の優先順位によりp.memberが先に解釈されますが、pはポインタなのでp.member自体が不正です。

正しくは(*p).memberまたはp->memberです。

C言語
// 誤りの例(コンパイルエラー):
// *p.age = 20;  // '.' が先に結合し p.age を試みるため不正

// 正しい書き方:
(*p).age = 20;
p->age = 20; // こちらが推奨(簡潔)

NULL ポインタに対する -> は使わない

NULLポインタに対して->でアクセスすると未定義動作です。

多くの場合、実行時エラー(セグメンテーション違反)になります。

必ず NULL チェックを行うか、関数の契約として「NULL不可」を明記してください。

C言語
void safe_set_age(Person *p, int age) {
    if (p == NULL) {
        // ここで早期リターンするか、エラーを報告する
        return;
    }
    p->age = age; // p が非 NULL であれば安全
}

ドット演算子との混同に注意

「実体にはドット、ポインタにはアロー」というルールを常に意識します。

途中でアドレス演算子&を使ったり、ポインタを渡したりすると、どちらを使うべきか見失いがちです。

たとえばsが実体ならs.age&sはポインタなので(&s)->ageになります。

括弧の有無にも注意してください。

状況正しい例間違いやすい例ポイント
実体にアクセスs.ages->agesはポインタではない
ポインタにアクセスp->agep.agepはポインタ
アドレスを取ってすぐアクセス(&s)->age&s->age&より->が優先されるため括弧が必要

迷ったら「その左側はポインタか実体か」を確かめると間違いを避けられます。

まとめ

本記事では、構造体へのポインタに用いる ->(アロー演算子) の基本を解説しました。

ポイントは次のとおりです。

アローはポインタからメンバーへアクセスするための記法で、p->m(p).mと同じ意味です。

実体には . 、ポインタには ->という使い分けを覚え、NULLポインタに対して -> を使わないこと、p.memberのような誤用をしないことに注意してください。

配列メンバーやネストしたポインタでもp->arr[i]p->child->nameのように素直に書けます。

基本を正しく押さえれば、構造体とポインタのコードはぐっと読みやすく、安全になります。

C言語 構造体・共用体・列挙型
この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!