虚関数、純虚関数と抽象クラス(浅論)

6513 ワード

 

オブジェクト向けのプログラム設計では,虚関数,純虚関数,抽象類が仕事や就職面接における重点難点となっている.私はここでこの3つについて簡単な論述をします.悪いところがあれば、同行者の皆さんの批判と指摘を歓迎します.
(作者オリジナル、転載は出典を明記してください).
虚関数といえば,多態性を言わざるを得ない.マルチステート性はオブジェクト向けプログラム設計の重要な特徴であり、1つの言語がクラスのみをサポートし、マルチステートをサポートしない場合は、オブジェクト向け言語とは言えず、オブジェクトベースの言語としか言えない.
では、問題が来ました.何が多態ですか.
多態性の英語の単語polymorphismはギリシャ語の根poly(多くのことを意味する)とmorph(形態を意味する)に由来する.その名の通り、多態とは一つの物事に多様な形態があることを意味する.
C++プログラム設計では,多態性とは異なる機能を持つ関数が同じ関数名で使用できることを意味する.これにより、同じ関数名で異なるコンテンツの関数を呼び出すことができます.
オブジェクト向けメソッドでは、一般に、異なるオブジェクトに同じメッセージを送信し、異なるオブジェクトが受信時に異なる動作(すなわちメソッド)を生じる多態性を表す.
システム時間の観点から,多態性は静的多態性と動的多態性の2つに分類される.
静的マルチステートには、関数リロードと演算子リロードがあります.静的多態性はコンパイル時の多態性とも呼ばれる.プログラムのコンパイル時に、システムは呼び出す関数を決定することができます.
動的多態性は実行時の多態性とも呼ばれ、動的多態性はプログラムの実行中に動的に決定操作が対象となるオブジェクトである.動的多様性は虚関数(virtual function)によって実現される.
前菜が終わり、食事が始まります.
虚関数について説明します
クラスの継承階層では、異なる階層で名前が同じで、パラメータの個数がタイプと同じで、機能が異なる関数が表示されます.(ただし、同じクラスでは絶対に許されません).コンパイルシステムは、同名上書きの原則に従って呼び出しオブジェクトを決定します.
したがって,同じ呼び出し形式で派生クラスの関数もベースクラスの同名関数も呼び出せるかどうかは自然に考えられる.
なぜ虚関数が必要なのですか?
虚関数は、派生クラスでベースクラスと同じ名前の関数を再定義し、ベースクラスポインタまたは参照によってベースクラスと派生クラスの同じ名前の関数にアクセスできるようにする役割を果たします.
//            
#include
#include
#include

using namespace std;

//    
class Student
{
public:
	Student(int, string, float);
     virtual void display();
	~Student();

protected:
	int num;
	string name;
	float score;
};

Student::Student(int n, string nam, float s)
	:num(n), name(nam), score(s) 
{
//	cout << "         !" << endl;
}

 void Student::display()
{
	cout << "  :" << num << endl;
	cout << "  :" << name << endl;
	cout << "  :" << score << endl;
}

Student::~Student()
{
//	cout << "          !" << endl;
}

//     
class Graduate:public Student
{
public:
	Graduate(int, string, float, float);
	virtual  void display();
	~Graduate();

private:
	float salary;
};

Graduate::Graduate(int n, string nam, float s, float sa)
	:Student(n, nam, s), salary(sa)
{
//	cout << "           !" << endl;
}

Graduate::~Graduate()
{
//	cout << "           !" << endl;
}

void Graduate::display()
{
	cout << "  :" << num << endl;
	cout << "  :" << name << endl;
	cout << "  :" << score << endl;
	cout << "  :" << salary << endl;
}

int main()
{
	Student std(2018060130, "James", 98.5);
	/*std.display();
	std.~Student();

	cout << endl << endl;*/

	Student * pt = &std;
//	Student & ptr = std;
	pt->display();
//	pt->~Student();

	cout << endl << endl;

	//ptr.display();
	//ptr.~Student();

//	cout << endl << endl;

	Graduate grd(2018060131, "Robert", 93.5, 3578.45);
	/*grd.display();
	grd.~Graduate();
	cout << endl << endl;*/

	pt = &grd;
	pt->display();
	
	cout << endl << endl;

	//ptr = grd;
	//ptr.display();

//	cout << endl << endl;

	system("pause");
	return 0;
}

説明:本来、ベースクラスポインタはベースクラスのオブジェクトを指すためのものであり、これを用いて派生クラスのオブジェクトを指す場合、ポインタタイプ変換を行い、派生クラスオブジェクトのポインタをベースクラスのポインタに先に変換するので、ベースクラスポインタは派生クラスオブジェクトのベースクラス部分を指す.
プログラムが変更される前に、派生クラスオブジェクトのメンバー関数をベースクラスのポインタで呼び出すことはできません.
虚関数の出現はこの制限を突破し,派生クラスのベースクラス部分では派生クラスの虚関数がベースクラスの元の虚関数に取って代わるため,ベースクラスポインタを用いて派生クラスオブジェクトを指すと,虚関数を呼び出すと派生クラスの虚関数が呼び出される.
上記の役割はvirtualで虚関数を宣言した場合にのみ使用されます.
ダミー関数として宣言しない場合は、ベースクラスポインタで派生クラスの非ダミー関数を呼び出そうとすることはできません.
    
