C++-Klassen und -Objekte – Polymorphismus

Artikelverzeichnis

Vorwort

        Polymorphismus ist eine der drei objektorientierten Funktionen von C++. Polymorphismus ist ein wichtiger Mechanismus in C++, der es verschiedenen Objekten ermöglicht, dieselbe Schnittstelle zu verwenden, sie aber unterschiedlich zu implementieren. Insbesondere ermöglicht Polymorphismus, dass ein Objekt zur Laufzeit unterschiedliche Verhaltensweisen zeigt, was Codeflexibilität und Skalierbarkeit ermöglicht.

        Polymorphismus in C++ kann durch Vererbung und virtuelle Funktionen erreicht werden. Wenn der Funktionsname und die Parameterliste in der Basisklasse und der abgeleiteten Klasse identisch sind und die Funktion in der Basisklasse als virtuelle Funktion deklariert ist, wird diese Funktion bei Verwendung des abgeleiteten Klassenobjekts über den Zeiger oder die Referenz aufgerufen Die Basisklasse wird entsprechend der Art des tatsächlichen Objekts dynamisch an die entsprechende Funktion gebunden, wodurch Polymorphismus erreicht wird.

Polymorphismus wird in zwei Kategorien unterteilt

1. Statischer Polymorphismus : Funktionsüberladung und Operatorüberladung gehören zum statischen Polymorphismus, bei dem Funktionsnamen wiederverwendet werden

2. Dynamischer Polymorphismus : Abgeleitete Klassen und virtuelle Funktionen implementieren Laufzeitpolymorphismus

Der Unterschied zwischen statischem Polymorphismus und dynamischem Polymorphismus :

Frühe Bindung der statischen polymorphen Funktionsadresse Bestimmen Sie die Funktionsadresse während der Kompilierung

Dynamische polymorphe Funktionsadresse späte Bindung – Funktionsadresse während        der Laufzeit bestimmen


1. Polymorphe Grundgrammatik

        Angenommen, Sie haben eine Animal-Klasse mit einer virtuellen Methode namens „speak“. Jetzt erstellen wir eine Dog-Klasse und eine Cat-Klasse, die beide von der Animal-Klasse erben und die „speak“-Methode überschreiben. Wenn wir einen Animal-Zeiger auf ein Dog-Objekt zeigen und die „speak“-Methode aufrufen, wird die „speak“-Methode des Dogs aufgerufen. Wenn der Animal-Zeiger auf ein Cat-Objekt zeigt, wird in ähnlicher Weise die „speak“-Methode von Cat aufgerufen. Dies ist die Verkörperung des Polymorphismus.

#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;
}

        Im obigen Beispiel enthalten die übergeordnete Klasse und die untergeordnete Klasse jeweils die Mitgliedsfunktion speak. Die dospeak-Funktion erfordert die Übergabe der Referenz des übergeordneten Klassentiers. In der Testfunktion test01() jedoch der Funktionsaufruf der Unterklasse Katze oder Hund sind erforderlich. Da die Adresse der übergeordneten Klasse früh gebunden ist, werden nur die Mitgliedsfunktionen in der übergeordneten Klasse aufgerufen. Sie müssen daher die virtuelle Funktion (Schlüsselwort: virtuell) verwenden und vor der Mitgliedsfunktion von „virtual“ hinzufügen In der übergeordneten Klasse können Sie die späte Bindung der Adresse realisieren und Mitgliedsfunktionen in Unterklassen aufrufen.

        Die Adresse wird früh gebunden und die Adresse der Funktion wird in der Kompilierungsphase bestimmt, sodass es unmöglich ist, „Katzengespräch“ und „Hundegespräch“ zu erzeugen. Wenn Sie die Katze zum Sprechen bringen möchten, kann die Adresse dieser Funktion nicht im Voraus gebunden werden. Sie muss während der Ausführungsphase gebunden werden, dh die Adresse wird spät gebunden und es muss dynamischer Polymorphismus verwendet werden.

Der dynamische Polymorphismus erfüllt die folgenden Bedingungen : 1. Es muss eine Vererbungsbeziehung bestehen. 2. Die Unterklasse überschreibt die virtuelle Funktion der übergeordneten Klasse

Umschreiben : Der Rückgabewerttyp der Funktion, der Funktionsname und die Parameterliste sind genau gleich.

