struct(構造体)
『C++』の struct(構造体)は、複数の変数をひとまとめにして扱うためのデータ集約の仕組みです。C++ では class と同様にメンバ関数やコンストラクタを定義でき、デフォルトのアクセス修飾子が public である点だけが異なります。値オブジェクトや POD(Plain Old Data)型の定義に広く使われます。
構文
// ========================================
// struct の基本構文
// ========================================
// メンバ変数を並べるだけの最もシンプルな構造体です
struct Fighter {
std::string name; // 戦士の名前です
int power; // 戦闘力です
bool isSaiyan; // サイヤ人かどうかを示します
};
// C++11 以降: 集成体初期化(メンバの宣言順に値を渡します)
Fighter goku = {"孫悟空", 9000, true};
// C++20 以降: 指示初期化子(メンバ名を指定して初期化します)
// Fighter goku = {.name = "孫悟空", .power = 9000, .isSaiyan = true};
// メンバへのアクセスはドット演算子を使います
std::cout << goku.name << " の戦闘力: " << goku.power << std::endl;
// ポインタ経由のアクセスはアロー演算子を使います
Fighter* ptr = &goku;
std::cout << ptr->name << std::endl;
// ========================================
// コンストラクタを持つ構造体の構文
// ========================================
struct Planet {
std::string name;
int population;
// コンストラクタ(class と同様に定義できます)
Planet(const std::string& n, int pop) : name(n), population(pop) {}
// メンバ関数も定義できます
void show() const {
std::cout << name << " (人口: " << population << ")" << std::endl;
}
};
Planet namek("ナメック星", 100);
namek.show();
構文一覧
| 構文・概念 | 概要 |
|---|---|
| struct 型名 { ... }; | 構造体を定義します。メンバ変数・関数・コンストラクタをまとめて記述します。 |
| 集成体初期化 { v1, v2, ... } | コンストラクタなしの構造体を宣言順の値で一括初期化します。C++11 以降で使えます。 |
| メンバ初期化子リスト | コンストラクタの : member(val) 記法で効率よくメンバを初期化します。 |
| ドット演算子 ( . ) | 実体(変数)のメンバにアクセスします。 |
| アロー演算子 ( -> ) | ポインタ経由でメンバにアクセスします。ptr->m は (*ptr).m と同じ意味です。 |
| class との違い | デフォルトのアクセス修飾子が public(class は private)である点だけが異なります。 |
| POD 型 | コンストラクタ・デストラクタ・仮想関数を持たない単純なデータ集合体です。メモリレイアウトが C の struct と互換で、memcpy が安全に使えます。 |
サンプルコード
dragonball_struct.cpp
// ========================================
// dragonball_struct.cpp
// ドラゴンボールのキャラクターを構造体で表現し
// 集成体初期化・コンストラクタ・配列での管理を
// 一通り確認するサンプルです
// ========================================
#include <iostream>
#include <string>
#include <vector>
// ========================================
// 構造体: Race(種族の簡易情報)
// コンストラクタを持たない集成体として定義します
// ========================================
struct Race {
std::string name; // 種族名です
bool canFly; // 自力飛行できるかどうかです
};
// ========================================
// 構造体: Technique(必殺技)
// コンストラクタを持たない集成体として定義します
// ========================================
struct Technique {
std::string name; // 技の名前です
int power; // 技の威力(概算値)です
};
// ========================================
// 構造体: Fighter(戦士の情報)
// コンストラクタとメンバ関数を持つ構造体です
// ========================================
struct Fighter {
std::string name; // 戦士の名前です
int battlePower; // 戦闘力です
Race race; // 種族情報です(構造体のネストです)
Technique signature; // 代表的な必殺技です
// コンストラクタで全メンバを一括初期化します
Fighter(
const std::string& name,
int battlePower,
const Race& race,
const Technique& signature
) : name(name),
battlePower(battlePower),
race(race),
signature(signature)
{}
// プロフィールを表示するメンバ関数です
void showProfile() const {
std::cout << "┌─────────────────────────────" << std::endl;
std::cout << "│ 名前 : " << name << std::endl;
std::cout << "│ 戦闘力 : " << battlePower << std::endl;
std::cout << "│ 種族 : " << race.name
<< (race.canFly ? "(飛行可能)" : "(飛行不可)") << std::endl;
std::cout << "│ 必殺技 : " << signature.name
<< "(威力 " << signature.power << ")" << std::endl;
std::cout << "└─────────────────────────────" << std::endl;
}
// 戦闘力を比較するメンバ関数です
bool isStrongerThan(const Fighter& other) const {
return battlePower > other.battlePower;
}
};
// ========================================
// ポインタを受け取って情報を表示する関数です
// アロー演算子 (->) を使ってメンバにアクセスします
// ========================================
void printFighterPointer(const Fighter* f) {
std::cout << "[ポインタ経由] " << f->name
<< " 戦闘力: " << f->battlePower << std::endl;
}
int main() {
// ----------------------------------------
// 種族と必殺技は集成体初期化で生成します
// ----------------------------------------
Race saiyan = {"サイヤ人", true};
Race namekian = {"ナメック星人", true};
Race frieza = {"フリーザ族", true};
Technique kamehameha = {"かめはめ波", 8500};
Technique galickGun = {"ギャリック砲", 7800};
Technique makankosappo = {"魔貫光殺砲", 6000};
Technique masenkou = {"魔閃光", 5000};
Technique deathBeam = {"デスビーム", 9500};
// ----------------------------------------
// Fighter をコンストラクタで初期化します
// ----------------------------------------
Fighter goku ("孫悟空", 9000, saiyan, kamehameha);
Fighter vegeta ("ベジータ", 8500, saiyan, galickGun);
Fighter piccolo ("ピッコロ", 3500, namekian, makankosappo);
Fighter gohan ("孫悟飯", 4000, saiyan, masenkou);
Fighter freezer ("フリーザ", 12000, frieza, deathBeam);
// ----------------------------------------
// 全員のプロフィールを表示します
// ----------------------------------------
std::cout << "=== Z戦士 プロフィール一覧 ===" << std::endl << std::endl;
std::vector<Fighter*> fighters;
fighters.push_back(&goku);
fighters.push_back(&vegeta);
fighters.push_back(&piccolo);
fighters.push_back(&gohan);
fighters.push_back(&freezer);
for (int i = 0; i < (int)fighters.size(); i++) {
fighters[i]->showProfile();
std::cout << std::endl;
}
// ----------------------------------------
// ポインタ経由のアクセス確認です
// ----------------------------------------
std::cout << "=== アロー演算子によるアクセス ===" << std::endl;
for (int i = 0; i < (int)fighters.size(); i++) {
printFighterPointer(fighters[i]);
}
std::cout << std::endl;
// ----------------------------------------
// 戦闘力の比較をメンバ関数で行います
// ----------------------------------------
std::cout << "=== 戦闘力比較 ===" << std::endl;
if (freezer.isStrongerThan(goku)) {
std::cout << freezer.name << " は " << goku.name
<< " より強いです。" << std::endl;
} else {
std::cout << goku.name << " は " << freezer.name
<< " より強いです。" << std::endl;
}
// ----------------------------------------
// 最強の戦士を探します
// ----------------------------------------
Fighter* strongest = fighters[0];
for (int i = 1; i < (int)fighters.size(); i++) {
if (fighters[i]->battlePower > strongest->battlePower) {
strongest = fighters[i];
}
}
std::cout << "最強の戦士: " << strongest->name
<< "(戦闘力 " << strongest->battlePower << ")" << std::endl;
return 0;
}
# コンパイルします g++ -std=c++11 dragonball_struct.cpp -o dragonball_struct && ./dragonball_struct === Z戦士 プロフィール一覧 === ┌───────────────────────────── │ 名前 : 孫悟空 │ 戦闘力 : 9000 │ 種族 : サイヤ人(飛行可能) │ 必殺技 : かめはめ波(威力 8500) └───────────────────────────── ┌───────────────────────────── │ 名前 : ベジータ │ 戦闘力 : 8500 │ 種族 : サイヤ人(飛行可能) │ 必殺技 : ギャリック砲(威力 7800) └───────────────────────────── ┌───────────────────────────── │ 名前 : ピッコロ │ 戦闘力 : 3500 │ 種族 : ナメック星人(飛行可能) │ 必殺技 : 魔貫光殺砲(威力 6000) └───────────────────────────── ┌───────────────────────────── │ 名前 : 孫悟飯 │ 戦闘力 : 4000 │ 種族 : サイヤ人(飛行可能) │ 必殺技 : 魔閃光(威力 5000) └───────────────────────────── ┌───────────────────────────── │ 名前 : フリーザ │ 戦闘力 : 12000 │ 種族 : フリーザ族(飛行可能) │ 必殺技 : デスビーム(威力 9500) └───────────────────────────── === アロー演算子によるアクセス === [ポインタ経由] 孫悟空 戦闘力: 9000 [ポインタ経由] ベジータ 戦闘力: 8500 [ポインタ経由] ピッコロ 戦闘力: 3500 [ポインタ経由] 孫悟飯 戦闘力: 4000 [ポインタ経由] フリーザ 戦闘力: 12000 === 戦闘力比較 === フリーザ は 孫悟空 より強いです。 最強の戦士: フリーザ(戦闘力 12000)
ネストと配列を使った構造体の追加パターン
dragonball_struct2.cpp
// ========================================
// dragonball_struct2.cpp
// 構造体の配列・比較関数・デフォルトメンバ初期化を示すサンプルです
// ========================================
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
// デフォルトメンバ初期化を使った構造体(C++11 以降)
struct Fighter {
std::string name;
int power = 0; // デフォルト値を設定します
bool isSaiyan = false;
// 比較演算子(戦闘力で並べ替えるために定義します)
bool operator<(const Fighter& other) const {
return power < other.power;
}
};
// 構造体を引数に取る関数です(const 参照で受け取ります)
void printFighter(const Fighter& f) {
std::cout << f.name
<< " 戦闘力=" << f.power
<< (f.isSaiyan ? " [サイヤ人]" : "")
<< std::endl;
}
int main() {
// 集成体初期化で配列を作ります
std::vector<Fighter> fighters = {
{"孫悟空", 9000000, true},
{"ベジータ", 8500000, true},
{"ピッコロ", 3200000, false},
{"クリリン", 1500000, false},
{"フリーザ", 120000000, false},
};
std::cout << "=== 戦闘力順(昇順)===" << std::endl;
std::sort(fighters.begin(), fighters.end()); // operator< で並べ替えます
for (int i = 0; i < (int)fighters.size(); i++) {
printFighter(fighters[i]);
}
return 0;
}
# コンパイルします g++ -std=c++11 dragonball_struct2.cpp -o dragonball_struct2 # 実行します ./dragonball_struct2 === 戦闘力順(昇順)=== クリリン 戦闘力=1500000 ピッコロ 戦闘力=3200000 ベジータ 戦闘力=8500000 [サイヤ人] 孫悟空 戦闘力=9000000 [サイヤ人] フリーザ 戦闘力=120000000
よくあるミス: struct と class のアクセス修飾子の違い忘れ
struct のデフォルトのアクセス修飾子は public、class のデフォルトは private です。この違いを意識せずに struct を class に書き換えたり、その逆を行うと、メンバが意図せずアクセスできなくなったり、逆に公開されてしまったりします。
NG: class のデフォルト private でコンパイルエラー
// class: デフォルト private なので外部からアクセスできない
class FighterC {
std::string name; // private(アクセス修飾子を省略したため)
int power; // private
};
FighterC fc;
// fc.name = "ベジータ"; // コンパイルエラー(private メンバへのアクセス)
OK: struct はそのまま使え、class は public を明示する
// struct: デフォルト public なので外部から直接アクセスできる
struct FighterS {
std::string name; // public
int power; // public
};
FighterS f;
f.name = "孫悟空"; // OK
// class で public を明示する
class FighterC2 {
public:
std::string name;
int power;
};
FighterC2 fc2;
fc2.name = "ベジータ"; // OK
よくあるミス: 集成体初期化のメンバ順序間違い
集成体初期化({ v1, v2, ... })は、メンバの宣言順に値を渡します。宣言順と異なる順序で初期化しようとしても、コンパイラはメンバ名を確認しないため、型が一致すれば誤った値が黙って代入されます。C++20 の指示初期化子({ .name = ..., .power = ... })を使うと順序に依存しないため、ミスを防げます。
NG: 順序を間違えると型が違う場合のみコンパイルエラー、同じ型なら通ってしまう
struct Fighter {
std::string name;
int power;
bool isSaiyan;
};
// NG: 型が異なる場合はコンパイルエラーで検出できる
// Fighter f = {9000000, "孫悟空", true};
// → name に int を渡そうとするのでコンパイルエラー
// NG: 型が一致してしまうケースでは黙って通る
// (例: 2つの int フィールドの順番を入れ替えても検出されない)
OK: 宣言順通りに渡す、または C++20 の指示初期化子を使う
Fighter f1 = {"孫悟空", 9000, true}; // 宣言順通り
Fighter f2 = {"ベジータ", 8500, false}; // 宣言順通り
// C++20: 指示初期化子でメンバ名を指定する(順序に依存しない)
// Fighter f3 = {.name = "ピッコロ", .power = 3200, .isSaiyan = false};
概要
struct は複数の変数をひとつの型としてまとめるデータ集約の仕組みです。C++ の struct は C の struct を拡張しており、コンストラクタ・デストラクタ・メンバ関数・アクセス修飾子・継承をすべて利用できます。class との唯一の違いはデフォルトのアクセス修飾子が public(class は private)である点だけです。一般的には、メンバをすべて公開してデータを保持するだけの型には struct、カプセル化が必要な型には class を使うというコーディング規約が広く採用されています。コンストラクタを持たないシンプルな struct は集成体初期化({ v1, v2, ... })で手軽に生成でき、構造体同士をネストすれば階層的なデータモデルを簡潔に表現できます。また、仮想関数・コンストラクタ・デストラクタを持たない構造体は POD 型として扱われ、C のコードや低レベル API とのバイナリ互換が保証されます。クラス(class)やコンストラクタと合わせて学ぶと理解が深まります。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。