コンテンツにスキップ

C言語/構造体

出典: フリー教科書『ウィキブックス(Wikibooks)』

構造体の基本

[編集]

構造体とは何か

[編集]

構造体の定義と必要性

[編集]

C言語では、単一のデータ型(例: int, double, char)だけでなく、複数の異なるデータ型をまとめて一つの単位として扱うことができます。このようなデータの集合を「構造体」と呼びます。構造体を使用すると、異なるデータ型の変数を一つのまとまりとして管理でき、より複雑なデータ構造を簡潔に扱えるようになります。

基本的な使用例

[編集]

例えば、学生の情報(名前、年齢、成績)を管理するために、以下のように構造体を定義できます。

struct Student {
    char name[50];
    int age;
    double grade;
};

この定義により、「Student」構造体変数を使用して、一つの変数に名前、年齢、成績をまとめて格納できます。

構造体の宣言と定義

[編集]

構造体の宣言方法

[編集]

構造体を宣言するには、structキーワードを使用します。構造体名を指定し、その中でメンバー変数を定義します。

struct StructName {
    dataType member1;
    dataType member2;
    // その他のメンバー
};

メンバー変数の定義

[編集]

例えば、点の座標を表す構造体を定義するには、次のようにします。

struct Point {
    int x;
    int y;
};

このように定義することで、「Point」という構造体が作成され、xおよびyの2つの整数メンバーを持つことになります。

構造体の使用

[編集]

構造体変数の定義と初期化

[編集]

構造体変数の宣言

[編集]

構造体を定義した後、その型を使用して変数を宣言できます。

struct Point p1;

構造体変数の初期化方法

[編集]

構造体変数は宣言時に初期化することも可能です。

struct Point p1 = {10.0, 20.0};                       // 通常の初期化
struct Point p2 = {.x = 10.0, .y = 20.0};             // メンバー名を指定して初期化
struct Point p3 = {.y = 20.0, .x = 10.0};             // 順序を問わず初期化可能
struct Point *p4 = &(struct Point){.x = 30.0, .y = 40.0}; // 複合リテラルを使用した初期化

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

[編集]

ドット演算子(.)を使ったアクセス

[編集]

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

p1.x = 15;
printf("x: %f, y: %f", p1.x, p1.y);

このコードでは、p1xメンバーに15を代入し、p1xyメンバーの値を表示しています。

構造体と配列

[編集]

構造体配列の定義と使用

[編集]

構造体配列の宣言と初期化

[編集]

構造体を配列として定義することも可能です。

struct Point points[5];

構造体配列を初期化することもできます。

struct Point points[] = {{0.1, 0.9}, {1.1, 1.2}, {2.3, 2.7}};
struct Point points2[] = {{.x = 0.1, .y = 0.9}, {.y = 1.2, .x = 1.1}, {.x = 2.3, .y = 2.7}};

構造体配列の操作

[編集]

配列の要素にアクセスして操作することができます。

points[0].x = 5.2;
printf("Point 0: x = %f, y = %f", points[0].x, points[0].y);

構造体を配列のメンバーとして使用

[編集]

配列メンバーとしての構造体の使用例

[編集]

構造体のメンバーとして配列を持つこともできます。

struct Polygon {
    struct Point vertices[5];
};

多次元構造体配列

[編集]

多次元の構造体配列も使用できます。

struct Point grid[3][3];

ポインタと構造体

[編集]

構造体へのポインタ

[編集]

構造体ポインタの宣言と初期化

[編集]

構造体へのポインタを使用することで、構造体を効率的に扱うことができます。ポインタを使うと、構造体のメモリの直接操作や、関数間でのデータの受け渡しが容易になります。

struct Point p = {10.0, 20.9}; // 構造体の定義と初期化
struct Point *pPtr = &p;       // 構造体へのポインタを初期化

アロー演算子(->)を使ったメンバーアクセス

[編集]

構造体ポインタを使ってメンバーにアクセスする場合、アロー演算子(->)を使用します。これにより、ポインタを介して構造体のメンバーに直接アクセスできます。

pPtr->x = 30.2;                // ポインタを介してメンバーに代入
printf("x: %f, y: %f", pPtr->x, pPtr->y); // メンバーの値を表示

