Summary of knowledge points in Chapter 6 (Polymorphism and Virtual Functions) of C++ Object-Oriented Programming (2nd Edition)

C++ object-oriented programming

参考书目:《C++面向对象程序设计》—— 谭浩强 《C++程序设计:思想与方法》—— 翁惠玉



1. The concept of polymorphism

       In C++ programming, polymorphism means that functions with different functions can use the same function name. Polymorphism is described in the object-oriented method like this: sending the same message to different objects will produce different behaviors after they receive it. (i.e. method). Function overloading and operator overloading are polymorphic phenomena. Functions with different functions can use the same function name, that is, using one function name to call functions with different contents.
       From a system implementation perspective, polymorphism is divided into two categories: static polymorphism and dynamic polymorphism. The function overloading and operator overloading that we have learned before belong to static polymorphism. When the program is compiled, the system can determine which function to call. Therefore, static polymorphism is also called compile-time polymorphism. Static polymorphism is achieved through function overloading. Dynamic polymorphism determines the object targeted by the operation while the program is running. It is also called runtime polymorphism. Dynamic polymorphism is achieved through virtual functions.

Typical examples

Example: Create a point class (point) with data members x, y (coordinates). Use it as the base class to derive a circle class, add the data member r (radius), then use the circle as the direct base class to derive a cylinder class, and then add the data member h (height), which requires overloading the operators << and >> Enable it to output the above class objects.

(1)声明基类point类
class Point
{
    
    
    protected:
	   float x, y;
    public:
	   Point(float = 0, float = 0);
	   void setPoint(float, float);
	   float getX() const {
    
     return x; }
	   float getY() const {
    
     return y; }
	   friend ostream & operator<<(ostream &, const Point &);
};
Point::Point(float a, float b) {
    
     x = a; y = b; }
void Point::setPoint(float a, float b) {
    
     x = a; y = b; }
ostream & operator<<(ostream &output, const Point &p)
{
    
    
	output << "[" << p.x << "," << p.y << "]" << endl;
	return output;
}
void main()
{
    
    
	Point p(3.5, 6.4);
	cout << "x=" << p.getX() <<",y=" << p.getY() << endl;
	p.setPoint(8.5, 6.8);
	cout << "p(new):" << p << endl;
}

Insert image description here

声明派生类circle类
class Circle :public Point
{
    
    
    protected:
	   float radius;
    public:
	   Circle(float x = 0, float y = 0, float r = 0);
	   void setRadius(float r) {
    
     radius = r; }
	   float getRadius() const {
    
     return radius; }
	   float area() const;
	   friend ostream &operator<<(ostream &, const Circle &);
};
Circle::Circle(float a, float b, float r) :Point(a, b), radius(r) {
    
    }
float Circle::area() const {
    
     return 3.14159*radius*radius; }
ostream &operator<<(ostream &output, const Circle &c)
{
    
    
	output << "Center=[" << c.x << "," << c.y << "], Radius=" << c.radius << ", area=" << c.area() << endl;
	return output;
}
void main()
{
    
    
	Circle c(3.5, 6.4, 5.2);
	cout << "original circle:\nx="<< c.getX() << ", y="<< c.getY() << ", r=" << c.getRadius() <<", area="<<c.area()<<endl;
		c.setRadius(7.5);
	c.setPoint(5, 5);
	cout << "new circle:\n" << c;
	Point &pRef = c;
	cout << "pRef:" << pRef;
}

Insert image description here

(3)声明circle 的派生类cylinder类
class Cylinder :public Circle
{
    
    
    public:
	   Cylinder(float x = 0, float y = 0, float r = 0, float h = 0);
	   void setHeight(float h) {
    
     height = h; }
	   float getHeight() const {
    
     return height; }
	   float area() const;
	   float volume() const;
	   friend ostream& operator<<(ostream&, const Cylinder&);
    protected:	  
	   float height;
};
Cylinder::Cylinder(float a, float b, float r, float h) :Circle(a, b, r) {
    
     height = h; }
float Cylinder::area() const
{
    
    
	return 2 * Circle::area() + 2 * 3.14159*radius*height;
}
float Cylinder::volume() const {
    
     return Circle::area()*height; }
