クラスの継承

■継承

クラスには既存のクラスを使用して新しいクラスを作成する継承という機能があります。
この機能を使用することで、既存のクラスのメンバを最初から持っている
新しいクラスを作成することができるようになりますので、
より効率的にプログラミングを行うことができます。

cpp_0001
	
●継承の方法
	継承は既存のクラスの情報をそのまま新しいクラスに利用する方法です。
	なので、継承を行うには最低二つのクラスが必要となります。
	継承の書式は継承先のクラス名の後ろに「: public 継承元クラス名」を記述します。
	※「: public 継承元クラス名」のpublicの部分はprivateなど設定を行えますが、
	 基本はpublicで問題がないので、現状はpublicと覚えてください。

	・書式例
		// 継承元
		class 継承元クラス名
		{
				
		};

		// 継承先
		class 継承先クラス名 : public 継承元クラス名
		{
				
		};

	・具体例
		// 継承元
		class Kaden
		{
		public:
			int m_KadenUriage;	// 売り上げ金

			void ShowUriage(void);	// 売り上げ表示
		};

		// 売り上げ表示
		void Kaden::ShowUriage(void)
		{
			printf("売り上げ金額 = %d円\n", m_KadenUriage);
		}

		// 継承先
		class OsakaSiten : public Kaden
		{

		};

		void main(void)
		{
			OsakaSiten osaka;
			osaka.m_KadenUriage = 1000000;
			osaka.ShowUriage();
		}
			
		表示結果:
			売り上げ金額 = 1000000円

●名称について
	継承を行う際に継承元になるクラスと継承先になるクラスは
	以下の記述している通りいくつかの名称で呼ばれていますが
	全て同じ意味ですので、書籍や資料で違う単語で書かれたとしても
	気にしないで大丈夫です。

	・継承元
		基本クラス基底クラスベースクラススーパークラス親クラス

	・継承先
		派生クラスサブクラス子クラス

	これ以降の継承の名称については継承元を基底クラス、派生クラスで統一したいと思います。

●継承の目的
	継承の目的は既存のクラスを再利用してプログラミングを効率化することです。
	例えばゲームで敵のクラスを作成する場合、大抵は敵の種類分のクラスを作成します。
	その敵クラスのメンバの中には同じ意図のメンバが複数存在します。
	継承を使用しない場合は全てのクラスで同じメンバの宣言を行う必要がありますが、
	継承を使用した場合、共通のメンバを持つ基底クラスを定義すれば、
	そのクラスを継承するだけで継承先のクラスではそれらのメンバを宣言する必要がなくなります。
	このような複数のクラスに共通するメンバを1つのクラスにまとめることを汎化(はんか)といいます。

	cpp_0002

●protected
	継承の機能を深く利用する上で大切な機能がアクセス指定子の「protected」です。
	protectedとはメンバに指定するアクセス指定子の一つで他には
	「public」「private」があり、各アクセス指定子の効果は以下の通りです。

		・public
			public指定されたメンバはクラスの中と外で自由に
			アクセスすることができる

		・private
			private指定されたメンバはクラス内(メンバ関数内)でしか
			アクセスすることができない

		・protected
			protected指定されたメンバは基底クラスと派生クラスの
			クラス内(メンバ関数内)でしかアクセスすることができない

	上記の記述であるようにprotectedを指定すると基底クラスと派生クラスで
	メンバを使用することが可能となります。
	この特性を生かして派生先で使用したいメンバは基底クラスでprotected宣言しておきます。

	・例
		// 継承元
		class Kaden
		{
		protected:
			int m_KadenUriage;		// 売り上げ金
		public:
			void ShowUriage(void);	// 売り上げ表示
		};

		// 売り上げ表示
		void Kaden::ShowUriage(void)
		{
			printf("売り上げ金額 = %d円\n", m_KadenUriage);
		}

		// 継承先
		class OsakaSiten : public Kaden
		{
		public:
			OsakaSiten(int uriage);
		};

		OsakaSiten::OsakaSiten(int kaden_uriage)
		{
			m_KadenUriage = kaden_uriage;
		}

		void main(void)
		{
			OsakaSiten osaka(1000000);
			osaka.m_KadenUriage = 1000000; // エラー
			osaka.ShowUriage();
		}

		表示結果:
			売り上げ金額 = 1000000円

	・privateの注意点
		継承を行う上での注意点としてprivateの有効範囲があります。
		privateの有効範囲はあくまでそのクラス内になりますので
		継承先でprivate設定したメンバの使用をすることはできません。

		・例
			// 継承元
			class Kaden
			{
			private:
				int m_KadenUriage;	// 売り上げ金
			public:
				void ShowUriage(void);	// 売り上げ表示
			};

			// 売り上げ表示
			void Kaden::ShowUriage(void)
			{
				printf("売り上げ金額 = %d円\n", m_KadenUriage);
			}

			// 継承先
			class OsakaSiten : public Kaden
			{
			public:
				OsakaSiten(int uriage);
			};

			OsakaSiten::OsakaSiten(int kaden_uriage)
			{
				m_KadenUriage = kaden_uriage; // エラー
			}

			void main(void)
			{
				OsakaSiten osaka(1000000);
				osaka.m_KadenUriage = 1000000; // エラー
				osaka.ShowUriage();
			}

			表示結果:
				売り上げ金額 = 1000000円

