C++ 学習ノート 19 - ポリモーフィズム

19.1 ポリモーフィズムの基本概念

19.1.1 静的多態性と動的多態性

ポリモーフィズムは、C++ の 3 つの主要なオブジェクト指向機能 (カプセル化、継承、ポリモーフィズム) の 1 つであり、次の
2 つのカテゴリに分類されます。

  • 静的多態性: 関数のオーバーロードと演算子のオーバーロードは静的多態性に属し、関数名は再利用されます。
  • 動的ポリモーフィズム: 派生クラスと仮想関数は実行時ポリモーフィズムを実装します。

静的ポリモーフィズムと動的ポリモーフィズムの違い:

  • 静的多態性の関数アドレスは早期にバインドされます。関数アドレスはコンパイル段階で決定されます。
  • 動的多態性における関数アドレスの遅延バインディング - 関数アドレスは実行フェーズ中に決定されます。

以下にケースを通してポリモーフィズムを説明します。

#include<iostream>
using namespace std; 
//多态

//动物类

//地址早绑定,在编译阶段就确定了函数地址
class Animal
{
    
    
public:
	void speak()  
	{
    
    
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
    
    
public:
	void speak()
	{
    
    
		cout << "小猫在说话" << endl;
		m_A = 100;
	}
	int m_A;
};

//执行说话的函数

void doSpeak(Animal& animal) //Animal& animal = cat; 传入的是子类,用父类接收,此时此对象转换为了父类
{
    
    
	cout << (typeid(animal).name()) << endl;
	animal.speak();
}

//如果想要执行让猫说话,那么这个函数地址就不能早绑定,需要在运行阶段进行绑定,也就是晚绑定

//动态多态
//1.有继承关系
//2.子类要重写父类的虚函数

//动态多态的使用
//使用动态的指针或者引用来执行子类对象
class Animal_02
{
    
    
public:
	 virtual void speak()  //虚函数
	{
    
    
		cout << "动物在说话" << endl;
	}
};

class Cat_02 :public Animal_02
{
    
    
public:
	//函数重写 函数返回值类型 函数名 参数列表 完全不同  子类中的virtual可写可不写
	void speak()
	{
    
    
		cout << "小猫在说话" << endl;
	}
};
class Dog_02 :public Animal_02
{
    
    
public:
	void speak()
	{
    
    
		cout << "小狗在说话" << endl;
	}
};

void doSpeak_02(Animal_02& animal)    //这里仍旧是子类,因为父类中有虚函数,所以在这里
{
    
    
	cout << (typeid(animal).name()) << endl;
	animal.speak();
	animal.Animal_02::speak();
}

void test1_01()
{
    
    
	Cat cat1;
	doSpeak(cat1);

	Cat_02 cat2;
	doSpeak_02(cat2);

	Dog_02 dog;
	doSpeak_02(dog);
//通过晚绑定,使得函数地址不是一开始就绑定完毕, 而是通过不同的对象指针来绑定不同的函数地址。
}

int main()
{
    
    	
	test1_01();
	system("pause");
	return 0;
}

要約:
動的ポリモーフィズムの条件を満たす:

  1. 相続関係があります。
  2. サブクラスは親クラスの仮想関数をオーバーライドする必要があります。

動的ポリモーフィズムの使用:
   
 親クラスのポインターまたは参照を使用して、サブクラス オブジェクトを実行します。

19.1.2 多型の原理解析

上記のコードを例にとると、仮想関数が記述されると、クラス内でいくつかの変更が発生します。

Animal_02クラスには、仮想関数テーブルvftable(仮想関数テーブル)を指す仮想関数ポインタvfptr (仮想関数ポインタ)が格納されており、仮想関数テーブル内には仮想関数のアドレスが記録されている。

仮想関数テーブルに関数を格納する方法は、この関数のスコープを一緒に格納することです。格納されるのはそのアドレス、つまり です&Animal_02::speak
ここに画像の説明を挿入

サブクラス(cat_02)が親クラス(Animal_02)を継承する場合、サブクラスは親クラスの仮想関数ポインタと仮想関数テーブルを継承します。
現時点では:

  1. サブクラスが親クラスの仮想関数をオーバーライドしない場合、サブクラスの仮想関数ポインタと仮想関数テーブルは親クラスのものとまったく同じになります。
    ここに画像の説明を挿入

