C++入門|4章を例題で確認する

戦士が修行カプセルに入った瞬間、気の値は自動で初期化される。コンストラクタを使えば、C++オブジェクトは誕生した瞬間から安全に動き出します。

ここまでの4章では、C++のクラスに用意できる特別なメンバ関数として、コンストラクタデストラクタを学びました。

コンストラクタは、インスタンスが生成された瞬間に自動で呼び出される特別なメンバ関数です。
デストラクタは、インスタンスが消える直前に自動で呼び出される特別なメンバ関数です。

今回の例題では、よりドラゴンボールの世界観に寄せて、修行カプセルを表す KiTrainingCapsule クラスを作ります。

このクラスでは、戦士名、流派、現在の気の出力、総蓄積エネルギーを管理します。

ドラゴンボール風にたとえるなら、戦士が近未来の修行カプセルに入った瞬間、カプセルが自動起動して、内部の気のメーターを初期値に整えるイメージです。

C++では、この「インスタンス生成時に自動で初期化する処理」をコンストラクタで書きます。

今回の例題では、コンストラクタを使って、次のような初期化を行います。

メンバ変数初期値意味
m_name未登録戦士まだ戦士名が設定されていない状態
m_style未設定流派まだ流派が設定されていない状態
m_power0.0気の出力が0の状態
m_totalEnergy0.0修行で蓄積した気が0の状態

このように、インスタンスが作られた時点で安全な初期状態にしておくと、その後の処理が分かりやすくなります。

図:修行カプセル生成時にコンストラクタが自動起動する

この図が示していること

この図では、KiTrainingCapsule capsule;によってインスタンスが生成された瞬間に、コンストラクタが自動的に呼び出されることを表しています。

コンストラクタによって、戦士名、流派、気の出力、総蓄積エネルギーが初期化されます。
そのため、修行カプセルは生成直後から安全な状態で使い始められます。

例題4-1:修行カプセルにコンストラクタを追加する

問題文

以下の仕様の、修行カプセルを表す KiTrainingCapsule クラスを作りなさい。

KiTrainingCapsule クラスには、戦士名、流派、現在の気の出力、総蓄積エネルギーを表すメンバ変数を持たせます。

また、コンストラクタを追加し、インスタンス生成時に各メンバ変数を安全な初期値に設定してください。

さらに、戦士名と流派と気の出力を設定したあと、指定した時間だけ修行し、総蓄積エネルギーを表示するプログラムを作成しなさい。

メンバ変数

変数名概要
stringm_name戦士名
stringm_style流派
doublem_power現在の気の出力
doublem_totalEnergy総蓄積エネルギー

メンバ関数

戻り値の型関数名引数概要
なしKiTrainingCapsuleなしコンストラクタ。各メンバ変数を初期化する
voidsetNamestring name戦士名を設定する
voidsetStylestring style流派を設定する
voidsetPowerdouble power気の出力を設定する
doublegetTotalEnergyなし総蓄積エネルギーを取得する
voidtraindouble hour指定した時間だけ修行する
voidshowStatusなし現在の戦士情報を表示する

期待される実行結果

修行カプセルを起動しました。
戦士名:Leo
流派:亀仙流
現在の気の出力:0
総蓄積エネルギー:0
Leoが亀仙流の構えで修行開始!
気の出力400で1.5時間修行
600ポイントの気を蓄積しました。
Leoが亀仙流の構えで修行開始!
気の出力650で2時間修行
1300ポイントの気を蓄積しました。
総蓄積エネルギー:1900ポイント

この例題では、コンストラクタによって、インスタンス生成直後の状態を整えます。

そのあと、setName、setStyle、setPower、trainを使って、戦士が修行カプセルで気を蓄積していく流れを確認します。

解答例と解説

今回のプログラムは、次の3つのファイルに分けて作成します。

ファイル役割
ki_training_capsule.hKiTrainingCapsuleクラスの宣言を書く
ki_training_capsule.cppコンストラクタとメンバ関数の定義を書く
main.cppインスタンスを生成して修行処理を実行する

クラスの宣言、メンバ関数の定義、実際に使う処理を分けることで、C++らしいファイル分割の形も確認できます。

KiTrainingCapsuleクラスの宣言

まず、ヘッダーファイルに KiTrainingCapsule クラスの宣言を書きます。

プロジェクト名/ファイル名: Ex4_1/ki_training_capsule.h

#ifndef _KI_TRAINING_CAPSULE_H_
#define _KI_TRAINING_CAPSULE_H_

#include <string>

class KiTrainingCapsule {
public:
    // コンストラクタ
    KiTrainingCapsule();

    // 戦士名を設定する
    void setName(std::string name);

    // 流派を設定する
    void setStyle(std::string style);

    // 気の出力を設定する
    void setPower(double power);

