完全理解!C言語での構造体の使い方と応用例10選

C言語で構造体を使用するイラスト付きのガイドブックC言語
この記事は約16分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

プログラミング言語Cでコードを書く際、特に大規模なプロジェクトにおいて、構造体は必須の知識となります。

構造体を理解し、効果的に使用することで、より読みやすく、効率的なプログラムを作成することが可能となります。

この記事では、C言語で構造体を完全に理解し、活用するための10のステップを提供します。

●C言語の構造体とは

構造体は、異なる型のデータを一つのグループとして扱うためのC言語の特性です。

○基本的な構造体の定義

C言語で構造体を定義するための基本的な構文は次のようになります。

struct Student {
    char name[50];
    int age;
    float score;
};

このコードでは、学生(Student)を表す構造体を定義しています。

この例では、名前(name)、年齢(age)、得点(score)という3つの異なる型のデータを一つのグループとして扱っています。

○構造体のメンバへのアクセス方法

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

下記のコードでは、先ほど定義したStudent型の構造体の各メンバにアクセスしています。

struct Student student1;

strcpy(student1.name, "John");
student1.age = 20;
student1.score = 85.5;

●構造体の使い方:詳細なガイド

これから、構造体の使い方を詳しく解説します。

具体的なコード例を用いて、基本的な使用方法から少し応用的な使い方までを見ていきましょう。

○サンプルコード1:基本的な構造体の使用例

下記のコードでは、先ほど定義したStudent型の構造体を使用し、学生の情報を出力するプログラムを作成しています。

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

struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    struct Student student1;

    strcpy(student1.name, "John");
    student1.age = 20;
    student1.score = 85.5;

    printf("Name: %s\n", student1.name);
    printf("Age: %d\n", student1.age);
    printf("Score: %.2f\n", student1.score);

    return 0;
}

このコードを実行すると、次のような結果が得られます。

Name: John
Age: 20
Score: 85.50

これは、構造体のメンバに値を設定し、その値を出力しています。

○サンプルコード2:構造体配列の使用例

次に、構造体を配列として使用する方法を見ていきましょう。

下記のコードは、3人の学生の情報を格納し、それぞれの情報を出力するものです。

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

struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    struct Student students[3];

    for(int i = 0; i < 3; i++) {
        printf("Enter information for student %d:\n", i + 1);

        printf("Name: ");
        scanf("%s", students[i].name);

        printf("Age: ");
        scanf("%d", &students[i].age);

        printf("Score: ");
        scanf("%f", &students[i].score);
    }

    for(int i = 0; i < 3; i++) {
        printf("\nInformation for student %d:\n", i + 1);
        printf("Name: %s\n", students[i].name);
        printf("Age: %d\n", students[i].age);
        printf("Score: %.2f\n", students[i].score);
    }

    return 0;
}

このコードを実行すると、各学生の情報を入力するプロンプトが表示され、その後で入力した情報が出力されます。

このように、構造体と配列を組み合わせることで、同じ型の複数のデータを一元的に管理することができます。

●応用例とサンプルコード

これから、C言語で構造体を使用した応用例を10個紹介します。

これらの例を通して、構造体の柔軟性とパワフルさを理解し、自身のプロジェクトに役立てることができるでしょう。

○サンプルコード3:構造体を関数に渡す

下記のコードは、構造体を関数に渡す方法を表しています。

ここでは、構造体「student」を作成し、その情報を関数「printStudent」を使って印刷しています。

#include<stdio.h>

struct student {
    char name[50];
    int age;
};

void printStudent(struct student s) {
    printf("名前: %s\n", s.name);
    printf("年齢: %d\n", s.age);
}

int main() {
    struct student s1 = {"山田太郎", 20};
    printStudent(s1);
    return 0;
}

このコードを実行すると、「山田太郎」と「20」という情報が出力されます。

このように、構造体を関数に渡すことで、構造体のデータをまとめて操作することが可能になります。

○サンプルコード4:構造体を使ったデータの管理

次に、構造体を使って複数のデータを一元管理する例を見てみましょう。

下記のコードは、商品の情報を管理するための「product」構造体を定義し、その配列を作成しています。

#include<stdio.h>

struct product {
    char name[50];
    int price;
};

int main() {
    struct product items[3] = {{"apple", 100}, {"banana", 200}, {"cherry", 300}};

    for(int i = 0; i < 3; i++) {
        printf("商品名: %s, 価格: %d円\n", items[i].name, items[i].price);
    }

    return 0;
}

このコードを実行すると、「apple」、「banana」、「cherry」とそれぞれの価格が出力されます。

このように、構造体を配列として使用することで、複数のデータを一元管理することが可能になります。

○サンプルコード5:ポインタを使った構造体のアクセス

