C++ polymorphism - virtual functions

Virtual functions are the basis of dynamic binding. Virtual functions must be non-static member functions. After the virtual function is derived, the polymorphism of the running process can be realized in the class family.

Objects of derived classes can be used instead of objects of base classes according to type compatibility rules. If the pointer of the base class type points to the derived class object, the object can be accessed through this pointer, but what is accessed is only the function member with the same name inherited from the base class. If you need to point to the object of the derived class through the pointer of the base class and access a member with the same name as the base class, first declare the function with the same name as a virtual function in the base class. In this way, through the pointer of the base class type, different objects belonging to different derived classes can have different behaviors, thereby realizing the polymorphism of the running process.

1. General virtual function members

(1) The declaration syntax of general virtual function members is:
virtual 函数类型 函数名(参数表);

virtualThis is actually using keywords to limit member functions in class definitions . Virtual function declarations can only appear in function prototype declarations in class definitions, not when member functions are implemented.

Polymorphism during operation needs to meet three conditions:
(1) Type compatibility rules must be met between classes
(2) Virtual functions must be declared
(3) Virtual functions must be called by member functions or accessed through pointers and references

[Note] Virtual functions are generally not declared as inline functions, because calls to virtual functions require dynamic binding, and the processing of inline functions is static, so virtual functions generally cannot be processed as inline functions. But declaring a virtual function as an inline function will not cause an error, because the compiler will automatically ignore it.

(2) Comparison between ordinary function members and virtual function members

① Ordinary function members

#include<iostream>
using namespace std;

class A//基类A定义
{
    
    
public:
	void display()const//声明基类A中的成员函数为普通函数
	{
    
    
		cout << "显示类A" << endl;
	}
};

class B :public A//公有派生类B定义
{
    
    
public:
	void display()const
	{
    
    
		cout << "显示类B" << endl;
	}
};

class C :public B//公有派生类C定义
{
    
    
public:
	void display()const
	{
    
    
		cout << "显示类C" << endl;
	}
};

void fun(A* p)//参数为指向基类A的对象的指针
{
    
    
	p->display();//"对象指针->成员名"
}

int main()
{
    
    
	A a;//定义基类对象A
	B b;//定义直接基类为A类的派生类B的对象
	C c;//定义直接基类为B类的派生类C的对象

	fun(&a);//用基类A的对象的指针调用fun函数
	fun(&b);//用直接基类为A类的派生类B对象的指针调用fun函数
	fun(&c);//用直接基类为B类的派生类C对象的指针调用fun函数

	return 0;
}

Running results:
insert image description here
Analysis:
In the above program, although the pointer of the base class A points to the objects of the derived classes B and C, when the fun function is running, only the pointers to the derived classes B and C inherited from the base class A can be accessed through this pointer. The member function display of , instead of the function display of the same name in the derived classes B and C itself.

②Virtual function members

class A//基类A定义
{
    
    
public:
	virtual void display()const//声明基类A中的成员函数为虚函数
	{
    
    
		cout << "显示类A" << endl;
	}
};

class B :public A//公有派生类B定义
{
    
    
public:
	void display()const//覆盖基类的虚函数
	{
    
    
		cout << "显示类B" << endl;
	}
};

class C :public B//公有派生类C定义
{
    
    
public:
	void display()const//覆盖基类的虚函数
	{
    
    
		cout << "显示类C" << endl;
	}
};

void fun(A* p)//参数为指向基类A的对象的指针
{
    
    
	p->display();//"对象指针->成员名"
}

int main()
{
    
    
	A a;//定义基类对象A
	B b;//定义直接基类为A类的派生类B的对象
	C c;//定义直接基类为B类的派生类C的对象

	fun(&a);//用基类A的对象的指针调用fun函数
	fun(&b);//用直接基类为A类的派生类B对象的指针调用fun函数
	fun(&c);//用直接基类为B类的派生类C对象的指针调用fun函数

	return 0;
}

Running result:
insert image description here
Analysis:
A, B, and C in the program belong to the same class family, and are derived from the public, so they meet the type compatibility rules. At the same time, the function members of the base class A are declared as virtual functions, and the object pointer is used to access the function members in the program. In this way, the binding process is completed during operation, and polymorphism during operation is realized. The members of the object being pointed to can be accessed through the pointer of the base class type, so that the objects in the same class family can be uniformly processed, the abstract program is higher, and the program is more concise and efficient.

In this program, the derived class does not explicitly declare a virtual function. At this time, the system will follow the following rules to judge whether a function member is a virtual function: (1)
Whether the function has the same Name
(2) Whether the function has the same number of parameters and the same corresponding parameter types as the virtual function of the base class
(3) Whether the function has the same return value as the virtual function of the base class or a pointer that satisfies the assignment compatibility rules, reference return value

If the function of the derived class meets the above conditions after checking from the three aspects of name, parameter and return value, it will be automatically determined as a virtual function. At this time, the virtual function of the derived class covers the virtual function of the class. Not only that, but a virtual function in a derived class hides all other overloaded forms of the function of the same name in the base class.

[Note]
①The member function covered by the derived class in the base class can still be called with the pointer to the derived class object, and the method is limited by "::". For example, if the fun function in the above example is changed to the following form, other parts remain unchanged:

void fun(A* p)//参数为指向基类A的对象的指针
{
    
    
	p->A::display();//"对象指针->成员名"
}

