[C + +] learning notes [12]

polymorphic

Basic concepts of polymorphism

Polymorphism is one of the three characteristics of C + + object-oriented

Polymorphisms fall into two categories

  • Static polymorphism: function overloading and operator overloading belong to static polymorphism and reuse function names
  • Dynamic polymorphism: derived classes and virtual functions implement runtime polymorphism

The difference between static polymorphism and dynamic polymorphism

  • Statically polymorphic function address early binding - the function address is determined at the compilation stage
  • Dynamic polymorphic function address late binding - function address determined at run time

Dynamic polymorphism meets the following conditions:

  • There is an inheritance relationship
  • The subclass should override the virtual function of the parent class of [function return value type, function name and parameter list should be exactly the same]

Use of dynamic polymorphism

  • The pointer or reference of the parent class points to the object of the child class

The simple exercise code is as follows:

#include <iostream>
using namespace std;

//Animals
class Animal
{
public:
	virtual void speak()
	{
		cout << "Animals are talking" << endl;
	}
};

class Cat :public Animal
{
	void speak()
	{
		cout << "The kitten is talking" << endl;
	}
};

class Dog :public Animal
{
	void speak()
	{
		cout << "The dog is talking" << endl;
	}
};

//Execute speech function
//The address is bound early, and the function address is determined at the compilation stage
//If you want to make the cat talk, the function address cannot be bound in advance, and the address needs to be bound late [virtual function mechanism]

void doSpeak(Animal& animal)//Reference to animal = cat
{
	animal.speak();
}

void test01()
{
	Cat cat;
	doSpeak(cat);
	Dog dog;
	doSpeak(dog);
}

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

. The operation results are as follows:


Let's use the developer command tool to check the layout of Animal and Cat classes:



We can see that in the Animal class, there is a virtual function pointer pointing to the virtual function table. The virtual function table stores the address of the virtual function. When the subclass rewrites the virtual function in the parent class, the virtual function table of the subclass becomes the virtual function address of the subclass itself.

Polymorphic case -- calculator class

Case description: a calculator class with two operands is designed by using common writing method and polymorphic technology

Advantages of polymorphism:

  • Clear code organization
  • Strong code readability
  • It is conducive to early and later expansion and maintenance

The common code is as follows

#include <iostream>
#include <string>
using namespace std;

//Calculator class
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;
		}
	}

	//If you want to extend new functions to calculators, you need to modify the source code
	//In real development, the principle of opening and closing is advocated
	//Opening / closing principle: open the extension and close the modification

	int m_num1;
	int m_num2;
};

void test01()
{
	//Create a calculator object
	Calculator c;
	c.m_num1 = 10;
	c.m_num2 = 10;

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

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

The operation results are as follows:


The disadvantage of implementing a calculator by ordinary methods is that if you want to extend new functions to the calculator, you must modify the source code.

The code of calculator realized by polymorphic technology is as follows:

#include <iostream>
#include <string>
using namespace std;

//Abstract classes that implement calculators
class AbstractCalculator
{

public:
	virtual int getResult()
	{
		return 0;
	}

	int m_num1;
	int m_num2;
};

//Design an addition calculator class
class AddCalculator :public AbstractCalculator
{
	int getResult()
	{
		return m_num1 + m_num2;
	}
};

//Design a subtraction calculator class
class SubCalculator :public AbstractCalculator
{
	int getResult()
	{
		return m_num1 - m_num2;
	}
};

//Design a multiplication calculator class
class MulCalculator :public AbstractCalculator
{
	int getResult()
	{
		return m_num1 * m_num2;
	}
};

void test01()
{
	//Polymorphic usage condition: the pointer or reference of the parent class points to the subclass object
	AbstractCalculator* a = new AddCalculator;
	a->m_num1 = 10;
	a->m_num2 = 20;

	cout << a->m_num1 << "+" << a->m_num2 << "=" << a->getResult() << endl;
	delete a;
}

void test02()
{
	AbstractCalculator* b = new SubCalculator;
	b->m_num1 = 10;
	b->m_num2 = 20;
	cout << b->m_num1 << "-" << b->m_num2 << "=" << b->getResult() << endl;
	delete b;
}

void test03()
{
	AbstractCalculator* c = new MulCalculator;
	c->m_num1 = 10;
	c->m_num2 = 20;
	cout << c->m_num1 << "*" << c->m_num2 << "=" << c->getResult() << endl;
	delete c;
}

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

The operation results are as follows:

Pure virtual functions and abstract classes

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

Syntax of pure virtual function: virtual return value type function name (parameter list) = 0;

When there are pure virtual functions in a class, this class is also called an abstract class.

Abstract class features:

  • Unable to instantiate object
  • Subclasses must override virtual functions in abstract classes, otherwise they also belong to abstract classes

As shown in the figure below, when a class is a virtual base class, it cannot instantiate an object.

Simple exercises for pure virtual functions and abstract classes:

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:

	//Pure virtual function:
	//As long as there is a pure virtual function, this class is called an abstract class
	//Abstract classes cannot instantiate objects
	//The subclass of an abstract class must override the pure virtual function in the parent class, otherwise it also belongs to an abstract class
	virtual void func() = 0;
};