構造体ポインタの活用

[編集]

動的メモリ確保と構造体

[編集]

動的に構造体のメモリを確保する場合は、malloc関数を使用します。動的メモリ確保により、プログラムの実行時に必要なメモリ量を柔軟に管理できます。

#include <stdlib.h> // mallocを使用するために必要

struct Point *pPtr = (struct Point *)malloc(sizeof(struct Point)); // メモリ確保
if (pPtr == NULL) { // メモリ確保の成功を確認
    perror("Failed to allocate memory");
    return 1; // エラーハンドリング
}

pPtr->x = 10; // メンバーに値を代入
pPtr->y = 20;

メモリを確保した後は、使用が終わった際にfree関数を使って解放することを忘れないでください。

free(pPtr); // 確保したメモリを解放

構造体ポインタを使った関数引数の例

[編集]

構造体ポインタを関数の引数として渡すことで、効率的なデータ操作が可能です。ポインタを使うことで、構造体のコピーを避け、メモリの使用効率を向上させます。

void printPoint(struct Point *p) {
    printf("x: %f, y: %f", p->x, p->y); // 構造体のメンバーを表示
}

printPoint(pPtr); // ポインタを引数として渡す

ネストした構造体

[編集]

構造体内の構造体

[編集]

ネスト構造体の定義と使用方法

[編集]

構造体のメンバーとして他の構造体を定義することができます。

struct Rectangle {
    struct Point topLeft;
    struct Point bottomRight;
};

ネスト構造体の初期化

[編集]

ネストした構造体も初期化可能です。

struct Rectangle rect = {{0.1, 0.9}, {10.1, 10.2}};
struct Rectangle rct2 = {.topLeft = {0.1, 0.9}, .bottomRight = {10.1, 10.2}};
struct Rectangle rct3 = {.topLeft.x = 0.1, .topLeft.y = 0.9, .bottomRight.x = 10.1, .bottomRight.y = 10.2};

ネスト構造体のアクセス

[編集]

ネスト構造体メンバーへのアクセス方法

[編集]

ネスト構造体のメンバーにアクセスする場合、ドット演算子を連続して使用します。

rect.topLeft.x = 5;
printf("Top-left x: %f", rect.topLeft.x);

ネスト構造体とポインタの併用

[編集]

ネスト構造体をポインタで扱うこともできます。

struct Rectangle *rectPtr = &rect;
rectPtr->topLeft.x = 5.5;
printf("Top-left x: %f", rectPtr->topLeft.x);

構造体と関数

[編集]

関数への構造体の引数渡し

[編集]

値渡しとポインタ渡し

[編集]

構造体を関数に渡す際、値渡しとポインタ渡しのどちらも使用できます。値渡しの場合、関数内での変更は元の構造体に影響を与えません。

void modifyPoint(struct Point p) {
    p.x = 100;
}

ポインタ渡しの場合、関数内での変更は元の構造体に反映されます。

void modifyPointRef(struct Point *p) {
    if (p == NULL) {
        return;
    }
    p->x = 100;
}

構造体を返す関数

[編集]

関数から構造体を返すことも可能です。

struct Point createPoint(int x, int y) {
    struct Point pt = (struct Point){x, y};
    return pt;
}

構造体を使ったプログラム設計

[編集]

構造体を使ったモジュール設計

[編集]

構造体を使うことで、データのカプセル化とモジュール設計が容易になります。例えば、複数の関数が共通して使用するデータを一つの構造体としてまとめることで、コードの可読性と保守性が向上します。

struct Account {
    int accountNumber;
    double balance;
};

void deposit(struct Account *acc, double amount) {
    acc->balance += amount;
}

double withdraw(struct Account *acc, double amount) {
    if (acc->balance >= amount) {
        acc->balance -= amount;
        return amount;
    } else {
        printf("Insufficient balance\n");
        return 0.0;
    }
}

int main() {
    struct Account myAccount = {1001, 500.0};

    deposit(&myAccount, 200.0);
    printf("Current balance: %.2f\n", myAccount.balance);

    double withdrawnAmount = withdraw(&myAccount, 100.0);
    if (withdrawnAmount > 0.0) {
        printf("Withdrawn amount: %.2f\n", withdrawnAmount);
    }
    printf("Current balance: %.2f\n", myAccount.balance);

    return 0;
}

