継承(Inheritance)について説明する.継承は,あるクラスの機能に別の機能を追加したクラスを作る機能である.以下に簡単な例を示す.
t.cpp
#include <iostream>
using namespace std;
struct A{
void print(){
cout << "this is A\n";
}
};
struct B : public A{
void printB(){
cout << "this is B\n";
}
};
int main(){
A a;
B b;
a.print();
b.print();
b.printB();
return 0;
}
このコードに,クラスAとBが存在する.クラスBは8行目でAの機能を継承するものと定義されている.public と記載されているのは,Aの機能をpublicメンバとして定義することを意味する.これにより,15, 16行目各クラスのオブジェクトを定義したとき,bでは,A, B両方のメンバ関数を呼び出すことが,19, 20行目に記載するように可能となる.実行結果を以下に示す.
上記例のクラスでAのように元となるクラスを基本クラス,Bのように機能を拡張したクラスを派生クラスと呼ぶ.派生クラスのオブジェクトは基本クラスの変数へ代入が可能である.また,派生クラスで基本クラスと同じメンバを定義すると,メンバ参照演算子で参照されるのは,宣言時のクラスの関数となる.以下に例と実行結果を示す.
#include <iostream>
using namespace std;
struct A{
void print(){
cout << "this is A\n";
}
};
struct B : public A{
void print(){
cout << "this is B\n";
}
};
int main(){
A a;
B b;
a.print();
b.print();
a = b;
a.print();
return 0;
}
21行目で代入が行われている.その次の22行目では,Bのprintが呼び出されるのではなく,Aのprintが呼び出されていることを実行結果より確認できる.このように,基本クラスへ代入した場合,派生クラスのメンバを参照することはできなくなる.
だが,ポインタや参照で受け取る場合,参照先のクラスのメンバを呼び出すことが可能となる.以下に例を示す.4行目のようにvirtualと記載された関数が呼び出されるとき,参照先のクラスの関数が呼び出されるようになる.
仮想関数の例
#include <iostream>
using namespace std;
struct A{
virtual void print(){
cout << "this is A\n";
}
};
struct B : public A{
void print(){
cout << "this is B\n";
}
};
int main(){
A *a;
A a1;
B b;
a = &a1;
(*a).print();
a = &b;
(*a).print();
A &ab = b;
ab.print();
A &aa = a1;
aa.print();
return 0;
}
実行結果は次のとおりである.上記コードの20行目では,Aのオブジェクトへのポインタがaに代入されているため,print は Aのものが使われる.一方で,22行目では,Bのprintが使われていることがわかる.これは,直前にBのオブジェクトのポインタがaへ代入されているためである.
このような,virtual をつけられた関数を仮想関数とよび,C++では仮想関数が派生クラスで定義し直されたときに特別な動きをするように決められている.その動きとは,ポインタを通してメンバ関数が呼び出されたときは,そのポインタが指すオブジェクトのクラスのメンバ関数が,ポインタの型によらず呼び出されるというものである.ただし,代入先のポインタの型は,代入するクラスの基本クラスでなければならない.また,このことは,参照を使った場合にも適用される.先のコードの23行目から,参照による例を示している.
ところでなぜかInheritanceには継承と日本語が割り当てられている.不思議である.以下にいくらか日本語の対応を乗せる.このような不思議について,読者にも考えてみてもらいたい.