class Son :public Base
{
public:
	void func()
	{
		cout << "func Function call" << endl;
	}
};

int main()
{
	Base* base = new Son;
	base->func();
	return 0;
}

Case study - making drinks

Case description: the general process of making drinks is: boiling water - brewing - pouring into the cup - adding accessories

Requirements: use polymorphism technology to realize this case, provide abstract beverage base class and subclass to make coffee and tea

The code is as follows:

#include <iostream>
#include <string>
using namespace std;

//Abstract making drinks
class AbstractDrinking
{
public:
	//boil water
	virtual void Boil() = 0;

	//Brew
	virtual void Brew() = 0;

	//Pour into a cup
	virtual void PourInCup() = 0;

	//Add excipients
	virtual void PutSomething() = 0;

	//Making drinks
	void makeDrink()
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

//Making coffee
class Coffee :public AbstractDrinking
{
public:
	void Boil()
	{
		cout << "Boiled farmer spring" << endl;
	}

	void Brew()
	{
		cout << "Brew coffee" << endl;
	}

	void PourInCup()
	{
		cout << "Pour into the coffee cup" << endl;
	}

	void PutSomething()
	{
		cout << "Add coffee and sugar" << endl;
	}
};

//Making tea
class Tea :public AbstractDrinking
{
public:

	void Boil()
	{
		cout << "Boiled dew" << endl;
	}

	void Brew()
	{
		cout << "Brewing tea" << endl;
	}

	void PourInCup()
	{
		cout << "Pour into a teacup" << endl;
	}

	void PutSomething()
	{
		cout << "Add tea" << endl;
	}
};

void doWork(AbstractDrinking* abs)
{
	abs->makeDrink();
	delete abs;
	
}

void test01()
{
	//Making coffee
	doWork(new Coffee);
	cout << "**********************" << endl;
	doWork(new Tea);
}


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

The operation results are shown in the figure below:

Virtual destruct and pure virtual destruct

When polymorphism is used, if an attribute in a subclass is opened to the heap, 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.

Virtual destructor and pure virtual destructor have the following commonalities:

  • You can make the parent class pointer release the child class object
  • All need specific function implementation

Difference between virtual destruct and pure virtual destruct:

  • If it is a pure virtual destructor, this class belongs to an abstract class and cannot instantiate an object

The exercise code is as follows:

#include <iostream>
#include <string>
using namespace std;

class Animal
{
public:

	Animal()
	{
		cout << "Animal Constructor call for" << endl;
	}

	Virtual deconstruction
	//virtual ~Animal()
	//{
	//	Cout < < virtual destructor call of animal < < endl;
	//}
	Using virtual destructor can solve the problem that the parent class pointer can release the dirty memory of the child class heap

	//Pure virtual destructors need to be declared and implemented
	//With pure virtual destructor, this class is also an abstract class and cannot instantiate objects
	virtual ~Animal() = 0;

	virtual void speak() = 0;//Pure virtual function
};

//Realization of pure virtual destructor
Animal:: ~Animal()
{
	cout << "Animal Pure virtual destructor call" << endl;
}

class Cat :public Animal
{
public:

	Cat(string name)
	{
		cout << "Cat Constructor call for" << endl;
		m_name = new string(name);
	}
	void speak()
	{
		cout << *m_name << "The kitten is talking" << endl;
	}

