なぜデータをまとめるのか
これまで、すべての変数はそれぞれ独立していました。ここに int、あそこに string。しかし実際のプログラムは、いくつもの部品からなるものを扱います。点には x と y があり、学生には名前、年齢、GPAがあります。これらをバラバラの別々の変数として受け渡すのは間違いのもとです。何も互いを結びつけておらず、3つすべてを必要とする関数は3つの引数を取らなければなりません。
構造体はこれを解決します。関連する変数――そのメンバ――を1つの単位にまとめた新しい型を定義します。いったん定義すれば、そのまとまり全体を、保存し、コピーし、関数に渡せる1つの値として扱えます。
各メンバにはドット演算子でアクセスします(s.name、s.age)。構造体定義の閉じ波括弧 } のあとのセミコロンは必須です。これを付け忘れるのは、C++初心者が最もよく遭遇するコンパイルエラーの1つです。
構造体の初期化
各フィールドを手作業で代入する方法でも動きますが、冗長で、メンバを忘れやすいです。よりすっきりした方法は集成体初期化です。メンバが宣言された順番どおりに、値を波括弧の中に並べます。
空のデフォルトには注意してください。Point p;(波括弧なし)は、組み込み型のメンバが自動的にゼロ化されないため、x と y をゴミ値のまま残します。Point p{}; はそれらを値初期化で 0 にします。波括弧を優先しましょう。デフォルト値を定義に直接埋め込むこともでき、そうすれば Point p; でさえきれいな状態で始まります。
関数のパラメータとしての構造体
構造体は1つの値なので、関数は3つではなく1つの引数を取れます。ただし、構造体を値渡しするとすべてのメンバがコピーされることを覚えておいてください。int数個より大きいものなら、コピーを省くために const 参照で渡しましょう――参照のページで見たのと同じルールです。
構造体まるごとを関数から返すのは、複数の値を一度に返すための慣用的な方法であり、複数の出力参照を扱うよりはるかにすっきりします。
メンバ関数とコンストラクタの追加
構造体はデータだけに限られません。自身のメンバを操作するメンバ関数や、オブジェクトが作られた瞬間にそれを初期化するコンストラクタを持てます。ここで構造体は、振る舞いを備えた小さなオブジェクトのように見えはじめます。
メンバ関数の中では、メンバには名前でアクセスします(width、height)。これらはこのオブジェクトのコピーを指します。area() const と印を付けると、その関数はオブジェクトを読むだけだとコンパイラに伝わり、const Rectangle の値に対しても呼び出せるようになります。コンストラクタは、あの : width(w) という初期化子リストの構文を含め、それ自体が独立したトピックです――コンストラクタのページで深く掘り下げます。
struct と class: 本当の違い
「構造体はデータ用、クラスはオブジェクト用」と聞いたことがあるかもしれません。それは慣習であって、言語のルールではありません。C++では struct と class はほとんど同じもので、唯一の組み込みの違いはデフォルトのアクセスレベルです。
struct S {
int x; // デフォルトで public
};
class C {
int x; // デフォルトで private
};
struct のメンバは、別段の印を付けない限り public です。class のメンバは private で始まります。それだけです――どちらもコンストラクタ、メンバ関数、継承、その他すべてを持てます。メンバを隠すために、構造体に明示的なアクセス指定子を付けることさえできます。
実用上の要点はこうです。すべてのフィールドが自由に触れられることを意図した透明なデータのまとまり(Point、Color、設定レコード)には struct を使い、内部状態を公開インターフェースの背後で守りたいときには class を使いましょう。コンパイラは両者を同じに扱います。キーワードはあなたの意図を示すだけです。
構造体の配列とベクター
構造体はごく普通の型なので、たくさんの構造体を配列やベクターに入れて、他のどんな値とも同じようにループで回せます。
入れ子の波括弧に注目してください。各 {"Keyboard", 49.99} が1つの Product を集成体初期化し、外側の波括弧がベクターを組み立てます。範囲ベースのループで const Product& を使うと、繰り返しのたびに各構造体をコピーせずに済みます。& を外すと、各要素を無駄に複製してしまいます。
次は: 列挙型(Enums)
構造体を使うと、複数の値をまとめて1つの型を作れます。次の章は逆方向に進みます。列挙型(enum)は、小さな名前付きの集合の中からちょうど1つの値を保持できる型を定義します――Color::Red、Direction::North、あるいはステートマシンの状態のようなものに最適です。enum class が、いま組み立てている構造体やクラスと自然に組み合わさる、読みやすく型安全な定数をどのように与えてくれるかを見ていきます。
よくある質問
C++の構造体(struct)とは何ですか?
struct は、関連する複数の変数(メンバと呼びます)を1つの名前のもとにまとめる、ユーザー定義の型です。string name、int age、double gpa という変数を別々に扱う代わりに、それらを1つの Student 型にまとめ、その1つのオブジェクトを受け渡しします。構造体はメンバ関数やコンストラクタも持てます。現代のC++では、構造体はメンバがデフォルトで public になっている、れっきとしたクラスです。
C++におけるstructとclassの違いは何ですか?
技術的にはデフォルトのアクセスレベルだけです。struct のメンバは明示しない限り public で、class のメンバはデフォルトで private です。継承についても同様です。それ以外のすべて(コンストラクタ、メンバ関数、メソッド、継承)はまったく同じように動作します。慣習として、プログラマは単純なデータのまとまりには struct を、不変条件や隠された内部を持つ型には class を使います。
C++で構造体を初期化するにはどうすればよいですか?
最も簡単な方法は、メンバの順序どおりに波括弧で並べる**集成体初期化(aggregate initialization)**です: Point p{3, 4};。ドット演算子で各フィールドを手作業で代入する(p.x = 3;)、定義でメンバにデフォルト値を与える(int x = 0;)、あるいは構造体が自身を準備するようコンストラクタを書く、という方法もあります。波括弧による初期化は、フィールドを黙って未初期化のまま残さないので推奨されます。