この例では、Account構造体を定義し、それに対して預金(deposit)と引き出し(withdraw)の関数を定義しています。main関数では、myAccountという名前の構造体変数を作成し、預金と引き出しの操作を行っています。

構造体とファイル操作

[編集]

構造体のシリアライズ

[編集]

バイナリファイルへの書き込みと読み込み

[編集]

構造体をバイナリファイルに書き込み、読み込むことで、データの永続化と復元が可能です。

#include <stdio.h>

struct Employee {
    int empId;
    char name[50];
    double salary;
} __attribute__((aligned(64)));

int main() {
    // 構造体をファイルに書き込む例
    struct Employee emp1 = {1, "John Doe", 50000.0};
    FILE *fp = fopen("employee.dat", "wbe");
    if (fp == NULL) {
        perror("Open: employee.dat:wb");
        return 1;
    }
    fwrite(&emp1, sizeof(struct Employee), 1, fp);
    fclose(fp);

    // ファイルから構造体を読み込む例
    struct Employee emp2;
    fp = fopen("employee.dat", "rbe");
    if (fp == NULL) {
        perror("Open: employee.dat:rb");
        return 1;
    }
    fread(&emp2, sizeof(struct Employee), 1, fp);
    fclose(fp);

    printf("Employee ID: %d\n", emp2.empId);
    printf("Name: %s\n", emp2.name);
    printf("Salary: %.2f\n", emp2.salary);

    return 0;
}

テキストファイルへの書き込みと読み込み

[編集]

構造体をテキストファイルに書き込み、読み込むことも可能です。

#include <stdio.h>

struct Student {
    char name[50];
    int age;
    double gpa;
} __attribute__((aligned(64)));

int main() {
    // 構造体をファイルに書き込む例
    struct Student students[3] = {
        {"Alice", 20, 3.5}, {"Bob", 21, 3.8}, {"Charlie", 19, 3.2}};

    FILE *fp = fopen("students.txt", "wbe");
    if (fp == NULL) {
        perror("Open: students.dat:wb");
        return 1;
    }
    for (int i = 0; i < 3; i++) {
        fprintf(fp, "%s %d %.2f\n", students[i].name, students[i].age,
                students[i].gpa);
    }
    fclose(fp);

    // ファイルから構造体を読み込む例
    struct Student student;
    fp = fopen("students.txt", "rbe");
    if (fp == NULL) {
        perror("Open: students.dat:rb");
        return 1;
    }
    while (fscanf(fp, "%s %d %lf", student.name, &student.age, &student.gpa) !=
           EOF) {
        printf("Name: %s, Age: %d, GPA: %.2f\n", student.name, student.age,
               student.gpa);
    }
    fclose(fp);

    return 0;
}

構造体の永続化

[編集]

永続化の概念と必要性

[編集]

データの永続化とは、プログラムの実行が終了してもデータを保存し、次回の実行時に復元できることを指します。構造体をファイルに書き込むことで、プログラムの実行状態を保存し、後から読み込むことができます。

永続化の実装例

[編集]

前述のバイナリファイルやテキストファイルへの書き込みと読み込みの例は、永続化の実装例として非常に有用です。これにより、ユーザーが入力したデータやプログラムの状態を保存し、後で復元することが可能になります。

高度な構造体の使用

[編集]

共用体との組み合わせ

[編集]

共用体と構造体の違い

[編集]

共用体は、同じメモリ領域を複数の異なる形式で解釈することができるデータ型です。これに対し、構造体は異なる種類のデータを一つのまとまりとして管理するためのものです。

共用体と構造体の組み合わせ例

[編集]

共用体と構造体を組み合わせて、柔軟なデータ構造を設計することができます。

struct Data {
    int type;
    union {
        int intValue;
        double doubleValue;
        char stringValue[20];
    } value;
};

ビットフィールド

[編集]

ビットフィールドの定義

[編集]

ビットフィールドは、メモリ使用の最適化やデータの圧縮に役立ちます。