Die Verwendung von dynamischem Polymorphismus : Der Zeiger oder die Referenz der übergeordneten Klasse zeigt auf das Objekt der Unterklasse

1.1 Fall 1

Vorteile des Polymorphismus:
1. Klare Code-Organisationsstruktur
2. Gute Lesbarkeit
3. Erleichtert frühe und spätere Erweiterung und Wartung

Befürworten Sie das Open-Closed-Prinzip in der realen Entwicklung (Open-Closed-Prinzip: für Erweiterungen entwickeln, für Änderungen schließen)

Das Folgende ist ein Beispiel für einen Taschenrechner, der die traditionelle Schreibmethode bzw. die polymorphe Schreibmethode verwendet. Der Code lautet wie folgt:

#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;
}

 Bedingungen für die polymorphe Verwendung : Die übergeordnete Klasse verwendet Zeiger oder Referenzen auf Unterklassenobjekte

        In diesem Beispiel wird die Zeigermethode der übergeordneten Klasse verwendet, und es muss ein Zeiger abs der übergeordneten Klasse von abstractCalculator erstellt werden, der auf den mit new erstellten addcalculator (oder andere vier arithmetische Funktionen) zeigt und zerstört werden muss mit Löschen nach Gebrauch. Anhand dieses Falles können wir die Vorteile der Verwendung von Polymorphismus erkennen: 1. Die Organisationsstruktur ist klar. 2. Gute Lesbarkeit. 3. Für frühe und späte Erweiterung und hohen Wartungsaufwand 

2. Reine virtuelle Funktionen und abstrakte Klassen 

        Beim Polymorphismus ist die Implementierung virtueller Funktionen in der übergeordneten Klasse normalerweise bedeutungslos und ruft hauptsächlich den von der Unterklasse neu geschriebenen Inhalt auf. Daher können virtuelle Funktionen in rein virtuelle Funktionen umgewandelt werden.

Syntax rein virtueller Funktionen : Funktionsname des virtuellen Rückgabewerttyps (Parameterliste) = 0;

Wenn die Klasse eine rein virtuelle Funktion enthält, wird diese Klasse auch als abstrakte Klasse bezeichnet .

Abstrakte Klassenmerkmale

1. Das Objekt kann nicht instanziiert werden.
2. Die Unterklasse muss die rein virtuelle Funktion in der abstrakten Klasse neu schreiben, andernfalls gehört sie auch zur abstrakten Klasse.

Das Folgende ist der Beispielcode:

#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;
}

Im obigen Fall ist die übergeordnete Klasse eine rein virtuelle Funktion, sodass diese Klasse eine abstrakte Klasse ist und die Unterklasse in der abstrakten Klasse die übergeordnete Klasse neu schreiben muss.

2.1 Fall 2

 Fall 2: Getränke zubereiten.

Fallbeschreibung: Der allgemeine Prozess
der Getränkezubereitung ist: Wasser kochen – brauen – in Tassen gießen – Hilfsstoffe hinzufügen. Verwenden Sie polymorphe Technologie , um diesen Fall zu realisieren, stellen Sie eine abstrakte Basisklasse für die Getränkezubereitung und Unterklassen für die Kaffee- und Teezubereitung bereit

#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;
}

In diesem Fall wird die Methode zum Erstellen eines übergeordneten Klassenzeigers und zum Einrichten von Polymorphismus verwendet. 

3. Virtueller Destruktor und rein virtueller Destruktor 

Wenn bei Verwendung von Polymorphismus Attribute in der Unterklasse vorhanden sind, die dem Heap-Bereich zugeordnet sind, kann der Zeiger der übergeordneten Klasse den Destruktorcode der Unterklasse nicht aufrufen, wenn er freigegeben wird. Lösung : Ändern Sie den Destruktor in der übergeordneten Klasse in einen virtuellen Destruktor oder einen rein virtuellen Destruktor.

Gemeinsame Merkmale des virtuellen Destruktors und des rein virtuellen Destruktors :

1. Es kann den übergeordneten Klassenzeiger auflösen, um das Unterklassenobjekt freizugeben.
2. Beide benötigen eine spezifische Funktionsimplementierung

