コンテンツにスキップ

C++/構造体

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

はじめに

[編集]

構造体(struct)は、異なる種類のデータをまとめて1つのデータ型として定義するための仕組みです。C++における構造体は、複数のメンバー変数を持ち、それぞれが異なるデータ型を持つことができます。この章では、構造体の基本的な概念から応用までを学んでいきます。

基本的な構造体の定義

[編集]

構造体は、structキーワードを使用して定義されます。以下は、基本的な構造体の定義の例です。

#include <iostream>
#include <string>

// Person構造体の定義
struct Person {
    std::string name; // 名前
    int age;          // 年齢
};

この例では、Personという構造体が定義されており、nameageという2つのメンバー変数を持っています。nameは文字列型(std::string)であり、ageは整数型(int)です。

構造体のコンストラクタ

[編集]

構造体には、メンバー変数を初期化するためのコンストラクタを定義することができます。以下は、コンストラクタを使用してPerson構造体のインスタンスを初期化する例です。

// Person構造体の定義
struct Person {
    std::string name; // 名前
    int age;          // 年齢

    // コンストラクタの定義
    Person(const std::string& n, int a) : name(n), age(a) {}
};

このコンストラクタは、名前と年齢の引数を受け取り、それらをメンバー変数に代入しています。

構造体はメンバーがpublicなので、後述の集成体初期化を使うのが一般的です。

メンバー関数

[編集]

構造体内には、メンバー関数を定義することもできます。以下は、Person構造体にintroduce()というメンバー関数を追加した例です。

// Person構造体の定義
struct Person {
    std::string name; // 名前
    int age;          // 年齢

    // コンストラクタの定義
    Person(const std::string& n, int a) : name(n), age(a) {}

