[C言語のはじめ方] Part17: 構造体(struct)の定義と使用

C言語

ステップ4: 構造体・共用体・列挙体

構造体(struct)の定義と使用 ~ データをひとまとめにする魔法 ✨ ~

こんにちは! C言語学習、順調に進んでいますか? 😊 これまで、変数、配列、関数、そして少し複雑なポインタについて学んできましたね。これらを使いこなせるようになると、書けるプログラムの幅がぐっと広がります。

今回は、C言語のもう一つの重要な機能、構造体(struct)について学びます。構造体を理解すると、関連する複数のデータをひとまとめにして、より効率的に、そして分かりやすく扱うことができるようになります。

想像してみてください。例えば、一人の学生の情報を管理したいとき、どんなデータが必要でしょうか?

  • 学籍番号(整数: int
  • 名前(文字列: char 配列)
  • 年齢(整数: int
  • 平均点(浮動小数点数: float

これらはすべて異なるデータ型ですね。今までの知識だと、これらを個別の変数として宣言する必要がありました。

int student_id;
char student_name[50];
int student_age;
float student_average;

もし学生が100人いたら、変数が400個も必要になってしまい、管理がとても大変そうです… 😱

そこで登場するのが構造体です! 構造体を使えば、これらの異なるデータ型の情報を「学生」という一つのまとまりとして定義し、扱うことができるようになります。とっても便利そうですよね!

このステップでは、構造体の基本的な概念から、定義方法、そして実際の使い方まで、順を追って丁寧に解説していきます。さあ、一緒に構造体の世界を探検しましょう! 🚀

構造体(struct)って何? 🤔

構造体とは、複数の異なるデータ型の変数を一つにまとめた、ユーザー定義のデータ型です。

これまで使ってきた intfloatchar などは、C言語に最初から用意されている基本的なデータ型(組み込みデータ型)でした。構造体は、これらの基本的なデータ型を組み合わせて、プログラマーが自分で新しいデータ型を作り出す機能、と考えると分かりやすいかもしれません。

先ほどの「学生」の例で言うと、「学籍番号」「名前」「年齢」「平均点」という複数の要素(データ)をひとまとめにして、「学生情報」という新しいデータ型(構造体)を作るイメージです。

配列との違いは?
配列も複数のデータを扱いますが、配列に入れられるのは同じデータ型の要素だけでした(例: int 型の配列、char 型の配列)。一方、構造体は int 型、char 配列、float 型など、異なるデータ型の要素を自由に組み合わせることができます。これが構造体の大きな特徴です。

構造体の定義方法 📝

構造体を使うには、まず「どのようなデータ型の組み合わせで構成されるか」を定義する必要があります。構造体の定義は struct キーワードを使って行います。

基本的な構文は以下の通りです。

struct 構造体タグ名 {
    データ型1 メンバ名1;
    データ型2 メンバ名2;
    // ... 必要なだけメンバを定義 ...
    データ型N メンバ名N;
}; // ← セミコロンを忘れずに!
  • struct: 構造体を定義することを示すキーワードです。
  • 構造体タグ名: この構造体の名前(識別子)です。自由に名前を付けられますが、分かりやすい名前を付けることが推奨されます(例: `student`, `product`, `point` など)。このタグ名は、後でこの構造体型の変数を作るときに使います。
  • { }: 波括弧の中に、構造体に含めたい要素(変数)を定義します。
  • データ型 メンバ名;: 構造体の中に含まれる各変数をメンバと呼びます。通常の変数宣言と同じように、データ型とメンバ名を指定します。
  • ;: 構造体定義の最後の閉じ波括弧 } の後には、必ずセミコロン ; を付ける必要があります。忘れやすいので注意しましょう!⚠️

それでは、先ほどの「学生情報」を格納する構造体を定義してみましょう。

struct student {
    int id;          // 学籍番号 (整数)
    char name[50];   // 名前 (文字列、最大49文字 + ヌル文字)
    int age;         // 年齢 (整数)
    float average;   // 平均点 (浮動小数点数)
};

これで、int 型の idchar 型の配列 nameint 型の agefloat 型の average という4つのメンバを持つ、student という名前の構造体が定義されました。✨

typedef を使った別名定義 (より便利に!)

構造体を定義した後、この型の変数を使うときには毎回 struct 構造体タグ名 と書く必要があります。例えば、student 構造体型の変数を宣言するには struct student s1; のように書きます。

毎回 struct と書くのが少し面倒に感じるかもしれません。そこで、typedef というキーワードを使うと、定義した構造体型に別名を付けることができます。

typedef struct {
    int id;
    char name[50];
    int age;
    float average;
} Student; // ← ここで別名を定義 (大文字で始める慣習が多い)

このように定義すると、struct student という型名の代わりに Student という別名を使えるようになります。変数宣言が Student s1; のように、よりシンプルに書けるようになります。便利ですね!👍

あるいは、構造体タグ名を付けつつ、typedef で別名を定義することも可能です。

typedef struct student_tag { // 構造体タグ名も定義
    int id;
    char name[50];
    int age;
    float average;
} Student; // 別名も定義

この場合、struct student_tag s1;Student s2; のどちらの形式でも変数を宣言できます。どちらの書き方を使うかは好みやプロジェクトのコーディング規約によりますが、初学者のうちは typedef を使って簡潔に書く方法に慣れるのがおすすめです。

構造体変数の宣言と初期化 📦

構造体を定義したら、次はその型の変数を作成(宣言)します。定義はあくまで「設計図」であり、実際にデータを入れる「箱」を作るのが変数の宣言です。

変数の宣言

構造体タグ名を使って宣言する場合:

// struct student 型の変数 s1 を宣言
struct student s1;

typedef で別名を付けた場合:

// Student 型の変数 s2 を宣言 (typedef struct {...} Student; が定義済みとする)
Student s2;

これで、s1s2 という名前の変数がメモリ上に確保され、それぞれが id, name, age, average というメンバを持つ領域を持つことになります。

変数の初期化

構造体変数は、宣言と同時に初期化することができます。初期化するには、波括弧 {} を使い、定義したメンバの順序に合わせて値をカンマ , で区切って指定します。

// typedef で Student 型が定義済みとする

// 宣言と同時に初期化
Student s1 = {101, "Alice Smith", 20, 85.5f};

// 確認のため、中身を見てみましょう (次のセクションで詳しく説明します)
// s1.id には 101 が
// s1.name には "Alice Smith" が
// s1.age には 20 が
// s1.average には 85.5 が格納されます

波括弧の中の値は、構造体定義のメンバの順番に対応します。

注意点: 文字列メンバ(char 配列)を初期化する際は、ダブルクォーテーション " " で囲んだ文字列リテラルを指定します。

また、C99以降の規格では、指示付き初期化子 (designated initializer) という方法も使えます。メンバ名を指定して初期化できるため、順序を気にする必要がなく、一部のメンバだけを初期化したい場合に便利です。

// 指示付き初期化子を使った例
Student s2 = {
    .name = "Bob Johnson",
    .id = 102,
    .average = 78.0f
    // .age は指定しない場合、自動的に 0 で初期化される (環境や型による)
};

// s2.id には 102
// s2.name には "Bob Johnson"
// s2.age には 0 (または不定値の可能性もあるので注意)
// s2.average には 78.0

指示付き初期化子はコードの可読性を高めるため、積極的に利用すると良いでしょう。

もちろん、宣言だけしておいて、後から各メンバに値を代入することも可能です。その方法は次のセクションで学びます。

構造体メンバへのアクセス 👉

構造体変数を宣言し、初期化(または後から代入)したら、その中の個々のメンバにアクセスして値を読み取ったり、新しい値を書き込んだりする必要がありますね。

構造体のメンバにアクセスするには、ドット演算子 . を使います。

構文は以下の通りです。

構造体変数名.メンバ名

例えば、先ほど宣言・初期化した Student 型の変数 s1 の各メンバにアクセスするには、次のように書きます。

#include <stdio.h>
#include <string.h> // strcpy を使うために必要

// Student 型の定義 (typedef済みとする)
typedef struct {
    int id;
    char name[50];
    int age;
    float average;
} Student;

int main() {
    // 構造体変数の宣言と初期化
    Student s1 = {101, "Alice Smith", 20, 85.5f};
    Student s2; // s2 は宣言のみ

    // s1 のメンバの値を取得して表示
    printf("--- s1 の情報 ---\n");
    printf("ID      : %d\n", s1.id);       // s1 の id メンバにアクセス
    printf("Name    : %s\n", s1.name);     // s1 の name メンバにアクセス
    printf("Age     : %d\n", s1.age);      // s1 の age メンバにアクセス
    printf("Average : %.1f\n", s1.average); // s1 の average メンバにアクセス

    // s2 のメンバに値を代入
    s2.id = 102;
    // s2.name = "Bob Johnson"; // ← 文字列配列には直接代入できない!
    strcpy(s2.name, "Bob Johnson"); // strcpy関数を使って文字列をコピーする
    s2.age = 21;
    s2.average = 78.0f;

    // s2 のメンバの値を表示
    printf("\n--- s2 の情報 ---\n");
    printf("ID      : %d\n", s2.id);
    printf("Name    : %s\n", s2.name);
    printf("Age     : %d\n", s2.age);
    printf("Average : %.1f\n", s2.average);

    // メンバの値を変更することも可能
    printf("\n--- s1 の年齢を更新 ---\n");
    s1.age = s1.age + 1; // 年齢を1増やす
    printf("New Age : %d\n", s1.age);

    return 0;
}

このように、ドット演算子 . を使えば、まるで普通の変数のように構造体の各メンバにアクセスできます。簡単ですね! ✅

文字列メンバへの代入の注意点: 上のコード例にもあるように、構造体のメンバが char 配列(文字列)の場合、s2.name = "Bob Johnson"; のような直接代入はできません。配列の名前はポインタ定数として扱われるためです。文字列をコピーするには、string.h ヘッダファイルをインクルードして、strcpy 関数(またはより安全な strncpystrcpy_s)を使用する必要があります。
#include <string.h>
// ...
strcpy(構造体変数名.文字列メンバ名, "コピーしたい文字列");
文字列の扱いは少し注意が必要ですが、これも徐々に慣れていきましょう。

構造体の使い方の例 🧑‍💻

それでは、構造体を使って、複数の学生データを扱う簡単なプログラムを作成してみましょう。ここでは、2人の学生情報を構造体変数として用意し、それぞれの情報を表示します。

#include <stdio.h>
#include <string.h>

// 学生情報を格納する構造体 (typedef で Student 型を定義)
typedef struct {
    int id;
    char name[50];
    int age;
    float average;
} Student;

// 学生情報を表示する関数
void printStudentInfo(Student s) {
    printf("--------------------\n");
    printf("ID      : %d\n", s.id);
    printf("Name    : %s\n", s.name);
    printf("Age     : %d\n", s.age);
    printf("Average : %.1f\n", s.average);
    printf("--------------------\n");
}

int main() {
    // 構造体変数 (学生データ) を宣言・初期化
    Student student1 = {101, "Taro Yamada", 19, 88.2f};
    Student student2;

    // student2 の情報を設定
    student2.id = 102;
    strcpy(student2.name, "Hanako Sato");
    student2.age = 20;
    student2.average = 91.5f;

    // 各学生の情報を表示
    printf("学生1の情報:\n");
    printStudentInfo(student1); // 関数に構造体変数を渡す

    printf("\n学生2の情報:\n");
    printStudentInfo(student2);

    return 0;
}

このプログラムでは、まず Student 構造体を定義しています。そして、main 関数内で student1student2 という2つの Student 型変数を作成し、それぞれの情報を設定しています。

注目してほしいのは printStudentInfo 関数です。この関数は引数として Student 型の構造体変数 s を受け取ります。関数内では、受け取った構造体変数のメンバにドット演算子 . でアクセスして、情報を表示しています。

このように、構造体を関数に渡すことも可能です。関連するデータがひとまとめになっているので、関数に必要な情報を渡すのがとても簡単になりますね。もし構造体を使わなかったら、printStudentInfo 関数は id, name, age, average という4つの引数を個別に受け取る必要があり、コードが煩雑になっていたでしょう。

構造体を使うことで、データとその操作が整理され、プログラム全体の見通しが良くなることが実感できたでしょうか? 😊

補足: 上の例では、関数に構造体変数をそのまま渡しています(値渡し)。この場合、関数内で構造体のコピーが作成されるため、元の変数の値は変更されません。もし関数内で元の構造体の値を変更したい場合は、ポインタを使って構造体のアドレスを渡す方法(参照渡し)があります。これについては、次のステップ「構造体配列・構造体ポインタ」で詳しく学びますので、今は「構造体を関数に渡せるんだな」ということを覚えておきましょう。

まとめ 🎉

今回は、C言語の強力な機能である構造体(struct)について学びました。

  • ✅ 構造体は、異なるデータ型の変数を一つにまとめたユーザー定義のデータ型であること。
  • struct キーワードを使って定義し、中にメンバを含めること。
  • typedef を使うと、構造体型に別名を付けて便利に扱えること。
  • ✅ 構造体変数は、他の変数と同じように宣言し、波括弧 {}初期化できること。
  • ✅ 構造体のメンバには、ドット演算子 . を使ってアクセスすること。
  • ✅ 構造体を使うことで、関連するデータをまとめて管理しやすくなり、プログラムが整理されること。

構造体は、単体のデータを扱うだけでなく、これから学ぶ構造体の配列構造体のポインタと組み合わせることで、さらに複雑なデータ構造(例えば、リストや木構造など)を表現するための基礎となります。また、ファイルにデータを保存したり、読み込んだりする際にも非常に役立ちます。

最初は少し難しく感じるかもしれませんが、実際にコードを書いて動かしてみることで、その便利さが実感できるはずです。ぜひ、色々な構造体を定義して、メンバにアクセスする練習をしてみてくださいね!

次のステップでは、今回学んだ構造体をさらに活用する方法として、「構造体配列」と「構造体ポインタ」について解説します。お楽しみに! 💪

コメント

タイトルとURLをコピーしました