Der Unterschied zwischen einem virtuellen Destruktor und einem rein virtuellen Destruktor : Wenn es sich um einen rein virtuellen Destruktor handelt, gehört die Klasse zur Ölklasse und kann das Objekt nicht instanziieren 

Syntax des virtuellen Destruktors :
virtual ~ class name () {}

Syntax rein virtueller Destruktoren :
virtual ~class name()= 0;
außerhalb der Klasse schreiben: class name::~class name(){}

        Das Folgende ist ein Fall, in dem Attribute im Cat-Heap-Bereich der Unterklasse erstellt werden. Wenn der Zeiger der übergeordneten Klasse zerstört wird, wird der Destruktor in der Unterklasse nicht aufgerufen und es treten Speicherverluste auf. Virtuelle Destruktoren können verwendet werden, um das Problem der Freigabe zu lösen der Zeiger der übergeordneten Klasse. Unsauberes Problem beim Unterklassifizieren von Objekten. Im folgenden Code werden zwei Methoden bereitgestellt, eine ist ein virtueller Destruktor und die andere dient der Erstellung eines rein virtuellen Destruktors (die beiden Methoden können nicht gleichzeitig verwendet werden, und Methode eins ist in diesem Fall auskommentiert).

#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 Fall 3 – Computermontage

        Die Hauptkomponenten eines Computers sind die CPU (für die Datenverarbeitung), die Grafikkarte (für die Anzeige) und der Memory Stick (für die Speicherung). Jeder Teil ist in einer abstrakten Basisklasse gekapselt, und verschiedene Hersteller werden bereitgestellt, um unterschiedliche Teile herzustellen , wie Intel-Hersteller und Der Lenovo-Hersteller erstellt eine Computerklasse, um Funktionen bereitzustellen, die den Computer zum Laufen bringen, und baut drei verschiedene Computer zusammen, um die Schnittstelle jedes Teils zum Laufen zu bringen. 

 Design-Flussdiagramm

#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;
}

         Beim Polymorphismus wird ein virtueller Destruktor verwendet. Es wird nur der Destruktor der übergeordneten Klasse aufgerufen, der Destruktor der Unterklasse wird jedoch nicht aufgerufen, sodass der in der Unterklasse erstellte Heap-Speicher nicht freigegeben werden kann. In diesem Fall wird jedoch kein Heap-Speicher in der Unterklasse erstellt. 1. Hier kann die Computerinstanz direkt ohne Verwendung eines Zeigers instanziiert werden. Da die Einrichtung und Zerstörung der Computerinstanz unter derselben Funktion erfolgt, kann sie im Stapelbereich oder im Heapbereich gespeichert werden. 2. Es besteht keine Notwendigkeit für eine virtuelle Zerstörung, da die drei Komponenten-Heap-Bereiche, auf die der neue Computer-Heap-Bereich zeigt, im Computer-Destruktor freigegeben wurden. 3. Der Computer gehört zu einer einzelnen Klasse und seine Zerstörung hat keinen Einfluss auf die Zerstörung anderer Klassen, daher ist kein virtueller Destruktor erforderlich


Zusammenfassen

         Polymorphismus in C++ kann durch Vererbung und virtuelle Funktionen erreicht werden. Wenn der Funktionsname und die Parameterliste in der Basisklasse und der abgeleiteten Klasse identisch sind und die Funktion in der Basisklasse als virtuelle Funktion deklariert ist, wird diese Funktion bei Verwendung des abgeleiteten Klassenobjekts über den Zeiger oder die Referenz aufgerufen Die Basisklasse wird entsprechend der Art des tatsächlichen Objekts dynamisch an die entsprechende Funktion gebunden, wodurch Polymorphismus erreicht wird.

        Wenn bei Verwendung von Polymorphismus Attribute in der Unterklasse vorhanden sind, die dem Heap-Bereich zugeordnet sind, kann der Zeiger der übergeordneten Klasse den Destruktorcode der Unterklasse nicht aufrufen, wenn er freigegeben wird. Zu diesem Zeitpunkt können Sie den Destruktor in der übergeordneten Klasse in einen virtuellen Destruktor oder einen rein virtuellen Destruktor ändern, sodass der Speicher der Unterklasse freigegeben und Speicherlecks verhindert werden können.

Supongo que te gusta

Origin blog.csdn.net/m0_74893211/article/details/130875766
Recomendado
Clasificación