構造体を使ったプログラミングでは、ポインタを活用することもよくあります。

下記のコードは、ポインタを使って構造体のメンバにアクセスする例を表しています。

#include<stdio.h>

struct employee {
    char name[50];
    int id;
};

int main() {
    struct employee e1 = {"田中一郎", 12345};
    struct employee *ptr = &e1;

    printf("名前: %s, ID: %d\n", ptr->name, ptr->id);

    return 0;
}

このコードを実行すると、「田中一郎」と「12345」という情報が出力されます。

ポインタを使うことで、構造体のメンバに直接アクセスすることができ、データの操作が容易になります。

○サンプルコード6:構造体のネスト

構造体はネスト(入れ子)にすることも可能です。

下記のコードは、日付を表す構造体「Date」を、人物情報を表す構造体「Person」内で使用しています。

#include<stdio.h>

struct Date {
    int year;
    int month;
    int day;
};

struct Person {
    char name[50];
    struct Date birthday;
};

int main() {
    struct Person p = {"鈴木二郎", {2000, 1, 1}};

    printf("名前: %s, 生年月日: %d年%d月%d日\n", p.name, p.birthday.year, p.birthday.month, p.birthday.day);

    return 0;
}

このコードを実行すると、「鈴木二郎」とその生年月日が出力されます。

このように、構造体をネストすることで、関連性のあるデータをより体系的に管理できます。

○サンプルコード7:自己参照構造体

自己参照構造体は、構造体の中で自分自身の型を参照する方法です。

主にリンクリストや木構造など、データ構造の表現に利用されます。

次に自己参照構造体の基本的な使用例を紹介します。

#include <stdio.h>

// 自己参照構造体の定義
struct Node {
    int data;
    struct Node* next;
};

int main() {
    // 自己参照構造体の宣言
    struct Node node1, node2;

    node1.data = 10;
    node2.data = 20;
    node1.next = &node2;  // node1からnode2へのリンクを作る
    node2.next = NULL;

    printf("node1のデータ: %d\n", node1.data);
    printf("node2のデータ: %d\n", node1.next->data);

    return 0;
}

このコードでは、自己参照構造体の定義と使用を紹介しています。

自己参照構造体は、同じ型のポインタをメンバとして持つことで他の同じ型の構造体を参照できます。

この例では、node1node2を参照しています。

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

node1のデータ: 10
node2のデータ: 20

自己参照構造体を使用することで、データ同士のリンクを容易に表現できます。

ただし、このような構造体を使用する際は、メモリ管理に注意が必要です。データが消去されたり、参照が切れたりすると、未定義の動作を引き起こす可能性があります。

○サンプルコード8:共用体を使った構造体

C言語では、共用体(union)を使うことで、同じメモリ領域を異なる型で使用することが可能です。

共用体と構造体を組み合わせることで、データの型によって扱いを変えることが可能になります。

次に、共用体を使った構造体の使用例を紹介します。

#include <stdio.h>

// 共用体の定義
union Data {
    int i;
    float f;
    char str[20];
};

// 構造体の定義
struct Container {
    int type;
    union Data data;
};

int main() {
    struct Container container;

    // 整数の格納
    container.type = 1;
    container.data.i = 10;
    printf("整数: %d\n", container.data.i);

    // 浮動小数点数の格納
    container.type = 2;
    container.data.f = 20.5;
    printf("浮動小数点数: %.1f\n", container.data.f);

    // 文字列の格納
    container.type = 3;
    strcpy(container.data.str, "Hello, World!");
    printf("文字列: %s\n", container.data.str);

    return 0;
}

このコードでは、共用体を使ってint、float、char型のデータを同じメモリ領域で保持しています。

そして、それらのデータを構造体でラップしています。typeフィールドにより、現在どの型のデータが格納されているかを表しています。

このコードの実行結果は次のようになります。

整数: 10
浮動小数点数: 20.5
文字列: Hello, World!

このように共用体と構造体を使うと、異なる型のデータを柔軟に扱うことができます。

ただし、共用体を使用する際は、一度に一つのデータしか保持できないことを忘れないようにしましょう。

○サンプルコード9:typedefと構造体

C言語の構造体をより使いやすくするためには、typedefというキーワードが非常に有効です。

これは、型定義(type definition)を行うためのキーワードで、構造体を独自の型として定義し、繰り返し構造体を使う際の冗長さを減らすことができます。

typedefを用いた構造体の定義と使用方法を示すサンプルコードを掲載します。

#include <stdio.h>

// typedefを用いた構造体の定義
typedef struct {
    int id;
    char name[20];
} Student;

int main() {
    // typedefを用いた構造体の使用
    Student student;

    student.id = 1;
    sprintf(student.name, "Taro Yamada");

    printf("学生ID:%d\n", student.id);
    printf("学生名:%s\n", student.name);

    return 0;
}

