C++ classes and objects - polymorphism

Article directory

foreword

        Polymorphism is one of the three object-oriented features of C++. Polymorphism is an important mechanism in C++ that allows different objects to use the same interface but implement it differently. Specifically, polymorphism allows an object to exhibit different types of behavior at runtime, which enables code flexibility and scalability.

        Polymorphism in C++ can be achieved through inheritance and virtual functions. When the function name and parameter list in the base class and the derived class are the same, if the function is declared as a virtual function in the base class, then when using the derived class object, calling this function through the pointer or reference of the base class will The corresponding function will be dynamically bound according to the type of the actual object, thereby achieving polymorphism.

Polymorphism is divided into two categories

1. Static polymorphism : Function overloading and operator overloading belong to static polymorphism, reusing function names

2. Dynamic polymorphism : derived classes and virtual functions implement runtime polymorphism

The difference between static polymorphism and dynamic polymorphism :

Static polymorphic function address early binding - determine the function address during compilation

Dynamic polymorphic function address late binding - determine function address during        runtime


1. Polymorphic Basic Grammar

        For example, suppose you have an Animal class with a virtual method named "speak". Now we create a Dog class and a Cat class, both of which inherit from the Animal class, overriding the "speak" method. When we point an Animal pointer to a Dog object and call the "speak" method, the Dog's "speak" method will be called. Similarly, when the Animal pointer points to a Cat object, the "speak" method of Cat will be called. This is the embodiment of polymorphism.

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

        In the above example, the parent class and the subclass each contain the member function speak, and the dospeak function requires the reference of the parent class animal to be passed in. However, in the test function test01(), we want to implement the function call of the subclass cat or dog, because the parent If the class address is bound early, it will only call the member function in the parent class, so you need to use the virtual function (keyword: virtual), add virtual before the member function of the parent class, you can realize the late binding of the address, and you can call out Member functions in subclasses.

        The address is bound early, and the address of the function is determined at the compilation stage, so it is impossible to make "cat talk" and "dog talk". If you want to make the cat talk, then the address of this function cannot be bound in advance, it needs to be bound during the running phase, that is, the address is bound late, and dynamic polymorphism needs to be used.

Dynamic polymorphism satisfies the conditions : 1. There needs to be an inheritance relationship. 2. The subclass overrides the virtual function of the parent class

Rewriting : The function return value type, function name, and parameter list are exactly the same.

The use of dynamic polymorphism : the pointer or reference of the parent class, pointing to the object of the subclass

1.1 Case 1

Advantages of polymorphism:
1. Clear code organization structure
2. Strong readability
3. Facilitate early and later expansion and maintenance

Advocate the open-closed principle in real development (open-closed principle: develop for extensions, close for modifications)

The following is an example of a calculator, which uses the traditional writing method and the polymorphic writing method respectively. The code is as follows:

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

 Conditions for polymorphic use : the parent class uses pointers or references to subclass objects

        In this example, the pointer method of the parent class is used, and a pointer abs of the parent class of abstractCalculator needs to be created, pointing to the addcalculator (or other four arithmetic functions) created with new, and it needs to be destroyed with delete after use. From this case, we can see the benefits of using polymorphism: 1. The organizational structure is clear. 2. Strong readability. 3. For early and late expansion and high maintenance 

2. Pure virtual functions and abstract classes 

        In polymorphism, the implementation of virtual functions in the parent class is usually meaningless, mainly calling the content rewritten by the subclass. Therefore, virtual functions can be changed to pure virtual functions.

Pure virtual function syntax : virtual return value type function name (parameter list) = 0 ;

When there is a pure virtual function in the class, this class is also called an abstract class .

Abstract class characteristics

1. The object cannot be instantiated.
2. The subclass must rewrite the pure virtual function in the abstract class, otherwise it also belongs to the abstract class.

The following is the example code:

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

In the above case, the parent class is a pure virtual function, so this class is an abstract class, and the subclass in the abstract class must rewrite the parent class.

2.1 Case 2

 Case 2: Making drinks.

Case description: The general process
of making drinks is: Boil water - Brew - Pour into cups - Add auxiliary materials Use polymorphic technology to realize this case, provide an abstract base class for making drinks, and provide subclasses for making coffee and tea

#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 this case, the method of creating a parent class pointer and establishing polymorphism is used. 

3. Virtual destructor and pure virtual destructor 

When polymorphism is used, if there are attributes in the subclass that are allocated to the heap area, then the parent class pointer cannot call the destructor code of the subclass when it is released. Solution : Change the destructor in the parent class to virtual destructor or pure virtual destructor.

Common features of virtual destructor and pure virtual destructor :

1. It can solve the parent class pointer to release the subclass object
2. Both need to have specific function implementation

The difference between virtual destructor and pure virtual destructor : if it is pure virtual destructor, the class belongs to the oil class and cannot instantiate the object 

Virtual destructor syntax :
virtual ~ class name () {}

Pure virtual destructor syntax :
virtual ~class name()= 0;
write outside the class: class name::~class name(){}

        The following is a case where attributes are created in the subclass cat heap area. When the parent class pointer is destructed, the destructor in the subclass will not be called, and memory leaks occur. Virtual destructors can be used to solve the problem of releasing the parent class pointer. Unclean issue when subclassing objects. Two methods are provided in the following code, one is virtual destructor, and the other is to create pure virtual destructor (the two methods cannot be used at the same time, method one is commented out in the case).

#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 Case 3 - Computer Assembly

        The main components of a computer are the CPU (for computing), the graphics card (for display), and the memory stick (for storage). Each part is encapsulated into an abstract base class, and different manufacturers are provided to produce different parts, such as Intel manufacturers and The Lenovo manufacturer creates a computer class to provide functions to make the computer work, and assembles three different computers to work when calling the interface of each part to work. 

 Design flow chart

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

         Virtual destructor is used in polymorphism, only the destructor of the parent class is called, and the destructor of the subclass is not called, so the heap memory created in the subclass cannot be released. But in this case, no heap memory is created in the subclass. 1. Here, the Computer instance can be instantiated directly without using a pointer. Because the establishment and destruction of the Computer instance are under the same function, it can be stored in the stack area or the heap area. 2. There is no need for virtual destruction, because the three component heap areas pointed to by the new Computer heap area have been released in the Computer destructor 3. Computer belongs to a single class, and its destruction will not affect the destruction of other classes, so no need for virtual destructor


Summarize

         Polymorphism in C++ can be achieved through inheritance and virtual functions. When the function name and parameter list in the base class and the derived class are the same, if the function is declared as a virtual function in the base class, then when using the derived class object, calling this function through the pointer or reference of the base class will The corresponding function will be dynamically bound according to the type of the actual object, thereby achieving polymorphism.

        When polymorphism is used, if there are attributes in the subclass that are allocated to the heap area, then the parent class pointer cannot call the destructor code of the subclass when it is released. At this time, you can change the destructor in the parent class to a virtual destructor or a pure virtual destructor, so that the memory of the subclass can be released and memory leaks can be prevented.

Guess you like

Origin blog.csdn.net/m0_74893211/article/details/130875766