  2. サブクラスが親クラスの仮想関数をオーバーライドする場合、サブクラスによって書き換えられた関数は、サブクラスの仮想関数テーブル内の親クラスの仮想関数を上書きします。
    ここに画像の説明を挿入

注: 上記はポリモーフィズムの原理の分析です。実際のアプリケーションでは、仮想関数が親クラスに出現する場合、サブクラスが仮想関数をオーバーライドするかどうかに関係なく、typeid(object).name() を通じて見つけることができます。親クラスのポインター/ 参照が、通常のようにスーパークラスの型に変更されるのではなく、その型もサブクラスのオブジェクトであるサブクラスオブジェクトを指している場合でも。
つまり、親クラスに仮想関数がある場合、親クラスのポインタ/参照がサブクラス オブジェクトのコードを指していても、この時点ではオブジェクトは依然としてサブクラス型であり、そのオブジェクトへのアクセスに使用できます。サブクラスと親クラスの構文

ポリモーフィズムには、継承にはない関数があります。
メンバー関数 (名前は A とします) が仮想関数 (名前は B とします) を呼び出すために親クラスで定義されている場合、原則に従って、サブクラスを使用します。親クラスを継承して仮想関数Bを書き換えた後、サブクラスオブジェクトが親クラスオブジェクトのメンバ関数Aを呼び出すと、このAが呼び出した仮想関数Bがサブクラスで書き換えられた関数によって上書きされているため、オーバーライドされた関数 B は直接呼び出されます。つまり、新しい B を呼び出すためにサブクラスに新しい A を記述する必要はなく、このサブクラス オブジェクトを通じて親クラスの関数 A を直接呼び出すだけです。
この方法では、サブクラスで繰り返し呼び出される多くの関数を書き直す必要がありません


19.2 多態性のケース 1 - 計算機

ケースの説明:
  通常の書き込みテクノロジとポリモーフィック テクノロジをそれぞれ使用して、2 つのオペランドに対する演算を実行する計算機クラスを設計および実装します。

ポリモーフィズムの利点:

  • コード構成が明確です (抽象クラスとサブクラスが明確で読みやすい)。
  • 優れた可読性 (すべての関数が読みやすいクラスにあります)。
  • これは、初期段階と後期段階の拡張とメンテナンスに役立ちます (新しいサブクラスを直接作成または変更するだけです)。
#include<iostream>
using namespace std;

//普通写法
class Calculator
{
    
    
public:
	int getResult(string oper)
	{
    
    
		if (oper == "+")
		{
    
    
			return m_Num1 + m_Num2;
		}
		else if (oper == "-")
		{
    
    
			return m_Num1 - m_Num2;
		}
		else if (oper == "*")
		{
    
    
			return m_Num1 * m_Num2;
		}
		//如果想扩展新的功能,需要修改源码,需要不断增加源码
		//在真实的开发中,提倡一种 开闭原则
		//开闭原则: 对扩展进行开发,对修改进行关闭
	}

	//两个操作数
	int m_Num1;
	int m_Num2;
};

void test2_01()
{
    
    
	cout << "普通实现" << endl;
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 20;
	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
	cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
	cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}


//利用多态实现计算器
//实现计算器抽象类
class AbstractCalculator
{
    
    
public:
	virtual int getResult() = 0;

	int m_Num1;
	int m_Num2;
};
class AddCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 + m_Num2;
	}
};
class SubCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 - m_Num2;
	}
};
class MulCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 * m_Num2;
	}
};

void test2_02()
{
    
    
	cout << "多态实现" << endl;
	//多态使用条件
	//父类指针或者引用指向子类对象

	//加法
	AbstractCalculator* abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;

	abc = new SubCalculator;  //重新规定指针指向即可
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;

	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}

int main()
{
    
    
	test2_01();
	test2_02();
	system("pause");
	return 0;
}

19.3 純粋仮想関数と抽象クラス

ポリモーフィズムでは、親クラスでの仮想関数の実装は通常は無意味で、主にサブクラスによって書き換えられた内容を呼び出します。

したがって、仮想関数は純粋仮想関数に変更できます

純粋仮想関数の構文:virtual 返回值类型 函数名(参数列表) = 0;

クラス内に純粋仮想関数が存在する場合、このクラスは抽象クラス とも呼ばれます。

抽象クラスの機能:

  • オブジェクトをインスタンス化できません。

  • サブクラスは、抽象クラスの純粋仮想関数を書き換える必要があります。そうでない場合は、サブクラスも抽象クラスに属します。

例:

#include<iostream>
using namespace std;
class Base
{
    
    
public:
	//只要有一个纯虚函数,这个类就称为抽象类
	virtual void func() = 0;
};
class Son :public Base
{
    
    
public:
	int m_A;
};
class Son_02 :public Base
{
    
    
public:
	void func()
	{
    
    
		cout << "func的调用" << endl;
	}
	int m_A;
};
int main()
{
    
    
	//Base b;			   //无法实例化对象
	//Base* b = new Base;  //堆区也是不可以的
	//Son s;			   //子类不重写父类纯虚函数时,也会被认为是抽象类
	Son_02 s;              //重写虚函数后,可以实例化
	Base* base = new Son_02;
	base->func();
	system("pause");
}