このコードでは、”Student”という名前の新しい型を作成しています。

この新しい型は構造体であり、”id”と”name”という2つのメンバを持っています。

そして、この新しい型を使用して”student”という変数を定義し、値を設定してから表示しています。

実行すると以下の結果が得られます。

学生ID:1
学生名:Taro Yamada

このように、typedefを使用することで構造体を定義する際に”struct”と書く必要がなくなり、よりシンプルに、そして直感的にコードを書くことが可能になります。

○サンプルコード10:構造体を使ったリンクリスト

次に、構造体を用いてリンクリストを実装するサンプルコードを見てみましょう。

リンクリストはデータの集合を線形につなげたデータ構造で、各データ(ノード)が次のデータへの参照(リンク)を持っています。

#include <stdio.h>
#include <stdlib.h>

// リンクリストのノードを表す構造体
typedef struct node {
    int data;
    struct node* next;
} Node;

// リンクリストのノードを作成する関数
Node* create_node(int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));

    new_node->data = data;
    new_node->next = NULL;

    return new_node;
}

int main() {
    Node* n1 = create_node(10);
    Node* n2 = create_node(20);
    Node* n3 = create_node(30);

    n1->next = n2;
    n2->next = n3;

    Node* temp = n1;
    while(temp != NULL) {
        printf("%d\n", temp->data);
        temp = temp->next;
    }

    free(n1);
    free(n2);
    free(n3);

    return 0;
}

このコードでは、リンクリストのノードを表す構造体Nodeを定義しています。

そして、新しいノードを作成するための関数create_nodeを実装しています。

この関数は、新しいノードをメモリ上に確保し、そのノードのdataメンバに引数で与えられた値を設定し、nextメンバをNULL(つまり、次のノードはまだない)に設定しています。

その後、main関数内で3つのノードを作成し、それらをリンクリストとしてつなげています。

そして、リンクリストの先頭から順に各ノードのデータを表示しています。

このコードを実行すると、次のような出力が得られます。

10
20
30

この例は、構造体を用いてリンクリストというより複雑なデータ構造を表現する方法を表しています。

構造体とポインタを組み合わせることで、様々なデータ構造を実装することが可能になります。

●構造体の注意点と対処法

以上の例からも分かるように、構造体は非常に強力なツールであり、より高度なプログラミングを可能にします。

しかし、その使い方にはいくつか注意点があります。

まず一つ目は、構造体のメモリ管理です。

特に、関数内で動的に構造体を確保した場合は、その構造体のメモリを解放しなければならないということです。

これを怠るとメモリリークという問題が発生します。

メモリリークは、プログラムがメモリを無駄に消費し続ける現象で、長時間の実行や大量のデータ処理においては深刻な問題を引き起こす可能性があります。

二つ目の注意点は、構造体のサイズです。

構造体は、そのメンバの数や型によって占めるメモリのサイズが変わります。

大きな構造体を大量に生成した場合、必要なメモリが確保できないという問題が発生することがあります。

そのため、構造体を設計する際にはそのサイズを意識することが重要です。

●構造体のカスタマイズ方法

構造体の力を最大限に引き出すためには、そのカスタマイズ方法を理解することが必要です。

ここでは、構造体のカスタマイズ方法として、メンバの追加や削除、型の変更などを考慮することができます。

例えば、学生の情報を持つ構造体を考えてみましょう。最初は「名前」と「年齢」だけをメンバとして持つ構造体かもしれませんが、後に「成績」や「住所」などの情報が必要になるかもしれません。

その際には、構造体に新たなメンバを追加することで対応します。

また、既存のメンバが不要になった場合には、そのメンバを削除します。

メンバの削除は、単にそのメンバの定義を構造体から削除するだけです。

さらに、メンバの型を変更することも可能です。

例えば、「年齢」のメンバがint型からdouble型に変更された場合、その変更は構造体の定義の中で行います。

これらの操作は、構造体の定義を直接編集することで実現します。

そのため、構造体の定義が変更された場合、その構造体を使用しているコード全体が影響を受けることに注意が必要です。

まとめ

この記事では、C言語の構造体の使い方とその応用例を詳しく解説しました。

構造体は、複数の異なる型のデータをひとまとめにするための非常に便利なツールです。

構造体を理解し、適切に使用することで、プログラムの複雑さを大幅に軽減し、より高度なデータ操作を行うことが可能になります。

特に、データの集合を表現する際や、複数の関連するデータを一つの単位として扱いたい場合には、構造体は非常に有力な選択肢となります。

これからもC言語の学習を続ける中で、ぜひ構造体の使い方をマスターして、より高度なプログラミングに挑戦してみてください。