struct {
    unsigned int isStudent : 1;
    unsigned int age : 7;
} status;

メモリ効率を考えた構造体設計

[編集]

ビットフィールドを使って、メモリ使用の最適化を図ることができます。ただし、ビットフィールドの使用はプラットフォーム依存性に注意が必要です。

練習問題と実践

[編集]

基本的な練習問題

[編集]

基本的な構造体操作に関する問題

[編集]
  1. 点の座標を表す構造体Pointを定義し、2つの点の距離を計算する関数を作成してください。
  2. 学生情報を表す構造体Studentを定義し、3人分の学生の情報を入力して表示するプログラムを作成してください。

解答例と解説

[編集]
  1. Point構造体を使って、2点間の距離を計算する関数を作成します。
    #include <stdio.h>
    #include <math.h>
    
    struct Point {
        double x;
        double y;
    };
    
    double distance(struct Point p1, struct Point p2) {
        return hypot(p2.x - p1.x, p2.y - p1.y);
    }
    
    int main() {
        struct Point p1 = {1, 2};
        struct Point p2 = {4, 6};
        printf("Distance between points: %.2f\n", distance(p1, p2));
        return 0;
    }
    
  2. Student構造体を使って、3人の学生の情報を入力して表示するプログラムを作成します。
    #include <stdio.h>
    
    struct Student {
        char name[50];
        int age;
        double gpa;
    };
    
    int main() {
        struct Student students[3];
    
        // 学生の情報を入力
        for (int i = 0; i < 3; i++) {
            printf("Enter student %d name: ", i + 1);
            scanf("%s", students[i].name);
            printf("Enter student %d age: ", i + 1);
            scanf("%d", &students[i].age);
            printf("Enter student %d GPA: ", i + 1);
            scanf("%f", &students[i].gpa);
        }
    
        // 入力した学生情報を表示
        printf("\nEntered student information:\n");
        for (int i = 0; i < 3; i++) {
            printf("Student %d\n", i + 1);
            printf("Name: %s\n", students[i].name);
            printf("Age: %d\n", students[i].age);
            printf("GPA: %.2f\n", students[i].gpa);
            printf("\n");
        }
    
        return 0;
    }
    

このプログラムでは、Student構造体を使って、3人分の学生の情報を入力し、それを表示する例です。

応用的な練習問題

[編集]

実践的なプログラム設計に関する問題

[編集]
  1. バンクアカウントを表す構造体BankAccountを定義し、預金と引き出しの操作を行う関数を実装してください。
  2. データベースのレコードを表す構造体DatabaseRecordを定義し、ファイルからデータを読み込み、データを更新するプログラムを作成してください。

解答例と解説

[編集]
  1. BankAccount構造体を定義し、預金と引き出しの操作を行う関数を実装します。
    #include <stdio.h>
    
    struct BankAccount {
        int accountNumber;
        double balance;
    };
    
    void deposit(struct BankAccount *acc, double amount) {
        acc->balance += amount;
    }
    
    double withdraw(struct BankAccount *acc, double amount) {
        if (acc->balance >= amount) {
            acc->balance -= amount;
            return amount;
        } else {
            printf("Insufficient balance\n");
            return 0.0;
        }
    }
    
    int main() {
        struct BankAccount myAccount = {1001, 500.0};
    
        deposit(&myAccount, 200.0);
        printf("Current balance: %.2f\n", myAccount.balance);
    
        double withdrawnAmount = withdraw(&myAccount, 100.0);
        if (withdrawnAmount > 0.0) {
            printf("Withdrawn amount: %.2f\n", withdrawnAmount);
        }
        printf("Current balance: %.2f\n", myAccount.balance);
    
        return 0;
    }
    
  2. DatabaseRecord構造体を定義し、ファイルからデータを読み込み、データを更新するプログラムを作成します。以下は例としてファイルからの読み込みを示します。
    #include <stdio.h>
    #include <string.h>
    
    struct DatabaseRecord {
        int id;
        char name[50];
        int age;
    };
    
    int main() {
        FILE *fp = fopen("records.dat", "rb");
        if (fp == NULL) {
            perror("Error opening file");
            return 1;
        }
    
        struct DatabaseRecord record;
        while (fread(&record, sizeof(struct DatabaseRecord), 1, fp)) {
            printf("ID: %d\n", record.id);
            printf("Name: %s\n", record.name);
            printf("Age: %d\n", record.age);
            printf("\n");
        }
    
        fclose(fp);
        return 0;
    }
    

