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!