ostream &operator<<(ostream &output, const Cylinder& cy)
{
    
    
	output << "Center=[" << cy.x << "," << cy.y << "], r=" << cy.radius<< ", h=" << cy.height << " \narea=" << cy.area() <<", volume=" << cy.volume() << endl;
	return output;
}
int main()
{
    
    
	Cylinder cy1(3.5, 6.4, 5.2, 10);
	cout << "\n original cylinder:\n x=" << cy1.getX() << ", y="
		<< cy1.getY() << ", r=" << cy1.getRadius() << ", h="
		<< cy1.getHeight() << "\narea=" << cy1.area()
		<< ", volume="<<cy1.volume()<<endl;
	cy1.setHeight(15);
	cy1.setRadius(7.5);
	cy1.setPoint(5, 5);
	cout << "\nnew cylinder:\n" << cy1;
	Point &pRef = cy1;
	cout << "\npRef as a point:" << pRef;
	Circle &cRef = cy1;
	cout << "\ncRef as a Circle:" << cRef;
	return 0;
}

Insert image description here

注:
可以在一个工程里设计多个头文件 “point.h”、“cylinder.h”、“circle.h”分别定义各个类。
再设计 “point.cpp” 、“circle.cpp”, “cylinder.h”多个源程序文件分别定义各个类的成员函数。
设计一个主函数,通过对象访问类的成员函数。

2. Virtual function

1. The role of virtual functions

       Two functions with the same name and the same number and type of parameters cannot be defined in a class. In a class family, classes at different levels can have functions with the same name, the same number and type of parameters, but different functions. The compilation system decides which function to call based on the principle of coverage with the same name.
       The working principle of virtual function is to define a function with the same name as the base class function in the derived class, and access the function with the same name in the base class or derived class through the base class pointer or reference. The assignment compatibility rules described in the previous section are the basis of polymorphism. The same function can handle base class objects and derived class objects with public derivation relationships.

       How to use virtual functions:

  • Use virtual to declare member functions as virtual functions in the base class. Redefine the function of the same name in the derived class to give it new functionality.
  • When redefining this function in a derived class, the function name, function type, number and type of parameters are required to be the same as the virtual function of the base class, and the function body is redefined as needed. C++ stipulates that when a member function is declared as a virtual function, the function of the same name in its derived class automatically becomes a virtual function.
  • Define a pointer variable pointing to a base class object and let it get the address of an object in the same class family.
  • Using this pointer variable to call a virtual function will call the virtual function of the class to which the object belongs.
  • Constructors cannot be declared virtual. This is because the class object has not completed the creation process when the constructor is executed, and of course there is no binding between the function and the class object.
  • Using virtual functions requires a certain amount of space overhead in the system. When a class has virtual functions, the compilation system will construct a virtual function table (vtable for short) for the class, which is an array of pointers that stores the entry address of each virtual function. The system's time overhead when performing dynamic associations is very small, so polymorphism is efficient.
class Student
{
    
    
    public:   	
	   Student(int, string, float);
	   virtual void display();
    protected:  	
	   int num;   	
	   string name;   	
	   float score;
};
Student::Student(int n, string nam, float s)
{
    
    
	num = n; name = nam; score = s;
}
void Student::display()
{
    
    
	cout << "num:" << num << "\nname:" << name << "\nscore:"<< score << "\n\n";
}
class Graduate :public Student
{
    
    
    public:   	
	   Graduate(int, string, float, float);
	   void display();
    private:	   
	   float pay;
};
Graduate::Graduate(int n, string nam, float s, float p):Student(n, nam, s), pay(p) {
    
    }
void Graduate::display()
{
    
    
	cout << "num:" << num << "\nname:" << name << "\nscore:"<< score << "\npay=" << pay << endl;
}
int main()
{
    
    
	Student stud1(1001, "Li", 87.5);
	Graduate grad1(2001, "Wang", 98.5, 563.5);
	Student *pt = &stud1;
	pt->display();
	pt = &grad1;
	pt->display();
	return 0;
}

Insert image description here

函数重载处理的是同一层次中的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题。前者要求函数名相同,但函数的参数或类型不同;后者要求的是不仅函数名相同,而且函数参数和类型也要相同。

2. Static association and dynamic association

       Two understandings of polymorphism: (1) Functions with different functions can use the same function name, and one function name can be used to call functions with different contents. (2) Sending the same message (a call to a member function) results in completely different behaviors (different function implementations) when received by objects of different types.
       Static polymorphism is implemented through function overloading and operator overloading ;
       dynamic polymorphism is mainly implemented through virtual functions .
       When any function in the class is preceded by virtual, the compiler will secretly add a data member to the class, which is called VPTR here. It is a pointer to the function pointer table. Therefore, the With virtual class object occupies more memory space than Objects of No virtual class are large.