●コンストラクタとデストラクタ
	基底クラスに定義されているコンストラクタとデストラクタは
	派生クラスに継承されません。
	コンストラクタとデストラクタは定義しているクラスの名前が
	メンバ関数名になるので、基底クラスは基底クラスの
	派生クラスは派生クラスのコンストラクタ、デストラクタの宣言が必要です。

	・コンストラクタ、デストラクタの呼び出し順番
		コンストラクタとデストラクタの継承は行われませんが、
		基底クラスにコンストラクタ、デストラクタが定義されている場合
		派生クラスの生成、破棄が行われる際に基底クラスの
		コンストラクタ、デストラクタが自動的に呼び出されるようになっています。

		・例
			// 基底クラス
			class Base
			{
			public:
				Base();
				~Base();
			};

			Base::Base()
			{
				printf("Baseコンストラクタ\n");
			}

			Base::~Base()
			{
				printf("Baseデストラクタ\n");
			}

			// 派生クラス
			class Super : public Base
			{
			public:
				Super();
				~Super();
			};

			Super::Super()
			{
				printf("Superコンストラクタ\n");
			}

			Super::~Super()
			{
				printf("Superデストラクタ\n");
			}

			void main(void)
			{
				Super *test = new Super; // コンストラクタ呼び出し

				delete test; // デストラクタ呼び出し
			}

			実行結果:
				Baseコンストラクタ
				Superコンストラクタ
				Superデストラクタ
				Baseデストラクタ

		コンストラクタとデストラクタの呼び出される順番は
		コンストラクタは基底クラス→派生クラス、
		デストラクタは派生クラス→基底クラスの順番で呼び出されます。
		この順番は初期化や解放処理の手順で必要になることがあるかもしれませんので、
		覚えておいてください。

	・引数を持つコンストラクタの呼び出し
		コンストラクタは関数のオーバーロードが可能となっていますので、
		基底クラスに複数のコンストラクタを持っている可能性があります。
		その場合、派生クラスは基底クラスのどのコンストラクタを呼び出すかを
		指定しなければいけません。

		・コンストラクタの指定方法
			コンストラクタの指定は派生コンストラクタ定義部分で
			引数の「)」と関数開始の「{」の間に「:」をつけて
			その後ろに基底クラスのコンストラクタ名を指定して
			()の中に引数を指定します。
			そうするとその引数の内容に当てはまるコンストラクタが呼び出されます。
			この「:」から始まる部分をイニシャライザ(初期化子)と呼びます。
	
			・書式例
				クラス名::関数名(引数) : 基底クラス(初期値)
				{
				}

			・具体例
				class Base
				{
				public:
					Base();
					Base(int val);

					int m_Val;
				};

				Base::Base(void)
				{
					printf("Baseコンストラクタ1\n");
				}

				Base::Base(int val)
				{
					m_Val = val;
					printf("Baseコンストラクタ2 m_Val = %d\n", m_Val);
				}

				class Super : public Base
				{
				public:
					Super(int val2);
					Super(int val, int val2);

					int m_Val2;
				};

				Super::Super(int val2)
				{
					m_Val2 = val2;
					printf("Superコンストラクタ1 m_Val2 = %d\n", m_Val2);
				}

				Super::Super(int val, int val2) : Base(val)
				{
					m_Val2 = val2;
					printf("Superコンストラクタ2 m_Val2 = %d\n", m_Val2);
				}

				void main(void)
				{
					Super *super1 = new Super(10);
					Super *super2 = new Super(20, 300);

					delete super1;
					delete super2;
					while(true);
				}

				実行結果:
					Baseコンストラクタ1
					Superコンストラクタ1 m_Val2 = 10
					Baseコンストラクタ2 m_Val = 20
					Superコンストラクタ2 m_Val2 = 300

			上の例ではsuper1のコンストラクタの引数は1つ設定しており、
			super2は引数を2つ設定しています。

■アップキャスト

アップキャストとは派生クラスのポインタを基底クラスポインタにキャストすることです。
ポインタへのキャストは同じデータ型のポインタしか代入できないようになっていますが、
「派生クラスは基底クラスのメンバを全て持っている」という
約束事があるからこそ特別にキャストが認められています。
ただしキャストが完了した基底クラスでは派生クラスのメンバの使用ができませんので
注意が必要です。


●コード例

	class AnimalBase
	{
	public:
		int m_AnimalType;
	};

	class Cat : public AnimalBase
	{
	public:
		int m_CatKind;
	};

	void main(void)
	{
		Cat tama;
		// CatクラスをAnimalクラスにアップキャストする
		AnimalBase *animal = &tama;
			
		animal->m_AnimalType = 100;
		// AnimalBaseにキャストしているのでCatのメンバは使えない
		animal->m_CatKind = 1;
	}

	上の例ではCatクラスのtamaをアップキャストしてAnimalBaseのanimalに
	アップキャストをして代入しています。
	この処理によって、tamaはAnimalBaseクラスとして扱われますので、
	「animal->m_CatKind = 1;」という処理はエラーになります。
	AnimalBaseクラスにはm_CatKind変数がないからです。