クラスの継承に関する記事です。
オーバーライドや仮想関数に関しても解説しています。
継承とは?
既存のクラスを元に、新しいクラスを作ることを継承と言います。
継承の元となるクラスを基底クラス(親クラス)、継承したクラスを派生クラス(子クラス)と呼びます。
派生クラスのメンバは、基底クラスのメンバと派生クラスで新たに宣言したメンバとなります。
継承の定義
class 派生クラス名 : public 基底クラス
class 派生クラス名 : private 基底クラス
派生クラスは、基底クラスから複数作成することが可能です。
また、派生クラスを更に継承して、派生クラスを作成することも出来ます。
【private】を用いて継承をすると、基底クラスで【public】【protected】だったメンバが、派生クラスで【private】となります。
アクセス指定子
基底クラスで【public】だったメンバは、派生クラスからアクセスすることが出来ます。
基底クラスで【private】だったメンバは、派生クラスからアクセスすることが出来ません。
基底クラスで【protected】だったメンバは、派生クラスからアクセスすることが出来ます。
コーディング例①
#include <iostream>
// 基底クラス(親クラス)
class Fruit {
public:
void SetPrice(int p) {
price = p;
}
int GetPrice() {
return price;
}
private:
int price;
protected:
void Hello() {
std::cout << "Hello" << std::endl;
}
};
// 派生クラス(子クラス)
class Banana : public Fruit {
public:
void Print() {
std::cout << "Bananaクラス" << std::endl;
}
void Hello2() {
Hello();
}
};
int main() {
Banana MyBanana;
MyBanana.SetPrice(150);
MyBanana.Print();
std::cout << MyBanana.GetPrice() << "円" << std::endl;
MyBanana.Hello2();
return 0;
}
出力
Bananaクラス
150円
Hello
単一継承・多重継承
1つの基底クラスから派生クラスを作ることを単一継承と呼びます。
複数の基底クラスから派生クラスを作ることを多重継承と呼びます。
多重継承の定義
class 派生クラス名 : public 基底クラス①, public 基底クラス②, ・・・
class 派生クラス名 : private 基底クラス①, private 基底クラス②, ・・・
【private】を用いて継承をすると、基底クラスで【public】【protected】だったメンバが、派生クラスで【private】となります。
コーディング例①
基底クラスに同じ名称のメンバがある場合には、『○○があいまいです』というエラーが発生します。
派生クラスから重複したメンバにアクセスする場合には、スコープ解決演算子を使って、メンバを指定する必要があります。
#include <iostream>
// 基底クラス(親クラス)
class ClassA {
public:
void PrintA() {
std::cout << "ClassAの関数です。" << std::endl;
}
void SetNum(int a) {
num1 = a;
}
int GetNum() {
return num1;
}
private:
int num1;
};
// 基底クラス(親クラス)
class ClassB {
public:
void PrintB() {
std::cout << "ClassBの関数です。" << std::endl;
}
void SetNum(int a) {
num2 = a;
}
int GetNum() {
return num2;
}
private:
int num2;
};
// 派生クラス(子クラス)
class ClassC : public ClassA, public ClassB {
public:
void PrintC() {
std::cout << "ClassCの関数です。" << std::endl;
}
};
int main() {
ClassC MyClass;
MyClass.PrintA();
MyClass.PrintB();
MyClass.PrintC();
MyClass.ClassA::SetNum(10);
std::cout << MyClass.ClassA::GetNum() << std::endl;
MyClass.ClassB::SetNum(50);
std::cout << MyClass.ClassB::GetNum() << std::endl;
return 0;
}
出力
ClassAの関数です。
ClassBの関数です。
ClassCの関数です。
10
50
メンバ関数の再定義
コーディング例①
#include <iostream>
// 基底クラス(親クラス)
class Fruit {
public:
void SetPrice(int p) {
price = p;
}
int GetPrice() {
return price;
}
private:
int price;
};
// 派生クラス(子クラス)
class Banana : public Fruit {
public:
// メンバ関数の再定義
void SetPrice(int p) {
Fruit::SetPrice(p + 100);
}
};
int main() {
Fruit MyFruit;
Banana MyBanana;
MyFruit.SetPrice(100);
MyBanana.SetPrice(100);
std::cout << MyFruit.GetPrice() << std::endl;
std::cout << MyBanana.GetPrice() << std::endl;
return 0;
}
出力
100
200
派生クラスから代入
派生クラスのオブジェクトは、基底クラスのオブジェクトに代入することが可能です。
代入すると、派生クラスのメンバ変数の値が、基底クラスのメンバ変数にコピーされます。
ただし、派生クラス固有のメンバ変数はコピーされません。
また、基底クラスのオブジェクトを派生クラスのオブジェクトに代入することは出来ません。
記述方法
基底クラス = 派生クラス;
コーディング例①
#include <iostream>
// 基底クラス(親クラス)
class Fruit {
public:
void SetPrice(int p) {
price = p;
}
int GetPrice() {
return price;
}
private:
int price;
};
// 派生クラス(子クラス)
class Banana : public Fruit {
public:
void Print() {
std::cout << "Bananaクラス" << std::endl;
}
};
int main() {
Fruit MyFruit;
Banana MyBanana;
MyBanana.SetPrice(150);
std::cout << "MyBanana:" << MyBanana.GetPrice() << std::endl;
MyFruit.SetPrice(100);
std::cout << "MyFruit:" << MyFruit.GetPrice() << std::endl;
// 派生クラスの代入
MyFruit = MyBanana;
std::cout << "MyFruit:" << MyFruit.GetPrice() << std::endl;
return 0;
}
出力
MyBanana:150
MyFruit:100
MyFruit:150
基底クラスから派生クラスの呼び出し
派生クラスのオブジェクトのアドレスは、基底クラスのポインタに代入することが出来ます。
そうすることで、派生クラスのメンバ関数を基底クラスのポインタから呼び出すことが出来るようになります。
ただし、派生クラス固有のメンバ関数は呼び出すことが出来ません。
コーディング例①
#include <iostream>
// 基底クラス(親クラス)
class Fruit {
public:
void SetPrice(int p) {
price = p;
}
int GetPrice() {
return price;
}
private:
int price;
};
// 派生クラス(子クラス)
class Banana : public Fruit {
public:
void PrintBanana() {
std::cout << "Bananaクラス" << std::endl;
}
};
// 派生クラス(子クラス)
class Apple : public Fruit {
public:
void PrintApple() {
std::cout << "Appleクラス" << std::endl;
}
};
int main() {
Fruit *pFruit[2];
Banana MyBanana;
Apple MyApple;
pFruit[0] = &MyBanana;
pFruit[1] = &MyApple;
pFruit[0]->SetPrice(100);
pFruit[1]->SetPrice(200);
std::cout << pFruit[0]->GetPrice() << std::endl;
std::cout << pFruit[1]->GetPrice() << std::endl;
return 0;
}
出力
100
200
オーバーライド・仮想関数
派生クラスで再定義したメンバ関数を基底クラスのポインタから呼び出すと、再定義前のメンバ関数が呼び出されます。
再定義後のメンバ関数を呼び出したい場合には、仮想関数という機能を利用します。
仮想関数を派生クラスで定義し直すことをオーバーライドと呼びます。
記述方法
virtual メンバ関数
仮想関数にする場合は、メンバ関数の前に【virtual】を記述します。
コーディング例①
#include <iostream>
// 基底クラス(親クラス)
class Fruit {
public:
virtual void SetPrice(int p) { // 仮想関数
price = p;
}
int GetPrice() {
return price;
}
private:
int price;
};
// 派生クラス(子クラス)
class Banana : public Fruit {
public:
// メンバ関数の再定義(オーバーライド)
void SetPrice(int p) {
Fruit::SetPrice(p + 100);
}
};
int main() {
Fruit *pMyFruit;
Banana MyBanana;
pMyFruit = &MyBanana;
pMyFruit->SetPrice(100);
std::cout << pMyFruit->GetPrice() << std::endl;
return 0;
}
出力
200
デストラクタ
基底クラスのポインタが派生クラスのオブジェクトを指している場合、基底クラスのポインタを【delete】しても、派生クラスのデストラクタが呼ばれません。
そのため、デストラクタでメモリの解放を行っているような場合には、デストラクタを仮想関数にしておいた方が良いです。
純粋仮想関数
継承をすることを前提とする基底クラスの場合は、基底クラスで仮想関数の内容を決められないことがあります。
そのような場合には、基底クラスで内容を定義しない純粋仮想関数を使用します。
ただし、純粋仮想関数を含むクラスは、オブジェクトを生成することは出来ません。
virtual メンバ関数 = 0;
コーディング例①
#include <iostream>
// 基底クラス(親クラス)
class Fruit {
public:
virtual void SetPrice(int p) = 0; // 純粋仮想関数
int GetPrice() {
return price;
}
protected:
int price;
};
// 派生クラス(子クラス)
class Banana : public Fruit {
public:
// メンバ関数の再定義(オーバーライド)
void SetPrice(int p) {
price = p;
}
};
int main() {
Fruit *pMyFruit;
Banana MyBanana;
pMyFruit = &MyBanana;
pMyFruit->SetPrice(100);
std::cout << pMyFruit->GetPrice() << std::endl;
return 0;
}
出力
100
クラスの基礎知識
クラスの基礎知識に関しては、以下の記事で解説しています。
興味のある方は、良ければ見てください。