class base
{
    
     
    public:   
       virtual void Fn(int x){
    
    cout<< “在基类内x=<<x<<endl;  }
};
class subclass:public base
{
    
     
    public:    
       virtual void Fn( float x){
    
    cout<< “在派生类内x=<<x<<endl;  }
};
void test (base & b)
{
    
       
    int i=1;          
    b.Fn( i );
    float f=2.0;    
    b.Fn( f );  
}
void main()
{
    
       
    base  bc;    
    subclass  sc;
    cout<<“调用test(bc) \n”;    
    test(bc);
    cout<<“调用test(sc) \n”;    
    test(sc);
}

Insert image description here

注:只能将类的成员函数声明为虚函数。一个成员函数被声明为虚函数后,在同一类族不能再定义一个与该虚函数相同的非虚函数。

3. Under what circumstances should virtual functions be declared?

       When declaring a virtual function, the following points should be considered:

  • First check whether the class of the member function will be used as a base class. Then check in the derived class to see if its function will be changed. If it is to be changed, it should generally be declared as a virtual function. Otherwise don't declare it as a virtual function.
  • It should be considered whether the call to the member function is accessed through the object name or through a base class pointer or reference. If it is accessed through a base class pointer or reference, it should be declared as a virtual function.
  • Sometimes when defining a virtual function, the function body is empty, and only a virtual function name is defined, and the specific functions are left to the derived class to add.
class base
{
    
    
    protected:
	   int x;
    public:
		base(int a) {
    
     x = a; }
	   virtual void print(){
    
     cout << "base = "<< x << endl; }
};
class subclass :public base
{
    
    
    public:
	   subclass(int a):base(a){
    
    }
	   virtual void print(){
    
     cout << "First derivation\n"<< x << endl; }
};
class sub2class :public base
{
    
    
    public:
	sub2class(int a) :base(a) {
    
    }
	virtual void print() {
    
     cout << "Second derivation\n" << x << endl; }
};
int main()
{
    
    
	base  *p, obj1(1);
	subclass obj2(2);
	sub2class obj3(3);
	p = &obj1;
	p->print();
	p = &obj2;
	p->print();
	p = &obj3;
	p->print();
	obj2.print();
	obj3.print();
	return 0;
}

Insert image description here

4.Virtual destructor

       When an object of a derived class is canceled, the destructor of the derived class is generally called first, and then the destructor of the base class is called. When using the new operator to create a dynamic object, if there is a destructor in the base class, and a pointer variable pointing to the base class is defined. When you use the delete operator with a pointer parameter to delete an object in a program, the system will only execute the destructor of the base class, not the destructor of the derived class.

将基类的析构函数声明为虚函数:
先调用派生类的析构函数,再调用基类的析构函数。当基类的析构函数为虚函数时无论指针指的是同一类族中的哪一个类对象,撤销对象时,系统会采用动态关联,调用相应的析构函数。
如果基类的析构函数声明为虚函数,那么其所有派生类的析构函数也自动成为虚函数。
构造函数不能声明为虚函数。
class Point
{
    
    
    public:
	   Point() {
    
     cout << "Point Cons!" << endl; }
	   virtual ~Point() {
    
     cout << "Executing Point destructor" << endl; }
};
class Circle :public Point
{
    
    
    public:
		Circle() {
    
     cout << "Circle Cons!" << endl; }
		~Circle() {
    
     cout << "Executing Circle destructor" << endl; }
    private:
		int radius;
};
int main()
{
    
    
	Point *p = new Circle;
	delete p;
	Circle *pc = new Circle;
	delete pc;
	return 0;
}

Insert image description here

       When is a virtual destructor needed:

  • When you might delete a derived class object via a base class pointer
  • If you plan to allow others to call the object's destructor through a base class pointer (which is normal through delete), and the object being destroyed is an object of a derived class that has an important destructor, you need to let the base class 's destructor becomes virtual.
  • In inheritance, it is generally customary to declare a virtual destructor. Even if the base class does not need it, a virtual destructor with an empty function body is explicitly defined to ensure that the dynamically allocated space is correctly processed when undoing it.

3. Pure virtual functions and abstract classes

1.Pure virtual function

       A virtual function that is only declared but not defined is called a pure virtual function.
       The general format of pure virtual functions: virtual function type function name (parameter list) = 0;

       The role of pure virtual functions:

  • In many cases, a meaningful definition of a virtual function cannot be given in the base class, but it is described as a pure virtual function. Its function is to provide a consistent interface (interface) for the derived class. Its definition is left to derived classes, which define their own implementations as needed.

  • If a pure virtual function is declared in a class and is not defined (implemented) in its derived class, the virtual function remains pure virtual in the derived class.

    注:1.一个类可以说明一个或多个纯虚函数。2.纯虚函数没有函数体,不能被调用,所在的抽象类,也不能直接进行实例化。3.空的虚函数,函数体为空。但所在的类可以实例化。
    