    // 総蓄積エネルギーを取得する
    double getTotalEnergy();

    // 指定した時間だけ修行する
    void train(double hour);

    // 現在の状態を表示する
    void showStatus();

private:
    // 戦士名
    std::string m_name;

    // 流派
    std::string m_style;

    // 現在の気の出力
    double m_power;

    // 総蓄積エネルギー
    double m_totalEnergy;
};

#endif // _KI_TRAINING_CAPSULE_H_

このヘッダーファイルでは、KiTrainingCapsule クラスの設計図を書いています。

注目したいのは、次の部分です。

KiTrainingCapsule();

これはコンストラクタの宣言です。

コンストラクタには、次の特徴があります。

特徴内容
名前クラス名と同じ
戻り値書かない
呼び出しインスタンス生成時に自動で呼ばれる
主な役割メンバ変数を初期化する

今回のクラス名は KiTrainingCapsule です。
そのため、コンストラクタ名も KiTrainingCapsule になります。

通常のメンバ関数であれば、voidやdoubleなどの戻り値の型を書きます。

void setPower(double power);
double getTotalEnergy();

しかし、コンストラクタには戻り値の型を書きません。

void KiTrainingCapsule();

このようには書かないので注意しましょう。

privateで修行カプセルの内部情報を守る

このクラスでは、メンバ変数をprivateにしています。

private:
    std::string m_name;
    std::string m_style;
    double m_power;
    double m_totalEnergy;

privateにすることで、main.cppから直接アクセスできなくなります。

たとえば、次のような書き方はできません。

capsule.m_power = 400;

気の出力を設定したいときは、publicなsetPowerを使います。

capsule.setPower(400);

これは、これまでに学んだカプセル化の考え方です。

ドラゴンボール風に言えば、修行カプセルの内部メーターを外から直接いじらせず、操作パネルを通して安全に設定するイメージです。

コンストラクタとメンバ関数の定義

次に、ki_training_capsule.cpp にコンストラクタとメンバ関数の中身を書きます。

プロジェクト名/ファイル名: Ex4_1/ki_training_capsule.cpp

#include "ki_training_capsule.h"
#include <iostream>

using namespace std;

// コンストラクタ
KiTrainingCapsule::KiTrainingCapsule()
    : m_name("未登録戦士"), m_style("未設定流派"), m_power(0.0), m_totalEnergy(0.0) {
    cout << "修行カプセルを起動しました。" << endl;
}

// 戦士名を設定する
void KiTrainingCapsule::setName(string name) {
    m_name = name;
}

// 流派を設定する
void KiTrainingCapsule::setStyle(string style) {
    m_style = style;
}

// 気の出力を設定する
void KiTrainingCapsule::setPower(double power) {
    m_power = power;
}

// 総蓄積エネルギーを取得する
double KiTrainingCapsule::getTotalEnergy() {
    return m_totalEnergy;
}

// 指定した時間だけ修行する
void KiTrainingCapsule::train(double hour) {
    cout << m_name << "が" << m_style << "の構えで修行開始!" << endl;
    cout << "気の出力" << m_power << "で" << hour << "時間修行" << endl;
    cout << m_power * hour << "ポイントの気を蓄積しました。" << endl;

    // 修行で得た気を総蓄積エネルギーに加算する
    m_totalEnergy += m_power * hour;
}

// 現在の状態を表示する
void KiTrainingCapsule::showStatus() {
    cout << "戦士名:" << m_name << endl;
    cout << "流派:" << m_style << endl;
    cout << "現在の気の出力:" << m_power << endl;
    cout << "総蓄積エネルギー:" << m_totalEnergy << endl;
}

コンストラクタの定義を確認する

コンストラクタの定義は、次の部分です。

KiTrainingCapsule::KiTrainingCapsule()
    : m_name("未登録戦士"), m_style("未設定流派"), m_power(0.0), m_totalEnergy(0.0) {
    cout << "修行カプセルを起動しました。" << endl;
}

KiTrainingCapsule::KiTrainingCapsule() と書くことで、KiTrainingCapsuleクラスのコンストラクタを定義しています。

KiTrainingCapsule::KiTrainingCapsule()

クラス名と関数名が同じです。
そして戻り値の型がありません。

この形がコンストラクタの大きな特徴です。

メンバ初期化リスト

コンストラクタの次の部分を、メンバ初期化リストといいます。

: m_name("未登録戦士"), m_style("未設定流派"), m_power(0.0), m_totalEnergy(0.0)

ここでは、4つのメンバ変数を初期化しています。

メンバ変数初期値
m_name未登録戦士
m_style未設定流派
m_power0.0
m_totalEnergy0.0

この初期化は、インスタンスが生成された瞬間に行われます。

KiTrainingCapsule capsule;