    // 自己紹介をするメンバー関数
    void introduce() const {
        std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

このintroduce()関数は、構造体のインスタンスを引数なしで呼び出すことができ、そのインスタンスの情報を出力します。

集成体初期化(Aggregate Initialization)

[編集]

集成体初期化は、構造体のメンバー変数を初期化するための簡潔な方法です。以下は、集成体初期化を使用してPerson構造体のインスタンスを初期化する例です。

#include <iostream>
#include <string>

struct Person {
    std::string name; // 名前
    int age;          // 年齢
};

auto main() -> int {
    // 構造体の集成体初期化
    Person p1{"Alice", 28};
    auto p2 = Person{"Bob", 30};
    Person p3{ .name = "Charlie", .age = 8 };
    auto p4 = Person{ .name = "Dorothy", .age = 6 };
    auto p5 = Person{ .name = "Emmy", .age = 6 };

    for (auto p : {p1, p2, p3, p4, p5}) {
      std::cout << p.name << " is " << p.age << " years old." << std::endl;
    }

    return 0;
}

この例では、Person構造体のインスタンスp1p2を集成体初期化を使って初期化しています。

不変なメンバー変数の構造体

[編集]

不変なメンバー変数を持つ構造体を定義することもできます。以下はその例です。

// 不変なメンバー変数を持つPerson構造体の定義
struct Person {
    const std::string name; // 名前(不変)
    const int age;          // 年齢(不変)

    // コンストラクタの定義
    Person(const std::string& n, int a) : name(n), age(a) {}
};

この例では、nameageconst修飾子で宣言されており、初期化後に変更されることはありません。

このようにして、構造体を使ってデータをまとめ、簡潔かつ効果的に扱うことができます。次のセクションでは、構造体の配列やネストについて学んでいきましょう。

構造体の配列とベクター

[編集]

構造体の配列を使うことで、複数のデータを効率的に管理することができます。以下は、構造体の配列を使用する例です。

#include <iostream>
#include <vector>
#include <string>

// Person構造体の定義
struct Person {
    std::string name; // 名前
    int age;          // 年齢

    // コンストラクタの定義
    Person(const std::string& n, int a) : name(n), age(a) {}
};

auto main() -> int {
    // 構造体の配列
    Person peopleArray[2] = { {"Alice", 30}, {"Bob", 25} };

    for (const auto& person : peopleArray) {
        std::cout << person.name << " is " << person.age << " years old." << std::endl;
    }

    // ベクター
    std::vector<Person> peopleVector = { {"Charlie", 35}, {"Dave", 40} };

    for (const auto& person : peopleVector) {
        std::cout << person.name << " is " << person.age << " years old." << std::endl;
    }

    return 0;
}

この例では、Person構造体の配列とベクターを使って複数の人物情報を管理しています。

構造体のネスト

[編集]

構造体の中に別の構造体を定義することができます。これを構造体のネストと呼びます。以下は、ネストした構造体を使用する例です。

#include <iostream>
#include <string>

// Address構造体の定義
struct Address {
    std::string city;   // 都市
    std::string street; // 通り
    int number;         // 番地
};

// Person構造体の定義
struct Person {
    std::string name;   // 名前
    int age;            // 年齢
    Address address;    // 住所

    // コンストラクタの定義
    Person(const std::string& n, int a, const Address& addr)
        : name(n), age(a), address(addr) {}
};

auto main() -> int {
    Address addr{"New York", "5th Avenue", 123};
    Person p1{"Alice", 30, addr};

    std::cout << p1.name << " lives at " << p1.address.street << ", " << p1.address.city << "." << std::endl;

    return 0;
}

この例では、Person構造体の中にAddress構造体がネストしており、人物情報と住所情報が関連付けられています。

構造体の利用例

[編集]

構造体を使用したプログラムの例を見てみましょう。

#include <iostream>
#include <vector>
#include <string>

struct Person {
    std::string name;
    int age;

    Person(const std::string& name, int age) : name(name), age(age) {}

    void introduce() const {
        std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

auto main() -> int {
    std::vector<Person> people = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    for (const auto& person : people) {
        person.introduce();
    }

    return 0;
}

このプログラムでは、Person構造体を使用して複数の人物情報を管理し、各人物の自己紹介を出力しています。

structとclassの違い

[編集]

C++において、structclassは非常に似ていますが、いくつかの違いがあります。これらの違いは、主にアクセス制御とデフォルトの継承形式に関連しています。C++20の新機能を考慮して、structclassの違いを以下に示します。

デフォルトのアクセス制御
struct
メンバー変数やメンバー関数はデフォルトでpublicとなります。つまり、structで定義されたメンバーは、外部から直接アクセス可能です。
class
メンバー変数やメンバー関数はデフォルトでprivateとなります。つまり、classで定義されたメンバーは、外部からの直接アクセスが制限されます。
デフォルトの継承形式
struct
デフォルトでpublic継承が設定されています。つまり、structから派生したクラスは、publicprotected、およびprivateのすべての基底クラスメンバーにアクセスできます。
class
デフォルトでprivate継承が設定されています。つまり、classから派生したクラスは、publicprotectedの基底クラスメンバーにのみアクセスできます。
利用目的の違い
一般的に、structはデータのまとまりを表現し、classはオブジェクト指向プログラミングの機能をより豊富に使用して、データとそれに関連する操作をカプセル化します。
structはC言語との互換性を持ち、C言語で使われるstructと同様に振る舞います。そのため、簡易なデータ構造の定義に使用されることがあります。
派生クラスのアクセス性
structを基底クラスとする場合、デフォルトで派生クラスのメンバーはpublicになります。
classを基底クラスとする場合、デフォルトで派生クラスのメンバーはprivateになります。

C++20では、structclassの違いは主に上記の点にありますが、言語の進化により両者の間にはますます曖昧な境界が存在します。一般的なガイドラインとしては、データのまとまりを表現する場合にはstruct、オブジェクト指向プログラミングの機能を利用してデータと振る舞いをカプセル化する場合にはclassを使用することが推奨されています。

まとめと練習問題

[編集]

この章では、構造体の基本的な使い方から応用までを学びました。構造体を使うことで、関連するデータをまとめて効率的に管理できるようになります。

練習問題:

  1. 自分の情報(名前、年齢、趣味など)を保持するstructを定義し、それを使って自分の情報を出力するプログラムを書きなさい。
  2. 複数のメンバー関数を持つstructを定義し、各メンバー関数の使い方を示すプログラムを書きなさい。
  3. constメンバー変数を持つ構造体を定義し、その構造体を使って初期化するプログラムを書きなさい。
  4. 構造体の配列とベクターを使って複数のデータを管理するプログラムを書きなさい。
  5. ネストした構造体を使って、住所情報を含む人物情報を管理するプログラムを書きなさい。