新規記事の投稿を行うことで、非表示にすることが可能です。
2017年12月17日
《その184》 仮想関数(2)
仮想関数
前回《183》のプログラムと今回のプログラムの相違点は、
基底クラス Member のメンバ関数の関数宣言 void print() const; の前に virtual を付加して、
virtual void print() const;
としてある箇所( ◆印 )だけです。
これで、関数 print は仮想関数になります。
派生クラスのメンバ関数で、基底クラスの仮想関数と仕様(関数名と仮引数)が同じものは、virtual が付加されていなくても仮想関数とみなされます。
したがって、Member2クラスのメンバ関数 print も仮想関数です。
プログラムコードより実行結果が先になってしまいますが、実行結果は次のようになります。
上が前回《183》の実行結果、下が今回(メンバ関数 print が仮想関数)の実行結果です。
【 前回《183》の実行結果 】
【 今回の実行結果 】
今回のプログラムは、関数 print が仮想関数になっている点が、前回と違っています。
仮想関数は、x.print() として呼ばれたときに、x の静的な型ではなく、動的な型のメンバ関数を呼び出します。
動的な型とは、ポインタが指しているオブジェクトの実際の型であり、
harada, tahara であれば Member型、
kawada, tagawa であれば Member2型
ということになります。
その結果、x.print により、
harada, tahara では、Member::print()、
kawada, tagawa では、Member2::print()、
が実行されることになります。
以下が今回のプログラムです。
// ------------------------------------
#include <string>
#include <iostream>
using namespace std;
class Member {
public:
int number; // 番号
double best_time; // 自己ベスト
Member(int n, double t)
: number(n), best_time(t) { }
// 個人データの出力
virtual void print() const { // ◆
// void print() const {
cout << "【" << number << "】"
"自己ベスト : "
<< best_time << '\n';
}
};
class Member2 : public Member {
public:
char mf; // 性別
Member2(int n, double t, char c)
: Member(n, t), mf(c) { }
// 個人データの出力
void print() const {
cout << "【" << number << "】"
"性別 (" << mf << ") "
"自己ベスト : "
<< best_time << '\n';
}
};
int main() {
// Member型オブジェクトの生成
Member harada(1001, 13.2);
Member tahara(2001, 12.2);
// Member2型オブジェクトの生成
Member2 kawada(3002, 11.8, 'm');
Member2 tagawa(3006, 12.2, 'f');
Member* ptr[] = { &harada, &tahara, &kawada, &tagawa };
for (int i = 0; i < sizeof(ptr) / sizeof(ptr[0]); i++)
(*ptr[i]).print();
}
// ------------------------------------
《その183》 仮想関数(1)
仮想関数
下のプログラムは、仮想関数の働きを見るためのものです。
プログラム中の◆◆◆印の部分が仮想関数ですが、このプログラムでは、その部分をコメントアウトしてあります。
次回《184》にコメントアウトを外して、その働きを見てみることになります。
プログラム中に、2種類のクラスがあります。違いは次の2点です。
(1) クラス Member
・データメンバ : 番号 ・自己ベスト
・関数 print は、
番号, 自己ベスト を出力
(2) public派生クラス Member2
・データメンバ : 番号 ・自己ベスト ・性別
・関数 print は、
番号, 性別, 自己ベスト を出力
各オブジェクトを、
Member* ptr[] = { &harada, &tahara, &kawada, &tagawa };
のように、基底クラス型のポインタ ptr[0] 〜 ptr[3] で、指している点に注意してください。
末尾に載せてある出力結果を見ると、番号, 自己ベストだけが出力されています。
このことから、4人分のオブジェクトが全て Member型として扱われたことが分かります。
// ------------------------------------
#include <string>
#include <iostream>
using namespace std;
class Member {
public:
int number; // 番号
double best_time; // 自己ベスト
Member(int n, double t)
: number(n), best_time(t) { }
// 個人データの出力
// virtual void print() const { // ◆◆◆
void print() const {
cout << "【" << number << "】"
"自己ベスト : "
<< best_time << '\n';
}
};
class Member2 : public Member {
public:
char mf; // 性別
Member2(int n, double t, char c)
: Member(n, t), mf(c) { }
// 個人データの出力
void print() const {
cout << "【" << number << "】"
"性別 (" << mf << ") "
"自己ベスト : "
<< best_time << '\n';
}
};
int main() {
// Member型オブジェクトの生成
Member harada(1001, 13.2);
Member tahara(2001, 12.2);
// Member2型オブジェクトの生成
Member2 kawada(3002, 11.8, 'm');
Member2 tagawa(3006, 12.2, 'f');
Member* ptr[] = { &harada, &tahara, &kawada, &tagawa };
for (int i = 0; i < sizeof(ptr) / sizeof(ptr[0]); i++)
(*ptr[i]).print();
}
// ------------------------------------
《その182》 静的な型
静的な型
次のプログラムで、★印のポインタ p_b,参照 r_b の指す先は、両方ともクラスオブジェクト bbb ですが、p_b,r_b を typeid演算子で評価して得られるのは aaa型です。
このように、プログラム実行時の意味的には別の型であっても、宣言時などから判断できる本来の型を、「静的な型」と表現します。
// ------------------------------------
#include <typeinfo>
#include <iostream>
class Aaa {
public:
int a;
Aaa() : a(1) { }
};
class Bbb : public Aaa {
public:
int b;
Bbb() : b(2) { }
};
int main() {
Aaa aaa;
Bbb bbb;
std::cout << "(1)\n";
std::cout << typeid(Aaa).name() << '\n';
std::cout << typeid(Bbb).name() << "\n\n";
Aaa* p_a = &aaa;
Aaa* p_b = &bbb; // ★
// 基底クラスへのポインタは
// 派生クラスを指すことができる。
std::cout << "(2-1)\n";
std::cout << typeid(*p_a).name() << '\n';
std::cout << typeid(*p_b).name() << "\n\n";
std::cout << "(2-2)\n";
std::cout << typeid(p_a).name() << '\n';
std::cout << typeid(p_b).name() << "\n\n";
// ポインタ p_b は派生クラスを指しますが、
// そのポインタ型は基底クラス型
// です。
Aaa& r_a = aaa;
Aaa& r_b = bbb; // ★
// 基底クラス型の参照は、派生クラスを参照
// することができます。
std::cout << "(3-1)\n";
std::cout << typeid(r_a).name() << '\n';
std::cout << typeid(r_b).name() << "\n\n";
// r_b は bbb を参照しますが、その型
// は基底クラス型です。
std::cout << "(3-2)\n";
std::cout << typeid(&r_a).name() << '\n';
std::cout << typeid(&r_b).name() << "\n\n";
// 以下は自分の復習用なので、今回のテーマとは
// 関係ありません。
int n = 99;
int* p = &n;
int& r = n;
std::cout << *p << '\n';
std::cout << r << "\n\n";
*p = 100;
std::cout << *p << '\n';
std::cout << r << "\n\n";
r = 999;
std::cout << *p << '\n';
std::cout << r << "\n\n";
std::cout << typeid(*p).name() << '\n';
std::cout << typeid(r).name() << "\n\n";
std::cout << typeid(p).name() << '\n';
std::cout << typeid(&r).name() << '\n';
}
// ------------------------------------