この行が実行された時点で、m_name、m_style、m_power、m_totalEnergyには初期値が入ります。

ドラゴンボール風に言えば、修行カプセルが起動した瞬間に、戦士名は未登録、流派は未設定、気の出力は0、総蓄積エネルギーも0に整えられるわけです。

図:メンバ初期化リストで修行カプセルの内部を整える

この図が示していること

この図では、コンストラクタのメンバ初期化リストによって、修行カプセルの内部データが初期化されることを表しています。

m_name、m_style、m_power、m_totalEnergyは、インスタンス生成時に初期値を持ちます。
これにより、生成直後のオブジェクトが不安定な状態にならず、安全に使い始められます。

コンストラクタ本体の処理

初期化リストのあとにある波かっこの中は、コンストラクタ本体です。

{
    cout << "修行カプセルを起動しました。" << endl;
}

ここには、通常のメンバ関数と同じように処理を書けます。

今回の例では、インスタンスが生成されたことを分かりやすくするために、起動メッセージを表示しています。

修行カプセルを起動しました。

このメッセージは、main.cppで明示的に呼び出していなくても表示されます。

なぜなら、コンストラクタはインスタンス生成時に自動的に実行されるからです。

train関数で修行エネルギーを蓄積する

train関数では、指定した時間だけ修行し、気の蓄積ポイントを計算します。

void KiTrainingCapsule::train(double hour) {
    cout << m_name << "が" << m_style << "の構えで修行開始!" << endl;
    cout << "気の出力" << m_power << "で" << hour << "時間修行" << endl;
    cout << m_power * hour << "ポイントの気を蓄積しました。" << endl;

    // 修行で得た気を総蓄積エネルギーに加算する
    m_totalEnergy += m_power * hour;
}

m_powerは現在の気の出力です。
hourは修行時間です。

m_power * hourで、その修行で蓄積される気のポイントを求めます。

内容
m_power現在の気の出力
hour修行時間
m_power * hour今回の修行で得た気
m_totalEnergyこれまでの総蓄積エネルギー

たとえば、m_powerが400、hourが1.5なら、次のようになります。

400 × 1.5 = 600

そのため、600ポイントの気が蓄積されます。

次に、m_powerが650、hourが2.0なら、次のようになります。

650 × 2.0 = 1300

1回目の600ポイントと2回目の1300ポイントを合わせると、総蓄積エネルギーは1900ポイントになります。

main.cppで修行カプセルを使う

最後に、main.cppを書きます。

プロジェクト名/ファイル名: Ex4_1/main.cpp

#include <iostream>
#include "ki_training_capsule.h"

using namespace std;

int main(int argc, char** argv) {
    // 修行カプセルのインスタンスを生成する
    KiTrainingCapsule capsule;

    // 初期状態を表示する
    capsule.showStatus();

    // 戦士名と流派を設定する
    capsule.setName("Leo");
    capsule.setStyle("亀仙流");

    // 気の出力を設定して修行する
    capsule.setPower(400);
    capsule.train(1.5);

    // 気の出力をさらに高めて修行する
    capsule.setPower(650);
    capsule.train(2.0);

    // 総蓄積エネルギーを表示する
    cout << "総蓄積エネルギー:" << capsule.getTotalEnergy() << "ポイント" << endl;

    // プログラムが正常に終わったことを示す
    return 0;
}

このmain.cppでは、最初にKiTrainingCapsuleクラスのインスタンスを生成しています。

KiTrainingCapsule capsule;

この1行で、capsuleという修行カプセルオブジェクトが作られます。

そして、このタイミングでコンストラクタが自動的に呼び出されます。

KiTrainingCapsule::KiTrainingCapsule()

そのため、main.cppの中でコンストラクタを直接呼び出す必要はありません。

インスタンス生成直後の状態を確認する

main.cppでは、インスタンス生成直後にshowStatusを呼び出しています。

capsule.showStatus();

この時点では、まだsetNameやsetStyleを呼び出していません。

しかし、コンストラクタによって、すでに次の初期値が入っています。

メンバ変数
m_name未登録戦士
m_style未設定流派
m_power0.0
m_totalEnergy0.0

そのため、showStatusを呼び出すと、次のような初期状態が表示されます。

戦士名:未登録戦士
流派:未設定流派
現在の気の出力:0
総蓄積エネルギー:0

このように、コンストラクタで初期化しておくと、まだ値を設定していない段階でも、オブジェクトが安全な状態になっています。

実行結果の流れ

このプログラムの実行結果は次のようになります。

修行カプセルを起動しました。
戦士名:未登録戦士
流派:未設定流派
現在の気の出力:0
総蓄積エネルギー:0
Leoが亀仙流の構えで修行開始!
気の出力400で1.5時間修行
600ポイントの気を蓄積しました。
Leoが亀仙流の構えで修行開始!
気の出力650で2時間修行
1300ポイントの気を蓄積しました。
総蓄積エネルギー:1900ポイント

