
C言語基礎|構造体とアロー演算子
「ポインタ先の構造体に、迷わず一発アクセス!ドットとアローで“読みやすいC”にしよう。」
構造体に慣れてくると、次に必ず出会うのが「関数に構造体を渡して、中身を書き換えたい」という場面です。
でも、構造体をそのまま渡すと“コピー”が作られてしまい、関数の中で変更しても元の値は変わりません。
そこで使うのが 構造体へのポインタ。
ポインタで渡せば、関数の中から元の構造体を直接更新できます。
そして、そのとき一緒に覚えておくと超ラクになるのが アロー演算子(->)です。
(*p).member のような書き方は、括弧が必要でミスしがち。
p->member を使うと、同じ意味を スッキリ安全に書けます。
この記事で扱うポイント(全体像)
| テーマ | 何が分かる? |
|---|---|
| 構造体ポインタ | 関数で“元の構造体”を更新できる理由 |
| (*p).mem | ポインタが指す構造体のメンバへアクセスする基本形 |
| 演算子の優先順位 | なぜ *p.mem がダメなのか |
| p->mem | (*p).mem を短く・読みやすく書く方法 |
| メンバアクセス演算子 | . と -> の役割の使い分け |

まずは基本:構造体のメンバアクセス(.)
書式
obj.mem
意味
- 構造体変数 obj の中のメンバ mem を表します。
例
item.price
ポインタが指す構造体のメンバへアクセスするには?
構造体へのポインタ p があるとき、p 自体は「住所」なので、そのままではメンバに触れません。
いったん *p で「指している実体」を取り出してから、. でメンバにアクセスします。
書式(基本形)
(*p).mem
意味
- p が指す構造体(*p)のメンバ mem を表します。
図で理解:(*p).mem の意味
例として、構造体 Device と、そのポインタ p を考えます。
図:ポインタ p が device を指す

図の説明
- p は device の先頭アドレスを持っています。
- *p は「p が指す実体」なので、*p は device と同じもの(エイリアス)として扱えます。
- だから、(*p).level は device.level と同じ場所を指します。
重要:*p.mem がダメな理由(優先順位)
ここが最初のつまずきポイントです。
書きたくなるけどダメ
*p.level
これは (p.level) のように解釈されてしまいます。
理由は、. のほうが * より優先順位が高いからです。
正しい書き方
(*p).level
アロー演算子(->)でスッキリ!
(*p).mem は括弧が必要で、書き忘れやすいです。
そこで、C言語には専用の短縮記法があります。それが **->(アロー演算子)**です。
表:アロー演算子の意味
| 表記 | 意味 | 同じ意味の書き方 |
|---|---|---|
| p->mem | p が指す構造体のメンバ mem | (*p).mem |
書式
p->mem
例で比較:同じ意味でも読みやすさが変わる
どちらも同じ意味
(*p).level = 5;
p->level = 5;
どっちが安全?
- p->level のほうが括弧が不要で、書き間違いを減らしやすい
- 読む人も「ポインタ先のメンバだな」と一瞬で分かります
サンプルプログラム
設定値が不正なら初期値に直すプログラム例です。
仕様(このプログラムでやること)
- 構造体 Device は id と level を持つ。
- level が 0 以下なら、関数で level を 1 に修正する
- 修正後の内容を表示する。
プロジェクト名:chap12-3-1 ソースファイル名:chap12-3-1.c
#include <stdio.h>
// 機器設定を表す構造体
struct Device {
int id; // 機器ID
int level; // レベル(1以上が正しい)
};
// d が指す Device の level が不正なら 1 に直す
void fix_level(struct Device *d)
{
if (d->level <= 0) {
d->level = 1;
}
}
int main(void)
{
struct Device dev = {101, 0}; // level が不正
fix_level(&dev);
printf("設定を確認しました。\n");
printf("機器ID:%d\n", dev.id);
printf("レベル:%d\n", dev.level);
return 0;
}このプログラムで登場する項目を丁寧に解説
struct Device { ... };
| 要素 | 何をする? |
|---|---|
| struct Device | Device というタグ名を持つ構造体型を定義する |
| int id; | 機器IDのメンバを定義する |
| int level; | レベルのメンバを定義する |
説明
これは「機器設定カードのフォーマット」を作っています。まだ実体は作っていません。
struct Device dev = {101, 0};
| 要素 | 何をする? |
|---|---|
| struct Device dev | Device 型の変数 dev を作る |
| {101, 0} | メンバを宣言順に初期化する(id=101, level=0)。 |
説明
ここでようやく「書き込めるカード(実体)」ができます。
fix_level(&dev);
| 要素 | 何をする? |
|---|---|
| &dev | dev のアドレスを取る(ポインタを作る) |
| fix_level(...) | 関数にポインタを渡して、元の dev を更新できるようにする |
説明
構造体を“コピー”で渡すのではなく、“住所”で渡すので、関数内の変更が dev に反映されます。
void fix_level(struct Device *d)
| 要素 | 何をする? |
|---|---|
| struct Device *d | Device 型へのポインタを受け取る(元の構造体を触れる) |
| void | 戻り値は返さない(更新が目的) |
説明
この関数は dev の中身を直す必要があるので、引数は struct Device ではなく struct Device * になります。
if (d->level <= 0) { d->level = 1; }
| 要素 | 何をする? |
|---|---|
| d->level | d が指す構造体の level メンバへアクセスする |
| <= 0 | 不正判定 |
| d->level = 1 | level を 1 に更新する(元の dev が更新される) |
説明
ここがこの記事の主役です。
d->level は (*d).level と同じ意味で、括弧ミスを防ぎつつ読みやすい表現です。
表:. と -> の使い分け(ここは暗記というより感覚でOK)
| 使いたい対象 | 使う演算子 | 例 |
|---|---|---|
| 構造体“そのもの”(変数) | . | dev.level |
| 構造体へのポインタ | -> | d->level |
図:-> は「ポインタ先のメンバ」だよ、のイメージ

図の説明
- d は dev の住所
- -> を使うと「住所の先にある構造体の中のメンバ」を直感的に書けます
まとめ(重要ポイント)
- 構造体を関数で更新したいなら、引数は 構造体ポインタ を使う。
- ポインタ先のメンバアクセスは (*p).mem が基本形
- ただし . のほうが * より強いので、*p.mem はダメ
- (*p).mem は p->mem と同じ意味で、-> のほうがミスが減って読みやすい。
- . と -> はまとめて「メンバアクセス演算子」と呼ばれる。