2.Abstract class

       A class with pure virtual functions is an abstract class. An abstract class describes the common semantics of a group of subclasses' public operation interfaces, and the semantics of these interfaces are also passed to subclasses. Generally speaking, an abstract class only describes the common operation interface of this group of subclasses, and the complete implementation is left to the subclasses.

       Description of abstract classes:

  • An abstract class is a special class that is established for the purpose of abstraction and design. It is at the upper level of the inheritance hierarchy, that is, it can only be used as a base class for other classes. Abstract classes cannot define objects.
  • A class derived from an abstract class must provide the implementation code of a pure virtual function or still declare it as a pure virtual function in the derived class, otherwise a compilation error occurs.
  • Abstract classes cannot be used as parameter types, function return types, or types for explicit conversions, but they can indicate pointers and references to the abstract class. This pointer can point to its derived class to achieve polymorphism.

3. Application examples

class Shape
{
    
    
    public:
	   virtual float area() const {
    
     return 0.0; }
	   virtual float volume() const {
    
     return 0.0; }
	   virtual void shapeName() const = 0;
};
class Point :public Shape
{
    
    
    protected:	  
	   float x, y;
    public:
	   Point(float a = 0, float b = 0) {
    
     x = a; y = b; }
	   void setPoint(float a , float b) {
    
     x = a; y = b; }
	   float getX() const {
    
     return x; }
	   float getY() const {
    
     return y; }
	   virtual void shapeName() const {
    
     cout << "Point:"; }
	   friend ostream & operator<<(ostream &, const Point &);
};
ostream & operator<<(ostream &output, const Point &p)
{
    
     output << "[" << p.x << "," << p.y << "]"; return output;  }
class Circle :public Point
{
    
    
    protected:  
	   float radius;
	public:
		Circle(float x = 0, float y = 0, float r = 0);
		void setRadius(float r) {
    
     radius = r; }
		float getRadius() const {
    
     return radius; }
		virtual float area() const;
		virtual void shapeName() const {
    
     cout << "Circle:"; }
		friend ostream &operator<<(ostream &, const Circle &);
};
Circle::Circle(float a, float b, float r) :Point(a, b), radius(r) {
    
    }
float Circle::area() const {
    
     return 3.14159*radius*radius; }
ostream &operator<<(ostream &output, const Circle &c)
{
    
    
	output << "[" << c.x << "," << c.y << "], r=" << c.radius;
	return output;
}
class Cylinder :public Circle
{
    
    
	 public:
		Cylinder(float x = 0, float y = 0, float r = 0, float h = 0);
		void setHeight(float h) {
    
     height = h; }
		float getHeight() const {
    
     return height; }
		virtual float area() const;
		virtual float volume() const;
		virtual void shapeName() const {
    
     cout << "Cylinder:"; }
		friend ostream& operator<<(ostream&, const Cylinder&);
	 protected:  float height;
};
Cylinder::Cylinder(float a, float b, float r, float h):Circle(a, b, r), height(h) {
    
    }
float Cylinder::area() const
{
    
    
	return 2 * Circle::area() + 2 * 3.14159*radius*height;
}
float Cylinder::volume() const {
    
     return Circle::area()*height; }
ostream &operator<<(ostream &output, const Cylinder& cy)
{
    
    
	output << "[" << cy.x << "," << cy.y << "], r=" << cy.radius<< ", h=" << cy.height;
	return output;
}
int main()
{
    
    
		Point point(3.2, 4.5);
		Circle circle(2.4, 12, 5.6);
		Cylinder cylinder(3.5, 6.4, 5.2, 10.5);
		point.shapeName();   //静态关联
		cout << point << endl;
		circle.shapeName();
		cout << circle << endl;
		cylinder.shapeName();
		cout << cylinder << endl << endl;
		Shape *pt;
		pt = &point;    //动态关联
		pt->shapeName();
		cout << "x=" << point.getX() << ",y="<< point.getY() << "\narea="<< pt->area() << "\nvolume="<< pt->volume() << "\n\n";
		pt = &circle;
		pt->shapeName();
		cout << "x=" << circle.getX()<< ",y=" << circle.getY() <<"\narea=" << pt->area() <<"\nvolume=" << pt->volume()<< "\n\n";
		pt = &cylinder;
		pt->shapeName();
		cout << "x=" << cylinder.getX()<< ",y=" << cylinder.getY()<< "\narea=" << pt->area()<< "\nvolume=" << pt->volume() << "\n\n";
		return 0;
}

Insert image description here

Guess you like

Origin blog.csdn.net/weixin_43312470/article/details/108045969