ポイント:
ベースクラスのメンバー関数を虚関数として宣言した後、派生クラスで関数を再定義し、新しい機能を付与し、ベースクラスを指すポインタで同じクラスの異なるオブジェクトを指すことで、同じ名前の関数を呼び出すことができます.
虚関数によって実現される動的多様性は、同じクラスのファミリー内の異なるオブジェクトが、同じ関数呼び出しに対して異なる応答を行うことである.
虚関数の使用方法:
(1)ベースクラスでvirtualでメンバー関数を虚関数と宣言することで,派生クラスでこの関数を再定義し,新しい機能を付与し,容易に呼び出すことができる.(クラス外で虚関数を定義する場合、virtualを追加することはできません).
(2)派生クラスで虚関数を定義する場合,関数タイプ,パラメータタイプ,パラメータ個数などすべてベースクラスの虚関数と同一であることが要求される.派生クラスの必要に応じて関数体を再定義します.
C++は、1つのメンバー関数が虚関数として宣言された後、その派生クラスの同名関数が自動的に虚関数になることを規定している.この言葉を覚えておいてください.いったん虚しくなったら、ずっと虚しくなります.
備考:(2)条の議論に基づいて、派生クラスで虚関数を再宣言する場合は、キーワードvirtualを追加してもよいし、追加しなくてもよい.しかし、慣習的には、各レイヤが虚関数を宣言するときにvirtualを加えて、プログラムをより明確にするのが一般的です.派生クラスでベースクラスの虚関数が再定義されていない場合、派生クラスは直接ベースクラスの虚関数を簡単に継承します.
(3)ベースクラスオブジェクトを指すポインタ変数を定義し、同じクラスファミリー内のオブジェクトを指すようにします.
(4)このポインタ変数によってこの虚関数が呼び出され,そのときに呼び出されるのがポインタ変数が指すオブジェクトの同名関数である.
同じ階層の同名関数の問題は関数の再ロードで解決されます.
異なる階層上の同名関数問題は虚関数で解決される.
もう1つの点:虚関数のヘッダは同じです.すなわち、関数タイプのパラメータの個数は同じです.
関数のリロード時に関数の先頭が異なる、すなわちパラメータの個数やタイプが異なる.
虚関数を使用する場合は、次の2つの点に注意してください.
(1)virtualでクラスのメンバー関数のみを宣言し,クラス外の一般関数を虚関数として宣言するのではなく虚関数とする.虚関数は、派生クラスでベースクラスの虚関数を再定義できるようにする役割を果たすからです.クラスの継承階層でのみ使用できるのは明らかです.
(2)1つのメンバー関数が虚関数として宣言された後、同じクラスのクラスでvirtual以外の非virtualを定義することはできませんが、この虚関数と同じパラメータ(個数とタイプを含む)と関数戻り値タイプの同名関数を持つことはできません.
虚関数は、各虚関数のエントリアドレスを格納するポインタ配列である虚関数テーブル(virtual function table,vtable)によって実現されることに注意してください.虚関数を使用する場合、システムは一定の空間オーバーヘッドを必要とします.クラスに虚関数がある場合、コンパイルシステムはクラスのために虚関数テーブルを構築します.
次に、純粋な虚関数について説明します.
純虚関数は、虚関数を宣言するときに「初期化」されて0になる関数です.
純粋な虚関数を宣言する一般的な形式は、次のとおりです.
virtual関数タイプ関数名(パラメータテーブル列)=0;
注意:(1)純虚関数には関数体がありません.
(2)最後尾の"=0"は関数の戻り値が0であることを示さず,単純な虚関数であることをコンパイルシステムに伝える形式的な役割を果たす.
(3)これは宣言文で、最後にセミコロンが必要です.
純粋な虚関数の役割は、必要に応じて派生クラスを定義するために、ベースクラスに派生クラスの関数の名前を保持することです.ベースクラスに関数の名前が保持されていない場合は、マルチステート性は実現されません.
クラスに純虚関数が宣言され、その派生クラスに関数が定義されていない場合、その虚関数は派生クラスにおいて純虚関数のままです.
 
抽象クラスについて説明します
抽象クラス:継承されたクラスとしてオブジェクトを定義せず、基本タイプとしてのみ使用されます.
純粋な虚関数を含むクラスはすべて抽象クラスである.
解釈:純粋な虚関数は呼び出せないため、純粋な虚関数を含むクラスはオブジェクトを構築できません.抽象クラスの役割は、クラスの共通のベースクラスとして、またはクラスに共通のインタフェースを提供することである.
抽象クラスのまとめ:
(1)1つのベースクラスが1つ以上の純虚関数を含む場合,抽象ベースクラスである.抽象ベースクラスは、オブジェクトを定義する必要もありません.
(2)抽象ベースクラスは一般ベースクラスとは異なり,一般に現実的に存在するオブジェクトの抽象ではなく,物理的または他の実用的な意味の意味を持たなくてもよい.
(3)クラスの階層では,最上位層または最上位層が抽象ベースクラスであってもよい.抽象ベースクラスは本クラスの各種類の共通性を体現し、各種類の中国共産党のあるメンバー関数を抽象ベースクラスに集中して声明した.
(4)抽象ベースクラスは,本クラスの共通インタフェースである.あるいは、同じベースクラスから派生した複数のクラスには、同じインタフェースがある.
(5)動的関連と静的関連を区別する.
(6)基底クラスで虚関数が宣言されている場合、派生クラスでは、その関数と同じ関数名、関数タイプ、パラメータ個数、パラメータタイプの関数は、派生クラスで宣言されているかどうかにかかわらず、虚関数であり、虚になると、ずっと虚である.ただし、同名の虚関数は、異なるクラスで異なる定義を持つことができます.
純虚関数は抽象ベースクラスで宣言され、抽象ベースクラスでのみ純虚関数となり、その派生クラスではこの関数が継承されていますが、再び「=0」で純虚関数と宣言しない限り、純虚関数とは呼べません.
(7)虚関数を用いることでプログラムの拡張性が向上する.