19.4 多態性ケース 2 - 飲み物の作成

ケースの説明:

飲み物を作る一般的なプロセスは、お湯を沸騰させる - 抽出する - カップに注ぐ - 副原料を加えるという流れです。

このケースを実現するには、ポリモーフィック テクノロジを使用します。飲料を作成するための抽象基本クラスを提供し、コーヒーと紅茶を作成するためのサブクラスを提供します。
ここに画像の説明を挿入

例:

#include<iostream>
using namespace std;

class AbstractDrinking
{
    
    
public:
	//煮水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void PourInCup() = 0;
	//加入辅料
	virtual void PutSomething() = 0;
	//制作饮品
	void makeDrink()
	{
    
    
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};
//咖啡
class Coffee :public AbstractDrinking
{
    
    
public:
	//煮水
	void Boil()
	{
    
    
		cout << "煮农夫山泉" << endl;
	}
	//冲泡
	void Brew()
	{
    
    
		cout << "冲泡咖啡" << endl;
	}
	//倒入杯中
	void PourInCup()
	{
    
    
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	void PutSomething()
	{
    
    
		cout << "加入糖和牛奶" << endl;
	}
	
};
//茶叶
class Tea :public AbstractDrinking
{
    
    
public:
	//煮水
	void Boil()
	{
    
    
		cout << "煮开水" << endl;
	}
	//冲泡
	void Brew()
	{
    
    
		cout << "冲泡茶叶" << endl;
	}
	//倒入杯中
	void PourInCup()
	{
    
    
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	void PutSomething()
	{
    
    
		cout << "加入枸杞和柠檬" << endl;
	}
};
void dowork(AbstractDrinking* abs)
{
    
    
	abs->makeDrink();
	delete abs;
}
void test4_01()
{
    
    
	cout << "制作咖啡:" << endl;
	dowork(new Coffee);
	cout << "制作茶叶:" << endl;
	dowork(new Tea);
}
int main()
{
    
    
	test4_01();
	system("pause");
	return 0;
}

19.5 仮想および純粋仮想破壊

問題: ポリモーフィズムを使用する場合、サブクラスにヒープ領域に割り当てられたプロパティがある場合、親クラス ポインターは解放時にサブクラスのデストラクター コードを呼び出すことができません。デストラクタが仮想関数ではない場合、デストラクタは仮想関数ポインタを持たず、ポリモーフィズムの場合、関数は仮想関数ポインタを通じて呼び出されるため、デストラクタ チェーン全体は呼び出されません。

解決策: 親クラスのデストラクターを仮想デストラクターまたは純粋な仮想デストラクターに変更します。

仮想関数と純粋仮想関数デストラクターの共通機能:

  • 親クラス ポインターを解決してサブクラス オブジェクトを解放できます。つまり、サブクラスのデストラクターを呼び出すことができます。
  • どちらも特定の関数実装が必要であり、純粋な仮想デストラクターにも関数実装が必要で、内部定義と外部定義を使用するだけです。

仮想デストラクターと純粋な仮想デストラクターの違い:

  • 純粋な仮想デストラクターの場合、クラスは抽象クラスであり、オブジェクトをインスタンス化できません。

仮想デストラクターの構文:

virtual ~类名(){
    
    }

純粋な仮想デストラクターの構文:

//类内声明
virtual ~类名() = 0;
//类外定义
类名::~类名(){
    
    }

例:

#include<iostream>
using namespace std;
class Animal5
{
    
    
public:
	Animal5()
	{
    
    
		cout << "Animal5构造函数调用" << endl;
	}
	//虚析构
	//virtual ~Animal5()
	//{
    
    
	//	cout << "Animal5析构函数调用" << endl;
	//}
	//纯虚析构
	virtual ~Animal5() = 0;

	//纯虚函数
	virtual void speak() = 0;	
};
Animal5::~Animal5()
{
    
    
	cout << "Animal5析构函数调用" << endl;
}
class Cat5 :public Animal5
{
    
    
public:
	Cat5(string name)
	{
    
    
		cout << "Cat5构造函数" << endl;
		m_Name = new string(name);
	}
	virtual void speak()
	{
    
    
		cout << *m_Name << "小猫在说话" << endl;
	}
	~Cat5()
	{
    
    
		cout << "Cat5析构函数" << endl;
		if (m_Name != NULL)
		{
    
    
			delete m_Name;
			m_Name = NULL;
		}
	}
	string* m_Name;   //创建在堆区
};

void test5_01()
{
    
    
	Animal5* animal = new Cat5("汤姆");
	animal->speak();
	delete animal;
}
int main()
{
    
    
	test5_01();
	system("pause");
	return 0;
}

要約:

