C言語の構造体は、複数の関連する値を1つにまとめて扱える便利な仕組みです。
本記事では構造体のメンバーにアクセスする方法に焦点を当て、ドット演算子(.)の基本から、ネストした構造体、配列との組み合わせまでを丁寧に解説します。
よくある間違いと避け方、読みやすく書くコツも合わせて学び、実用的なコードが書けるようになりましょう。
構造体メンバーアクセスの基本
構造体(struct)とメンバーの関係
構造体(struct)は、異なる型の値をひとまとまりにしたユーザー定義の型です。
たとえば、人を表す構造体には名前、年齢、身長といったメンバー
(フィールド)を定義できます。
各メンバーは、構造体変数からメンバー名
でアクセスします。
// 人を表す構造体の定義例
struct Person {
char name[32]; // 名前(文字配列)
int age; // 年齢
double height; // 身長(cm)
};
構造体は「設計図」、実際に使うときは変数を宣言して値を入れます。
メンバーにアクセスするための基本が次のドット演算子です。
メンバーアクセスはドット(.)で行う
構造体変数のメンバーにアクセスするときは、ドット(.)を使います。
書式は変数.メンバー名
です。
ネストしている場合は変数.サブ構造体.メンバー名
のようにドットを重ねます。
以下はよく使う書き方の早見表です。
対象 | 記法 | 例 |
---|---|---|
構造体変数のメンバー | 変数.メンバー | p.age |
配列要素のメンバー | 配列[i].メンバー | people[0].height |
ネストしたメンバー | 変数.サブ.メンバー | p.address.city |
参考: 構造体ポインタ(詳細は別記事) | (*ptr).メンバー または ptr->メンバー | (*pp).x |
ポインタに対してドットは使えません。
ポインタの場合は(*ptr).member
のように間接参照してからドット、または->
演算子を使います(アロー演算子の詳細は別記事で扱います)。
ドット演算子(.)の使い方
基本の書き方と例
まずはドット演算子でメンバーに値を代入し、読み取る基本例です。
#include <stdio.h>
#include <string.h> // 文字配列に文字列をコピーするため
// 構造体の定義
struct Person {
char name[32];
int age;
double height;
};
int main(void) {
struct Person p; // 構造体変数を宣言
strcpy(p.name, "Alice"); // 文字配列には文字列コピー関数を使う
p.age = 20; // 数値は代入可能
p.height = 165.5;
// ドットで参照して出力
printf("Name: %s\n", p.name);
printf("Age : %d\n", p.age);
printf("Height(cm): %.1f\n", p.height);
return 0;
}
Name: Alice
Age : 20
Height(cm): 165.5
メンバーの値の読み取りも、代入も、すべて変数.メンバー
で行います。
文字配列(例えばchar name[32]
)へは文字列リテラルを直接代入できないため、strcpy
やsnprintf
などの関数を使います。
メンバーの読み取りと代入
ドット演算子は「読み」と「書き」のどちらにも使えます。
読み取った値を別の計算に使うことも自然です。
#include <stdio.h>
#include <string.h>
struct Person {
char name[32];
int age;
double height;
};
int main(void) {
struct Person p;
strcpy(p.name, "Bob");
p.age = 29;
p.height = 172.4;
// 読み取り: メンバーの値を他の変数へ
int next_age = p.age + 1; // 来年の年齢
double taller = p.height + 2.0; // 2cmヒールを履いた想定
// 書き込み: メンバーへ新しい値を代入
p.age = next_age;
printf("%s is now %d years old.\n", p.name, p.age);
printf("With heels: %.1f cm\n", taller);
return 0;
}
Bob is now 30 years old.
With heels: 174.4 cm
ドット演算子は式の一部として自由に使えるため、計算や条件分岐の中でも活躍します。
ネストした構造体のメンバーにアクセス
構造体のメンバーに別の構造体を入れることができます。
この場合、変数.サブ構造体.メンバー
のようにドットをつなげて参照します。
#include <stdio.h>
#include <string.h>
// 住所を表す構造体
struct Address {
char prefecture[16];
char city[16];
char street[32];
};
// 人を表す構造体(住所を内包)
struct Person {
char name[32];
int age;
struct Address address; // ネストした構造体
};
int main(void) {
struct Person p;
// 各メンバーに代入
strcpy(p.name, "Carol");
p.age = 35;
// ネストした構造体の各メンバーへアクセス
strcpy(p.address.prefecture, "Tokyo");
strcpy(p.address.city, "Chiyoda");
strcpy(p.address.street, "1-1-1");
// ネストの参照
printf("%s (%d)\n", p.name, p.age);
printf("Address: %s, %s, %s\n",
p.address.prefecture,
p.address.city,
p.address.street);
return 0;
}
Carol (35)
Address: Tokyo, Chiyoda, 1-1-1
ネストが深いほどドットが増えますが、基本は同じです。
必要に応じて一時変数を使うと読みやすくなります(後述)。
構造体配列からメンバーにアクセス
複数人の情報を扱うなら、構造体の配列が便利です。
配列要素をpeople[i]
と書けるので、people[i].member
でアクセスします。
#include <stdio.h>
#include <string.h>
struct Person {
char name[32];
int age;
double height;
};
int main(void) {
// 初期化子で配列を用意
struct Person people[3];
strcpy(people[0].name, "Dan");
people[0].age = 18;
people[0].height = 168.2;
strcpy(people[1].name, "Eve");
people[1].age = 22;
people[1].height = 159.7;
strcpy(people[2].name, "Frank");
people[2].age = 27;
people[2].height = 180.3;
// ループで順に参照
for (int i = 0; i < 3; i++) {
printf("%d: %s, %d, %.1f cm\n",
i, people[i].name, people[i].age, people[i].height);
}
return 0;
}
0: Dan, 18, 168.2 cm
1: Eve, 22, 159.7 cm
2: Frank, 27, 180.3 cm
配列のインデックスで要素を選び、その後にドットでメンバーへアクセスするのが基本です。
構造体メンバーアクセスのよくある間違い
ポインタにはドットは使えない
構造体ポインタに対してドット.
は使えません。
ポインタなら(*ptr).member
のように間接参照してからドットを使うか、->
演算子を使います(アロー演算子の詳細は別記事で解説します)。
間違いの例(コンパイルエラー)
#include <stdio.h>
struct Point { int x; int y; };
int main(void) {
struct Point p = { 10, 20 };
struct Point *pp = &p;
// 間違い: ポインタにドットは使えない
printf("x=%d\n", pp.x); // ここでコンパイルエラー
return 0;
}
想定されるエラーメッセージ(GCCの一例)
error: request for member 'x' in something not a structure or union
正しい書き方(この章ではドットに統一)
#include <stdio.h>
struct Point { int x; int y; };
int main(void) {
struct Point p = { 10, 20 };
struct Point *pp = &p;
// ポインタを間接参照してからドットでアクセス
printf("x=%d, y=%d\n", (*pp).x, (*pp).y);
return 0;
}
x=10, y=20
メンバー名のスペルミスによるコンパイルエラー
メンバー名は定義どおりでなければなりません。
age
と定義したのにages
と書くとエラーです。
// 定義
struct Person { int age; };
// 間違い: スペルミス
// p.ages のように存在しないメンバーへアクセスするとコンパイルエラー
想定されるエラーメッセージ(GCCの一例)
error: 'struct Person' has no member named 'ages'
定義と使用箇所のスペルを統一し、補完機能のあるエディタでミスを減らすのが有効です。
未初期化のままアクセスしない
初期化していない構造体のメンバー値は不定です。
未初期化のまま読み取ると未定義動作になります。
悪い例(実行しない)
#include <stdio.h>
struct S { int a; double b; };
int main(void) {
struct S s; // 未初期化
// 未定義動作: 値は不定
printf("%d, %f\n", s.a, s.b);
return 0;
}
良い例(ゼロ初期化や明示的な代入)
#include <stdio.h>
struct S { int a; double b; };
int main(void) {
struct S s = {0}; // すべて0で初期化
s.a = 100; // 必要に応じて代入
s.b = 3.14;
printf("%d, %.2f\n", s.a, s.b);
return 0;
}
100, 3.14
構造体変数を使う前に、必ず初期化または必要なメンバーへ代入してから読むようにしましょう。
構造体メンバーアクセスのコツ
名前付けで意図を明確にする
読みやすさはバグの予防になります。
構造体名、メンバー名、変数名は役割が伝わる名前にすると、アクセスするコードも自然に理解できます。
- 構造体名は名詞で上位概念を表す(例:
Person
,Address
) - メンバー名は具体的で短く、必要なら下線区切りを使う(例:
birth_year
,postal_code
) - 配列には複数形や集合名(例:
people
,employees
)
命名の指針を簡単にまとめます。
項目 | 推奨例 | 説明 |
---|---|---|
構造体名 | Person | 対象物を表す名詞を使う |
メンバー名 | age , postal_code | 短く具体的、下線で可読性を高める |
変数名 | person , people | 単数と複数で役割を区別する |
長い式は一時変数で読みやすくする
ネストや配列が重なると、ドットによるアクセスが長くなります。
一時変数に取り出してから使うと理解しやすく、デバッグもしやすいです。
before: 1行が長い
double avg = (people[0].height + people[1].height + people[2].height) / 3.0;
after: 一時変数で分解
double h0 = people[0].height;
double h1 = people[1].height;
double h2 = people[2].height;
double avg = (h0 + h1 + h2) / 3.0;
同様に、ネストした構造体も一段取り出すと分かりやすくなります。
struct Address addr = person.address; // ネストを一時変数へ
printf("%s, %s, %s\n", addr.prefecture, addr.city, addr.street);
「ドットの回数が増えて読みにくい」と感じたら、一時変数を検討するのがコツです。
まとめ
本記事では、構造体のメンバーにアクセスするにはドット演算子(.)を使うという基本を、配列やネストした構造体まで含めて解説しました。
ポインタにドットは使えない点と、未初期化の読み取りは未定義動作である点は必ず押さえましょう。
命名と一時変数の活用は、読みやすく安全なコードづくりに直結します。
ドット演算子を確実に使いこなし、より大きなプログラムでも迷わずメンバーへアクセスできるように練習してみてください。
アロー演算子(->
)や関数と構造体の連携などの発展的な話題は、別記事で詳しく解説します。