	~Cat()
	{
		if (m_name != nullptr)
		{
			cout << "Cat Destructor call for" << endl;
			delete m_name;
			m_name = nullptr;
		}
	}

	string* m_name;
};

void test01()
{
	Animal* animal = new Cat("tom");
	animal->speak();
	delete animal;
}

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


summary

  • Virtual destructors and pure virtual destructors are used to release subclass objects through parent class pointers
  • If there is no heap data in the subclass, it can not be written as virtual destructor pure virtual destructor
  • Classes with pure virtual destructors are also abstract classes and cannot instantiate objects
  • A pure virtual destructor must declare an implementation inside the class and outside the class. If it only declares no implementation, it will report an error in the link phase.

Polymorphic case - computer assembly

Case description: the main components of the computer are CPU (for calculation), graphics card (for display) and memory module (for storage). Encapsulate each part into an abstract base class, and provide different manufacturers to produce different parts, such as Intel manufacturers and Lenovo manufacturers. Create a computer, provide functions for the computer to work, and call the interface for each part to work. During the test, assemble three different computers to work.

The case code is as follows:

#include <iostream>
using namespace std;

//Abstract different part classes
class CPU
{
public:
	//Abstract computing function
	virtual void calculate() = 0;
};

//Abstract graphics card class
class VideoCard
{
public:
	//Abstract display function
	virtual void display() = 0;
};


//Abstract memory module class
class Memery
{
public:
	//Abstract storage function
	virtual void storage() = 0;
};

//Provide computer
class Computer
{
public:
	Computer(CPU* cpu, VideoCard* vc, Memery* mem)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	//Provide working functions
	void work()
	{
		//Call the interface to make the part work
		m_cpu->calculate();
		m_vc->display();
		m_mem->storage();
	}

	//Provide destructor to release 3 computer parts
	~Computer()
	{
		//Release CPU parts
		if (m_cpu != nullptr)
		{
			delete m_cpu;
			m_cpu = nullptr;
		}

		//Release the graphics card part
		if (m_vc != nullptr)
		{
			delete m_vc;
			m_vc = nullptr;
		}

		//Free memory parts
		if (m_mem != nullptr)
		{
			delete m_mem;
			m_mem = nullptr;
		}
	}

private:
	CPU* m_cpu;//cpu part pointer
	VideoCard* m_vc;//Graphics card part pointer
	Memery* m_mem;//Memory module part pointer
};

//Specific manufacturer
//Intel manufacturer
class IntelCPU :public CPU
{
public:
	void calculate()
	{
		cout << "Intel of CPU Start the calculation" << endl;
	}
};

class IntelVideoCard :public VideoCard
{
public:
	void display()
	{
		cout << "Intel Your graphics card is starting to show" << endl;
	}
};

class IntelMemery :public Memery
{
public:
	void storage()
	{
		cout << "Intel My memory module is beginning to store" << endl;
	}
};

//Lenovo manufacturer
class LenovoCPU :public CPU
{
public:
	void calculate()
	{
		cout << "Lenovo of CPU Start the calculation" << endl;
	}
};

class LenovoVideoCard :public VideoCard
{
public:
	void display()
	{
		cout << "Lenovo Your graphics card is starting to show" << endl;
	}
};

class LenovoMemery :public Memery
{
public:
	void storage()
	{
		cout << "Lenovo My memory module is beginning to store" << endl;
	}
};

//Assemble different computers
void test01()
{
	//First computer parts
	cout << "The first computer starts working" << endl;
	CPU* intelCpu = new IntelCPU;
	VideoCard* intelCard = new IntelVideoCard;
	Memery* intelMem = new IntelMemery;

	//Create first computer
	Computer* computer1 = new Computer(intelCpu, intelCard, intelMem);
	computer1->work();
	delete computer1;

	cout << "************************************" << endl;

	//Assemble the second computer
	cout << "The second computer starts working" << endl;
	Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemery);
	computer2->work();
	delete computer2;

	cout << "************************************" << endl;

	//Assemble the second computer
	cout << "The third computer starts working" << endl;
	Computer* computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new LenovoMemery);
	computer3->work();
	delete computer3;
}

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

The operation results are as follows:


✌✌✌
Wangcai, come on!

Keywords: C++ Back-end

Added by TwistedLogix on Thu, 30 Dec 2021 19:04:13 +0200