  • 仮想デストラクターまたは純粋仮想デストラクターは、親クラス ポインターを介してサブクラス オブジェクトを解放する問題を解決するために使用されます。
  • サブクラス内にヒープ領域データが存在しない場合、仮想デストラクタまたは純粋仮想デストラクタとして記述できない場合があります。
  • 純粋な仮想デストラクターを持つクラスも抽象クラスであるため、インスタンス化できません。

19.6 ポリモーフィズムのケース 3 - コンピューターのアセンブリ

コンピューターの主なコンポーネントは、CPU (コンピューティング用)、グラフィックス カード (表示用)、およびメモリー スティック (ストレージ用) です。
各パーツを抽象基本クラスにカプセル化し、Intel メーカーや Lenovo メーカーなど、さまざまなメーカーにさまざまなパーツを製造してもらいます。
コンピューターを動作させるための機能を提供し、各部分が動作するインターフェイスを呼び出すコンピューター クラスを作成します。
テスト中の作業用に 3 台の異なるコンピューターが組み立てられました。

例:

#include<iostream>
using namespace std;

class CPU
{
    
    
public:
	//抽象计算函数
	virtual void calculate() = 0;
};
class VideoCard
{
    
    
public:
	//抽象显示函数
	virtual void display() = 0;
};
class Memory
{
    
    
public:
	//抽象存储函数
	virtual void storage() = 0;
};

//传入零件的型号,使得电脑工作。
class Computer
{
    
    
public:
	Computer(CPU* cpu, VideoCard* vc, Memory* mem)
	{
    
    
		this->cpu = cpu;
		this->vc = vc;
		this->mem = mem;
	}
	void work()
	{
    
    
		cpu->calculate();
		vc->display();
		mem->storage();
	}
	~Computer()
	{
    
    
		if (cpu != NULL)
		{
    
    
			delete cpu;
			cpu = 0;
		}
		if (vc != NULL)
		{
    
    
			delete vc;
			vc = 0;
		}
		if (mem != NULL)
		{
    
    
			delete mem;
			mem = 0;
		}
	}
private:
	CPU* cpu;
	VideoCard* vc;
	Memory* mem;
};
//Inter
class InterCPU :public CPU
{
    
    
public:
	void calculate()
	{
    
    
		cout << "Intel的CPU开始计算了" << endl;
	}
};
class InterVideoCrad :public VideoCard
{
    
    
public:
	void display()
	{
    
    
		cout << "Inter的显卡开始显示了" << endl;
	}
};
class InterMemory :public Memory
{
    
    
public:
	void storage()
	{
    
    
		cout << "Inter的内存条开始存储了" << endl;
	}
};
//Lenovo
class LenovoCPU :public CPU
{
    
    
public:
	void calculate()
	{
    
    
		cout << "Lenovo的CPU开始计算了" << endl;
	}
};
class LenovoVideoCrad :public VideoCard
{
    
    
public:
	void display()
	{
    
    
		cout << "Lenovo的显卡开始显示了" << endl;
	}
};
class LenovoMemory :public Memory
{
    
    
public:
	void storage()
	{
    
    
		cout << "Lenovo的内存条开始存储了" << endl;
	}
};

void test6_01()
{
    
    
	//创建第一台电脑
	cout << "第一台电脑开始工作" << endl;
	CPU* intercpu = new InterCPU;
	VideoCard* intercard = new InterVideoCrad;
	Memory* intermemory = new InterMemory;
	Computer* computer1 = new Computer(intercpu, intercard, intermemory);
	computer1->work();
	delete computer1;

	//创建第二台电脑
	cout <<"-----------------\n" << "第二台电脑开始工作" << endl;
	CPU* lenovocpu = new LenovoCPU;
	VideoCard* lenovocard = new LenovoVideoCrad;
	Memory* lenovomemory = new LenovoMemory;
	Computer* computer2 = new Computer(lenovocpu, lenovocard, lenovomemory);
	computer2->work();
	delete computer2;

	//创建第三台电脑
	cout << "-----------------\n" << "第三台电脑开始工作" << endl;
	Computer* computer3 = new Computer(new LenovoCPU, new InterVideoCrad, new LenovoMemory);
	computer3->work();
	delete computer3;

}
int main()
{
    
    
	test6_01();
	system("pause");
	return 0;
}

おすすめ

転載: blog.csdn.net/qq_49030008/article/details/123354311