このプログラムは、records.datというバイナリファイルからDatabaseRecord構造体を読み込み、その内容を表示します。

プロジェクト

[編集]

小規模なプロジェクト例

[編集]
  1. シンプルなアドレス帳アプリケーションの作成
    構造体を使用して、名前、電話番号、住所などの情報を持つ連絡先を管理するシンプルなアドレス帳アプリケーションを作成してください。追加、編集、削除、検索などの基本的な操作ができるようにします。
  2. 簡易的な学生の成績管理システム
  3. 学生の名前、学籍番号、科目ごとの成績(数学、英語、科学など)を管理するプログラムを作成してください。成績の追加、削除、修正、平均成績の計算などの機能を実装します。

これらのプロジェクトは、構造体の基本的な操作から応用までを実践するための良い方法です。プロジェクトを通じて、実際の問題に対する解決策を設計し、プログラミングスキルを向上させることができます。

附録: 構造体リファレンス

[編集]

C言語の構造体のユースケース

[編集]

C言語の構造体は、関連するデータをまとめて一つのデータ型として扱うことができるため、さまざまなユースケースに適しています。以下にいくつかの具体的なユースケースを示します。

データベースレコードの表現

[編集]

構造体を使用して、データベースのレコードを表現できます。たとえば、ユーザー情報を格納するための構造体を定義できます。

struct User {
    int id;
    char name[50];
    char email[100];
};

2D点の表現

[編集]

2D空間の点を表現するために構造体を使うことができます。

struct Point {
    double x;
    double y;
};

struct Point p1;
p1.x = 3.5;
p1.y = 2.0;

学生情報の管理

[編集]

学生の情報を管理するための構造体を定義できます。

struct Student {
    int id;
    char name[50];
    double gpa;
};

車の情報の管理

[編集]

車の情報を管理するための構造体を作成できます。

struct Car {
    char model[50];
    int year;
    double engine_capacity;
};

複雑なデータ型の作成

[編集]

構造体の中に他の構造体を含めることで、より複雑なデータ型を作成できます。

struct Address {
    char street[100];
    char city[50];
    char state[50];
    int zip;
};

struct Person {
    char name[50];
    struct Address address;
};

関数の引数や戻り値

[編集]

構造体を関数の引数や戻り値として使用することで、複数の値を一度に渡したり受け取ったりできます。

struct Rectangle {
    double width;
    double height;
};

double calculateArea(struct Rectangle rect) {
    return rect.width * rect.height;
}

リストやスタック、キューの実装

[編集]

構造体を使用して、リストやスタック、キューなどのデータ構造を実装することができます。

struct Node {
    int data;
    struct Node* next;
};

struct Stack {
    struct Node* top;
};

画像データの表現

[編集]

画像のピクセル情報を構造体で表現できます。

struct Pixel {
    unsigned char red;
    unsigned char green;
    unsigned char blue;
};

struct Image {
    int width;
    int height;
    struct Pixel* pixels;
};
まとめ

C言語の構造体は、さまざまなデータを整理し、関連するデータをまとめるのに非常に便利です。これにより、プログラムの可読性が向上し、データの管理が簡単になります。

C言語の構造体のベストプラクティス

[編集]

C言語の構造体を効果的に使用するためのベストプラクティスは、コードの可読性、保守性、パフォーマンスを向上させるために重要です。以下にいくつかのベストプラクティスを示します。

明確な命名規則

[編集]
  • わかりやすい名前を使用: 構造体やメンバーの名前は、その内容や目的を明示するようにします。
      struct Employee {
          int id;
          char name[50];
          double salary;
      };
    

カプセル化

[編集]
  • 構造体を使ったカプセル化: 構造体を使用することで、データとそれに関連する操作をまとめて管理できます。
  • メンバーへの直接アクセスを避ける: 可能であれば、構造体のメンバーに直接アクセスするのではなく、アクセス用の関数を定義します。

