C++ クラスとオブジェクト - ポリモーフィズム

記事ディレクトリ

序文

        ポリモーフィズムは、C++ の 3 つのオブジェクト指向機能の 1 つです。ポリモーフィズムは、異なるオブジェクトが同じインターフェイスを使用しながら、異なる方法で実装できるようにする C++ の重要なメカニズムです。具体的には、ポリモーフィズムにより、オブジェクトが実行時にさまざまなタイプの動作を示すことができるため、コードの柔軟性とスケーラビリティが可能になります。

        C++ のポリモーフィズムは、継承と仮想関数によって実現できます。基底クラスと派生クラスの関数名とパラメータリストが同じ場合、基底クラスで関数が仮想関数として宣言されている場合、派生クラスのオブジェクトを使用する際に、この関数をポインタまたは参照を通じて呼び出します。基本クラスは、対応する関数が実際のオブジェクトの型に従って動的にバインドされ、それによってポリモーフィズムを実現します。

ポリモーフィズムは 2 つのカテゴリに分類されます

1.静的多態性: 関数のオーバーロードと演算子のオーバーロードは静的多態性に属し、関数名を再利用します。

2.動的ポリモーフィズム: 派生クラスと仮想関数は実行時ポリモーフィズムを実装します。

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

静的多態性関数アドレスの早期バインディング -コンパイル中に関数アドレスを決定します

動的多態性関数アドレス遅延バインディング -実行時に関数アドレスを決定します       


1. 多態性の基本文法

        たとえば、「speak」という名前の仮想メソッドを持つ Animal クラスがあるとします。次に、Dog クラスと Cat クラスを作成します。どちらも Animal クラスを継承し、「speak」メソッドをオーバーライドします。Animal ポインタを Dog オブジェクトにポイントし、「speak」メソッドを呼び出すと、Dog の「speak」メソッドが呼び出されます。同様に、Animal ポインタが Cat オブジェクトを指している場合、Cat の「speak」メソッドが呼び出されます。これはポリモーフィズムの具体化です。

