C++/構造体
はじめに
[編集]構造体(struct)は、異なる種類のデータをまとめて1つのデータ型として定義するための仕組みです。C++における構造体は、複数のメンバー変数を持ち、それぞれが異なるデータ型を持つことができます。この章では、構造体の基本的な概念から応用までを学んでいきます。
基本的な構造体の定義
[編集]構造体は、struct
キーワードを使用して定義されます。以下は、基本的な構造体の定義の例です。
#include <iostream> #include <string> // Person構造体の定義 struct Person { std::string name; // 名前 int age; // 年齢 };
この例では、Person
という構造体が定義されており、name
とage
という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
構造体のインスタンスp1
とp2
を集成体初期化を使って初期化しています。
不変なメンバー変数の構造体
[編集]不変なメンバー変数を持つ構造体を定義することもできます。以下はその例です。
// 不変なメンバー変数を持つPerson構造体の定義 struct Person { const std::string name; // 名前(不変) const int age; // 年齢(不変) // コンストラクタの定義 Person(const std::string& n, int a) : name(n), age(a) {} };
この例では、name
とage
はconst
修飾子で宣言されており、初期化後に変更されることはありません。
このようにして、構造体を使ってデータをまとめ、簡潔かつ効果的に扱うことができます。次のセクションでは、構造体の配列やネストについて学んでいきましょう。
構造体の配列とベクター
[編集]構造体の配列を使うことで、複数のデータを効率的に管理することができます。以下は、構造体の配列を使用する例です。
#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++において、struct
とclass
は非常に似ていますが、いくつかの違いがあります。これらの違いは、主にアクセス制御とデフォルトの継承形式に関連しています。C++20の新機能を考慮して、struct
とclass
の違いを以下に示します。
- デフォルトのアクセス制御
-
struct
- メンバー変数やメンバー関数はデフォルトで
public
となります。つまり、struct
で定義されたメンバーは、外部から直接アクセス可能です。 class
- メンバー変数やメンバー関数はデフォルトで
private
となります。つまり、class
で定義されたメンバーは、外部からの直接アクセスが制限されます。
- デフォルトの継承形式
-
struct
- デフォルトで
public
継承が設定されています。つまり、struct
から派生したクラスは、public
、protected
、およびprivate
のすべての基底クラスメンバーにアクセスできます。 class
- デフォルトで
private
継承が設定されています。つまり、class
から派生したクラスは、public
、protected
の基底クラスメンバーにのみアクセスできます。
- 利用目的の違い
- 一般的に、
struct
はデータのまとまりを表現し、class
はオブジェクト指向プログラミングの機能をより豊富に使用して、データとそれに関連する操作をカプセル化します。 struct
はC言語との互換性を持ち、C言語で使われるstruct
と同様に振る舞います。そのため、簡易なデータ構造の定義に使用されることがあります。- 派生クラスのアクセス性
struct
を基底クラスとする場合、デフォルトで派生クラスのメンバーはpublic
になります。class
を基底クラスとする場合、デフォルトで派生クラスのメンバーはprivate
になります。
C++20では、struct
とclass
の違いは主に上記の点にありますが、言語の進化により両者の間にはますます曖昧な境界が存在します。一般的なガイドラインとしては、データのまとまりを表現する場合にはstruct
、オブジェクト指向プログラミングの機能を利用してデータと振る舞いをカプセル化する場合にはclass
を使用することが推奨されています。
まとめと練習問題
[編集]この章では、構造体の基本的な使い方から応用までを学びました。構造体を使うことで、関連するデータをまとめて効率的に管理できるようになります。
練習問題:
- 自分の情報(名前、年齢、趣味など)を保持する
struct
を定義し、それを使って自分の情報を出力するプログラムを書きなさい。 - 複数のメンバー関数を持つ
struct
を定義し、各メンバー関数の使い方を示すプログラムを書きなさい。 const
メンバー変数を持つ構造体を定義し、その構造体を使って初期化するプログラムを書きなさい。- 構造体の配列とベクターを使って複数のデータを管理するプログラムを書きなさい。
- ネストした構造体を使って、住所情報を含む人物情報を管理するプログラムを書きなさい。