適切なメモリ管理

[編集]
  • 動的メモリの使用: 構造体のメンバーとしてポインタを使用する場合、適切にメモリを管理します。必要に応じてmallocfreeを使用し、メモリリークを防ぎます。
      struct DynamicArray {
          int* array;
          size_t size;
      };
    
      struct DynamicArray createArray(size_t size) {
          struct DynamicArray arr;
          arr.size = size;
          arr.array = (int*)malloc(size * sizeof(int));
          return arr;
      }
    
      void freeArray(struct DynamicArray* arr) {
          free(arr->array);
          arr->array = NULL;
      }
    

初期化

[編集]
  • 構造体の初期化を行う: 構造体を使用する前に初期化を行い、未初期化のメンバーを参照しないようにします。
      struct Point {
          int x;
          int y;
      };
    
      struct Point p = {0, 0}; // 初期化
    

不要なメンバーの排除

[編集]
  • メンバーは必要最低限に: 構造体には、必要なメンバーだけを含め、冗長なデータを避けます。

互換性の考慮

[編集]
  • バージョン管理: 構造体が将来的に変更される可能性がある場合、バージョン管理を行い、互換性を保つようにします。例えば、バージョン番号をメンバーとして追加することができます。
      struct Employee {
          int version;
          int id;
          char name[50];
          double salary;
      };
    

整合性の確保

[編集]
  • 構造体のメンバーに対する整合性の保持: 構造体のメンバー間の整合性を保つために、構造体を操作する関数を通じてメンバーを変更します。

サイズの確認

[編集]
  • 構造体のサイズを確認: 構造体が大きくなりすぎると、パフォーマンスに影響を与えることがあります。sizeofを使用して、構造体のサイズを確認し、必要に応じて最適化します。

typedefの利用

[編集]
  • typedefを使って可読性を向上: 構造体を定義する際に、typedefを使用して可読性を向上させます。
      typedef struct {
          int x;
          int y;
      } Point;
    

コメントを活用

[編集]
  • 明確なコメントを書く: 構造体やそのメンバーの意図や使用方法について、適切なコメントを追加します。
まとめ

これらのベストプラクティスに従うことで、C言語の構造体を効果的に使用し、コードの可読性と保守性を高めることができます。構造体は、関連するデータを整理する強力なツールですが、適切に管理することが重要です。

構造体に関する標準ライブラリ関数

[編集]

標準ライブラリ関数のリストと説明

[編集]

C言語の標準ライブラリには、構造体操作に役立つ多くの関数が用意されています。例えば、memcpy関数やmemcmp関数は、構造体のメモリのコピーと比較に使用できます。

#include <string.h>

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

int main() {
    struct Person p1 = {"Alice", 25};
    struct Person p2;

    // 構造体のメモリをコピーする例
    memcpy(&p2, &p1, sizeof(struct Person));

    // 構造体の比較を行う例
    if (memcmp(&p1, &p2, sizeof(struct Person)) == 0) {
        printf("p1 and p2 are equal\n");
    } else {
        printf("p1 and p2 are not equal\n");
    }

    return 0;
}

使用例

[編集]

構造体を効率的に操作するための標準ライブラリ関数の使用例を示しています。これらの関数は、特に大規模なデータ処理や高度な操作が必要な場合に便利です。

よくあるエラーとその対処法

[編集]

構造体に関する一般的なエラー

[編集]

構造体の使用中によく見られるエラーとその対処法についてのヒントを提供します。例えば、ポインタを使った構造体の操作でのメモリリークやNULLポインタの処理などがあります。

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

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

void printPerson(struct Person *p) {
    if (p != NULL) {
        printf("Name: %s, Age: %d\n", p->name, p->age);
    } else {
        printf("Invalid person\n");
    }
}

int main() {
    struct Person *p = malloc(sizeof(struct Person));
    if (p == NULL) {
        perror("Failed to allocate memory");
        return 1;
    }

    strcpy(p->name, "Bob");
    p->age = 30;

    printPerson(p);

    free(p);
    return 0;
}

この附録は、標準ライブラリ関数のリファレンスと共に、構造体に関する一般的なエラーとその対処法を提供し、より効率的なプログラミングをサポートします。