#include<iostream>
using namespace std;
//多态
//动物类
class Animal
{
public:
	//利用虚函数地址晚绑定
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
//猫类
class cat :public Animal
{
public:
	//重写:函数返回值类型、函数名、参数列表完全相同
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
//狗类
class dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};
//执行说话的函数
//地址早绑定,在编译阶段就确定函数的地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,就是地址晚绑定
//动态多态满足条件:1.需要有继承关系。 2.子类重写父类的虚函数
void dospeak(Animal &animal)
{
	animal.speak();
}
void test01()
{
	cat c1;
	dospeak(c1);//想执行cat就需要地址晚绑定,需要利用动态多态。
	dog d1;
	dospeak(d1);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

        上記の例では、親クラスとサブクラスのそれぞれにメンバー関数 speech が含まれており、dospeak 関数には親クラスのanimal の参照を渡す必要があります。ただし、テスト関数 test01() では、サブクラスの cat または Dog の関数呼び出し。親クラスのアドレスが早期にバインドされている場合、親クラスのメンバー関数のみが呼び出されるため、virtual 関数 (キーワード: virtual) を使用する必要があり、その前に virtual を追加します。親クラスのメンバー関数を使用して、アドレスの遅延バインディングを実現したり、サブクラスのメンバー関数を呼び出すことができます。

        アドレスは初期にバインドされており、関数のアドレスはコンパイル段階で決定されるため、「猫の話」と「犬の話」は不可能です。猫にしゃべらせたい場合は、この関数のアドレスを事前にバインドすることはできません。実行フェーズ中にバインドする必要があります。つまり、アドレスは後でバインドされ、動的ポリモーフィズムを使用する必要があります。

動的ポリモーフィズムは次の条件を満たします。 1. 継承関係が存在する必要があります。2. サブクラスは親クラスの仮想関数をオーバーライドします。

書き換え:関数の戻り値の型、関数名、パラメータリストは全く同じです。

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

1.1 ケース 1

ポリモーフィズムの利点:
1. 明確なコード構成構造
2. 優れた可読性
3. 初期および後の拡張とメンテナンスの容易化

実際の開発におけるオープンクローズ原則(オープンクローズ原則:拡張には開発、修正にはクローズ)を提唱します。

以下は、従来の書き込み方法とポリモーフィック書き込み方法をそれぞれ使用する計算機の例です。コードは次のとおりです。

#include<iostream>
using namespace std;
#include<cstring>
//多态案例——计算机类
//分别利用普通的写法和多态技术实现计算器

//普通写法
class calculator//
{
public:
	int getresult(string oper)
	{
		if (oper == "+")
		{
			return m_num1 + m_num2;
		}
		if (oper == "-")
		{
			return m_num1 - m_num2;
		}
		if (oper == "*")
		{
			return m_num1 * m_num2;
		}
		if (oper == "/")
		{
			return m_num1 / m_num2;
		}
	}
	int m_num1;
	int m_num2;
};
void test01()
{
	calculator c1;
	c1.m_num1 = 20;
	c1.m_num2 = 10;
	cout << c1.m_num1 << "+" << c1.m_num2 << "=" << c1.getresult("+") << endl;
	cout << c1.m_num1 << "-" << c1.m_num2 << "=" << c1.getresult("-") << endl;
	cout << c1.m_num1 << "*" << c1.m_num2 << "=" << c1.getresult("*") << endl;
	cout << c1.m_num1 << "/" << c1.m_num2 << "=" << c1.getresult("/") << endl;
}
//在真实的开发中 提倡开闭原则
//开闭原则:对扩展进行开放,对修改进行关闭
//利用多态实现计算器的好处:1.组织结构清晰。 2.可读性强。 3.对于前期和后期扩展以及维护性高
class abstractCalculator//实现一个计算器的抽象类
{
public:
	virtual int getresult()//因为多态条件:有继承关系,关键字virtual
	{
		return 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;
	}
};
//除法计算器类
class divcalculator :public abstractCalculator
{
public:
	int getresult()
	{
		return m_num1 / m_num2;
	}
};
void test02()
{
	//多态使用的条件
	//父类指针或者引用指向子类对象
	//加法运算
	abstractCalculator * abs= new addcalculator;
	abs->m_num1 = 20;
	abs->m_num2 = 10;
	cout << abs->m_num1 << "+" << abs->m_num2 << "=" << abs->getresult() << endl;
	//new放在堆区的对象用完后需要析构销毁
	delete abs;

	//减法运算
	abs = new subcalculator;
	abs->m_num1 = 20;
	abs->m_num2 = 10;
	cout << abs->m_num1 << "-" << abs->m_num2 << "=" << abs->getresult() << endl;
	delete abs;

	//乘法运算
	abs = new mulcalculator;
	abs->m_num1 = 20;
	abs->m_num2 = 10;
	cout << abs->m_num1 << "*" << abs->m_num2 << "=" << abs->getresult() << endl;
	delete abs;

	//除法运算
	abs = new divcalculator;
	abs->m_num1 = 20;
	abs->m_num2 = 10;
	cout << abs->m_num1 << "/" << abs->m_num2 << "=" << abs->getresult() << endl;
	delete abs;
}

int main()
{
	//test01();
	test02();
	return 0;
}

 ポリモーフィックな使用の条件: 親クラスがサブクラス オブジェクトへのポインタまたは参照を使用する

        この例では、親クラスのポインタ メソッドが使用されており、abstractCalculator の親クラスのポインタ abs を作成し、new で作成した addcalculator (または他の四則演算関数) を指し、それを破棄する必要があります。使用後は削除してください。この事例から、ポリモーフィズムを使用する利点がわかります。 1. 組織構造が明確です。2. 優れた可読性。3. 初期および後期の拡張および高度なメンテナンス向け 

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

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

純粋仮想関数の構文: 仮想戻り値型関数名 (パラメータリスト) = 0 ;

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

抽象クラスの特性

1. オブジェクトをインスタンス化できません。
2. サブクラスは、抽象クラスの純粋仮想関数を書き換える必要があります。そうでない場合は、サブクラスも抽象クラスに属します。

コード例は次のとおりです。

#include<iostream>
using namespace std;
//纯虚函数和抽象类
class base
{
public:
	virtual void fun() = 0;//纯虚函数语法:virtual 返回值类型 函数名(参数列表) = 0
	//只要有一个纯虚函数,这个类被称为抽象类
	//抽象类的特点:1.无法实例化对象。2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
};
class son :public base
{
public:
	void fun()
	{
		cout << "fun()调用" << endl;
	}
};
void test01()
{
	base* b = new son;
	b->fun();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

上記の場合、親クラスは純粋仮想関数であるため、このクラスは抽象クラスとなり、抽象クラス内のサブクラスは親クラスを書き換える必要があります。

2.1 ケース2

 事例2:ドリンク作り。

ケースの説明:
飲み物を作る一般的なプロセスは次のとおりです: 水を沸騰させる - 淹れる - カップに注ぐ - 補助材料を追加するこのケースを実現するためにポリモーフィック テクノロジ
を使用し、飲み物を作るための抽象基本クラスを提供し、コーヒーと紅茶を作るためのサブクラスを提供します

#include<iostream>
using namespace std;
//多态案例2 制作饮品
class abstractdrinking
{
public:
	//煮水
	virtual void boil() = 0;
	//冲泡
	virtual void brew() = 0;
	//倒入杯中
	virtual void pourcup() = 0;
	//加入辅料
	virtual void putsth() = 0;
	//制作饮品全流程
	void makedrink()
	{
		boil();
		brew();
		pourcup();
		putsth();
	}
};
//制作咖啡
class coffee :public abstractdrinking
{
	//煮水
	virtual void boil()
	{
		cout << "煮雪水" << endl;
	}
	//冲泡
	virtual void brew()
	{
		cout << "冲咖啡" << endl;
	}
	//倒入杯中
	virtual void pourcup()
	{
		cout<<"倒入咖啡杯" << endl;
	}
	//加入辅料
	virtual void putsth()
	{
		cout << "加入糖和牛奶" << endl;
	}
};
//制作茶
class tea :public abstractdrinking
{
	//煮水
	virtual void boil()
	{
		cout << "煮山泉水" << endl;
	}
	//冲泡
	virtual void brew()
	{
		cout << "冲茶叶" << endl;
	}
	//倒入杯中
	virtual void pourcup()
	{
		cout << "倒入茶杯" << endl;
	}
	//加入辅料
	virtual void putsth()
	{
		cout << "加入枸杞" << endl;
	}
};
//制作的函数
void dowork(abstractdrinking*abs)
{
	abs->makedrink();
	delete abs;
}
void test01()
{
	//制作咖啡
	dowork(new coffee);
	cout << "-------------------" << endl;
	//制作茶叶
	dowork(new tea);

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

この場合、親クラスポインタを作成し、ポリモーフィズムを確立する方法が使用される。 

3. 仮想デストラクタと純粋仮想デストラクタ 

ポリモーフィズムを使用する場合、サブクラスにヒープ領域に割り当てられた属性があると、親クラスのポインタは解放時にサブクラスのデストラクタコードを呼び出すことができません。解決策: 親クラスのデストラクターを仮想デストラクターまたは純粋な仮想デストラクターに変更します。

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

1. 親クラス ポインタを解決してサブクラス オブジェクトを解放できます。
2. どちらも特定の関数を実装する必要があります。

仮想デストラクターと純粋な仮想デストラクターの違い: 純粋な仮想デストラクターの場合、クラスはオイル クラスに属し、オブジェクトをインスタンス化できません 

仮想デストラクタの構文:
virtual ~ クラス名 () {}

純粋な仮想デストラクタ構文:
virtual ~class name()= 0;
クラスの外に書き込みます: class name::~class name(){}

        以下はサブクラスのcatヒープ領域に属性を作成する場合です 親クラスのポインタが破棄されるとサブクラスのデストラクタが呼び出されなくなりメモリリークが発生します 解放の問題は仮想デストラクタを使用することで解決できます親クラス ポインタ オブジェクトをサブクラス化するときのクリーンでない問題。次のコードでは 2 つのメソッドが提供されています。1 つは仮想デストラクターで、もう 1 つは純粋な仮想デストラクターを作成するものです (2 つのメソッドを同時に使用することはできません。この場合、メソッド 1 はコメント化されています)。

#include<iostream>
using namespace std;
#include<string>
//虚析构和纯虚析构
class animal
{
public:
	animal()
	{
		cout << "animal构造函数调用" << endl;
	}
	//方法一:
	//virtual ~animal()//利用虚析构可以解决父类指针释放子类对象时不干净的问题
	//{
	//	cout << "animal析构函数调用" << endl;
	//}
	//方法二:
	virtual ~animal() = 0;//纯虚析构
	virtual void speak() = 0;//纯虚函数
	//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
};
//纯虚析构需要在类外写具体实现
animal::~animal()
{
	cout << "animal纯虚析构函数调用" << endl;
}
class cat:public animal
{
public:
	cat(string name)
	{
		cout << "cat的构造函数调用" << endl;
		m_name=new string(name);
	}
	void speak()
	{
		cout <<*m_name<< "小猫在说话" << endl;
	}
	string *m_name;
	~cat()//delete是清空指针区域的内容,并不清空指针,NULL是把指针替换为NULL地址
	{
		cout << "cat析构函数调用" << endl;
		if (m_name != NULL)
		{
			delete m_name;
			m_name = NULL;
		}
	}
};
void test01() 
{
	animal* a1 = new cat("Tom");//父类指针指向子类对象
	a1->speak();
	delete a1;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

3.1 ケース 3 - コンピューターの組み立て

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

 設計フローチャート

#include<iostream>
using namespace std;
#include<string>
//电脑组装案例
//1.抽象出每个零件的类
class cpu//抽象CPU类
{
public:
	virtual void calculate() = 0;
};
class videocard//抽象显卡类
{
public:
	virtual void display() = 0;
};
class memory//抽象内存条类
{
public:
	virtual void storage() = 0;
};
//2.构造电脑类
class computer
{
public:
	computer(cpu* c, videocard* v, memory* m)
	{
		m_c = c;
		m_v = v;
		m_m = m;
	}
	//工作函数
	void dowork()
	{
		//让零件工作起来,调用接口
		m_c->calculate();
		m_v->display();
		m_m->storage();
	}
	//提供析构函数 释放3个电脑零件
	~computer()
	{
		if (m_c != NULL)//释放CPU
		{
			delete m_c;
			m_c = NULL;
		}
		if (m_v != NULL)//释放显卡
		{
			delete m_v;
			m_v = NULL;
		}
		if (m_m != NULL)//释放内存条
		{
			delete m_m;
			m_m = NULL;
		}
	}
private:
	cpu* m_c;
	videocard* m_v;
	memory* m_m;
};
//3.具体厂商
//英特尔厂商
class InterCPU :public cpu
{
public:
	virtual void calculate()
	{
		cout << "Inter的CPU开始计算了" << endl;
	}
};
class InterVideoCard :public videocard
{
public:
	virtual void display()
	{
		cout << "Inter的VideoCard开始显示了" << endl;
	}
};
class InterMemory :public memory
{
public:
	virtual void storage()
	{
		cout << "Inter的Memory开始存储了" << endl;
	}
};
//联想厂商
class LenoveCPU :public cpu
{
public:
	virtual void calculate()
	{
		cout << "Lenove的CPU开始计算了" << endl;
	}
};
class LenoveVideoCard :public videocard
{
public:
	virtual void display()
	{
		cout << "Lenove的VideoCard开始显示了" << endl;
	}
};
class LenoveMemory :public memory
{
public:
	virtual void storage()
	{
		cout << "Lenove的Memory开始存储了" << endl;
	}
};

void test01()
{
	//第一台电脑零件   ——用父类指针指向子类对象
	cpu * intelcpu = new InterCPU;
	videocard* intelcard = new InterVideoCard;
	memory* intelmem = new InterMemory;
	//创建第一台电脑
	cout << "第一台电脑" << endl;
	computer* com1 = new computer(intelcpu, intelcard, intelmem);
	com1->dowork();
	delete com1;
	cout << "--------------------------" << endl;
	//创建第二台电脑
	cout << "第二台电脑" << endl;
	computer* com2 = new computer(new LenoveCPU, new LenoveVideoCard,new LenoveMemory);
	com2->dowork();
	delete com2;
	cout << "--------------------------" << endl;
	//创建第三台电脑
	cout << "第三台电脑" << endl;
	computer* com3 = new computer(new InterCPU, new LenoveVideoCard, new LenoveMemory);
	com3->dowork();
	delete com3;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

         仮想デストラクタはポリモーフィズムで使用され、親クラスのデストラクタのみが呼び出され、サブクラスのデストラクタは呼び出されないため、サブクラス内に作成されたヒープメモリは解放できません。ただし、この場合、サブクラスにはヒープ メモリは作成されません。1. ここで、Computer インスタンスはポインタを使わずに直接インスタンス化することができ、Computer インスタンスの確立と破棄が同一の機能となるため、スタック領域やヒープ領域に格納することが可能です。2. 新しいコンピュータ ヒープ領域が指す 3 つのコンポーネント ヒープ領域がコンピュータ デストラクタで解放されているため、仮想的な破壊は必要ありません。 3. コンピュータは 1 つのクラスに属しており、その破壊は他のクラスの破壊には影響しません。クラスなので、仮想デストラクターは必要ありません


要約する

         C++ のポリモーフィズムは、継承と仮想関数によって実現できます。基底クラスと派生クラスの関数名とパラメータリストが同じ場合、基底クラスで関数が仮想関数として宣言されている場合、派生クラスのオブジェクトを使用する際に、この関数をポインタまたは参照を通じて呼び出します。基本クラスは、対応する関数が実際のオブジェクトの型に従って動的にバインドされ、それによってポリモーフィズムを実現します。

        ポリモーフィズムを使用する場合、サブクラスにヒープ領域に割り当てられた属性があると、親クラスのポインタは解放時にサブクラスのデストラクタコードを呼び出すことができません。このとき、親クラスのデストラクタを仮想デストラクタまたは純粋仮想デストラクタに変更することで、サブクラスのメモリを解放し、メモリリークを防ぐことができます。

おすすめ

転載: blog.csdn.net/m0_74893211/article/details/130875766