処理の流れを整理すると、次のようになります。

手順処理内容
1KiTrainingCapsule capsule;インスタンス生成。コンストラクタが呼ばれる
2m_nameなどを初期化未登録戦士、未設定流派、0.0に整える
3capsule.showStatus();初期状態を表示する
4capsule.setName("Leo");戦士名を設定する
5capsule.setStyle("亀仙流");流派を設定する
6capsule.setPower(400);気の出力を400にする
7capsule.train(1.5);600ポイント蓄積する
8capsule.setPower(650);気の出力を650にする
9capsule.train(2.0);1300ポイント蓄積する
10capsule.getTotalEnergy();1900ポイントを取得する

図:生成、初期化、修行、表示までの流れ

この図が示していること

この図では、KiTrainingCapsuleインスタンスが生成されてから、コンストラクタで初期化され、戦士情報を設定し、修行によって気を蓄積し、最後に総蓄積エネルギーを表示する流れを表しています。

KiTrainingCapsule capsule;の時点で、コンストラクタが自動実行されます。
その後、setName、setStyle、setPower、trainを使って、修行カプセルを操作します。

コンストラクタで初期化する理由

コンストラクタで初期化する理由は、インスタンスを安全な状態で使い始めるためです。

もしm_totalEnergyが初期化されていなければ、最初からどんな値が入っているか分かりません。
その状態でtrainを呼び出すと、総蓄積エネルギーの計算がおかしくなる可能性があります。

そこで、コンストラクタで次のように初期化します。

KiTrainingCapsule::KiTrainingCapsule()
    : m_name("未登録戦士"), m_style("未設定流派"), m_power(0.0), m_totalEnergy(0.0) {
    cout << "修行カプセルを起動しました。" << endl;
}

これにより、インスタンス生成直後から、すべてのメンバ変数に意味のある初期値が入ります。

ドラゴンボール風に言えば、修行カプセルを起動した瞬間に、戦士登録欄、流派設定、気の出力メーター、総エネルギーゲージがすべて整えられるイメージです。

コンストラクタは一度だけ自動で呼ばれる

コンストラクタは、インスタンスが生成されたときに一度だけ呼び出されます。

今回のmain.cppでは、次の行で呼び出されます。

KiTrainingCapsule capsule;

この時点で、コンストラクタが自動的に実行されます。

その後、setNameやtrainを何回呼び出しても、コンストラクタはもう一度呼ばれるわけではありません。

capsule.setName("Leo");
capsule.train(1.5);
capsule.train(2.0);

これらは通常のメンバ関数です。
自分で呼び出したタイミングで実行されます。

一方、コンストラクタは、インスタンス生成時にC++が自動で呼び出します。

メンバ初期化リストとコンストラクタ本体

今回のコンストラクタでは、メンバ初期化リストとコンストラクタ本体の両方を使っています。

KiTrainingCapsule::KiTrainingCapsule()
    : m_name("未登録戦士"), m_style("未設定流派"), m_power(0.0), m_totalEnergy(0.0) {
    cout << "修行カプセルを起動しました。" << endl;
}

メンバ初期化リストでは、メンバ変数に初期値を入れています。

: m_name("未登録戦士"), m_style("未設定流派"), m_power(0.0), m_totalEnergy(0.0)

コンストラクタ本体では、起動メッセージを表示しています。

cout << "修行カプセルを起動しました。" << endl;

このように、初期化リストではメンバ変数の初期値を設定し、波かっこの中では追加の処理を書く、という使い分けができます。

この例題で確認したいポイント

今回の例題では、コンストラクタの基本を、修行カプセルの起動処理として確認しました。

確認ポイント内容
コンストラクタの名前クラス名と同じ KiTrainingCapsule
戻り値書かない
自動実行KiTrainingCapsule capsule;の時点で呼ばれる
初期化m_name、m_style、m_power、m_totalEnergyを初期化する
メンバ初期化リストメンバ変数(初期値)の形で書く
通常のメンバ関数setName、setStyle、setPower、trainなどは自分で呼び出す

C++では、インスタンスを作った直後にメンバ変数がどんな状態になっているかを明確にしておくことが大切です。

コンストラクタを使えば、インスタンス生成時に自動で初期化できます。

今回のKiTrainingCapsuleクラスでは、戦士名、流派、気の出力、総蓄積エネルギーを初期化しました。

これにより、修行カプセルは生成直後から安全な状態になり、その後のsetName、setStyle、setPower、trainを安心して実行できます。

ドラゴンボール風に言えば、コンストラクタは修行カプセルの自動起動システムです。
戦士が入った瞬間に、内部メーターを整え、いつでも修行を始められる状態にしてくれます。