Running result:
insert image description here
It can be seen that after using "::" to define, no matter what the polymorphic type of the object pointed to by p is, the display function of class A is always called in the end. In the function of the derived class, sometimes it is necessary to call the overridden function of the base class first, and then perform the specific operation of the derived class. At this time, you can use "base class name::function name(...)" to call the overridden function in the base class The function.

②When a derived class overrides a base class member function, the virtual keyword can be used or not, and there is no difference between the two. As long as a member function is declared as a virtual function in the base class, the member function with the same name in the derived class does not need to be declared as a virtual function. Sometimes it is customary to use the virtual keyword in derived class functions, because this can clearly indicate that this is a virtual function.

(3) The constructor and destructor of the base class call the virtual function

① When the constructor of the base class calls a virtual function, the virtual function of the derived class will not be called.

Suppose there is a base class A and a derived class B, and there is a virtual member function fun() in the two classes. When the constructor of the derived class B is executed, the constructor of the base class A needs to be called first. If A::A() calls the virtual function fun(), A::fun() is called instead of B::fun(). This is because the object is not yet a derived class object when the base class is constructed.

Also, when the base class is destructed, the object is no longer a derived class object, so if A::~A() calls fun(), A::fun() is called instead of B::fun().

class A//基类A定义
{
    
    
public:
	A()
	{
    
    
		fun();
		cout << "调用基类A的默认构造函数" << endl;
	}
	A(int a):x(a)
	{
    
    
		fun();
		cout << "调用基类A的构造函数" << endl;
	}
	virtual void fun()const//声明基类A中的成员函数为虚函数
	{
    
    
		cout << "显示类A" << endl;
	}
	~A()
	{
    
    
		cout << endl;
		fun();
		cout << "调用基类A的析构函数" << endl;
	}
private:
	int  x;
};

class B :public A//公有派生类B定义
{
    
    
public:
	B(){
    
    }
	B(int b) :y(b)
	{
    
    

		cout << "调用派生类B的构造函数" << endl;
	}
	virtual void fun()const//覆盖基类的虚函数
	{
    
    
		cout << "显示类B" << endl;
	}
	~B()
	{
    
    
		cout << endl;
		fun();
		cout << "调用派生类B的析构函数" << endl;
	}
private:
	int y;
};

int main()
{
    
    
	A a(5);

	cout << endl;

	B b(3);

	return 0;
}

Running results:
insert image description here
Analysis:

In the main function, an object a of the base class A is defined and initialized, and the constructor of the base class A is called during initialization, and the virtual function fun() is called in the constructor of the base class A, although in the base class A and derived classes There is a virtual function fun() in B, but the fun() function called in the constructor of base class A is the fun function in base class A, not the fun() function in derived class B. A derived class object b is defined and initialized. When initializing the derived class object b, the default constructor of the base class A is first called, and then the constructor of the B class is called to initialize the b object. When the default constructor of the A class is called, A The virtual function fun is called in the default constructor of the class. The virtual function fun called here is still not the virtual function fun in class B, but the virtual function fun in class A.
This is because when the base class A is constructed, the object is not yet a derived class object.

② Only virtual functions are polymorphically bound. If a derived class needs to modify the behavior of the base class (that is, rewrite a function with the same name as the base class function), it should declare the corresponding function as a virtual function in the base class. The non-virtual functions declared in the base class usually represent functions that do not want to be changed by the derived class, that is, polymorphism cannot be achieved. Generally, do not rewrite the inherited non-virtual function, because it will cause different results and cause confusion when calling the function of the same name through the pointer of the base class and the pointer of the derived class.

[Note] When rewriting an inherited virtual function, if the function has a default parameter value, do not redefine a different value. Because, although virtual functions are polymorphically bound, default parameters are statically bound. That is to say, through a base class pointer pointing to a derived class object, the virtual function of the derived class can be accessed, but the default parameter value can only come from the base class definition. For example:

class A//基类A定义
{
    
    
public:
	virtual void display()const//声明基类A中的成员函数为虚函数
	{
    
    
		cout << "显示类A" << endl;
	}
};

class B :public A//公有派生类B定义
{
    
    
public:
	virtual void display()const//覆盖基类的虚函数
	{
    
    
		cout << "显示类B" << endl;
	}
};

class C :public B//公有派生类C定义
{
    
    
public:
	virtual void display()const//覆盖基类的虚函数
	{
    
    
		cout << "显示类C" << endl;
	}
};

void fun(A* p)//参数为指向基类A的对象的指针
{
    
    
	p->A::display();//"对象指针->成员名"
}

int main()
{
    
    
	C c;//定义派生类对象
	A* p = &c;//基类指针p可以指向派生类对象
	A& r = c;//基类引用r可以作为派生类对象的别名
	A a = c;//调用基类A的拷贝构造函数用c构造a,a的类型是A而非C

	return 0;
}

Here, A a = c;object c of type C is used to initialize object a of type A, and the copy constructor of type A is used for initialization. Since the copy constructor receives a constant reference of type A, c of type C conforms to the type compatibility rules and can be passed to it as a parameter. Because the copy constructor of class A is executed, only the members of type A will be copied, and the newly added data members of class C will not be copied, and there is no space to store them, so the generated object is an object of base class A. This behavior of constructing base class objects by copying derived class objects is called object slicing. At this time, if a is used to call the virtual function of the base class A, the target object of the call is the A-type object obtained after object slicing, which has nothing to do with the C-type c object. The type of the object is very clear, so there is no need for polymorphic binding Certainly.

Guess you like

Origin blog.csdn.net/NuYoaH502329/article/details/132164151