C++——虚函数 – 作者:Johnson666

虚函数的引入

同一个类中,不能定义两个相同的函数,即名字相同、参数相同的函数。但是在类的继承层次结构中,不同层次的类中可以出现名字相同、参数相同而功能不同的函数。下面通过一个例子来讲解动态多态性是如何实现的,虚函数是如何实现的。

例子:
建立一个基类Person类,派生类Student类,它们都有display这个同名的函数。

#include <iostream>
#include <string>
using namespace std; 
class Person
{ 
public:
	Person(string,int);             
	void display();           
protected:                   
	string name;
	int age;
};
Person::Person(string n,int a) {
	name = n;age = a;
}
void Person::display(){
	cout << "name:" << name << ",age:" << age << endl;
}
class Student : public Person          
{public:
	Student(string,int,int);        
	void display();				
private:
	int num;
}; 
Student::Student(string nam,int ag,int nu):Person(nam,ag),num(nu){}
void Student::display(){
	cout << "name:" << name << ",age:" << age << ",num:" << num << endl;
}
int main()
{   Person person("张三",18);          
	Student student("李四",20,10001); 
	Person * p1 = &person;          
	Person * p2 = &student;         
	p1->display();              
	p2->display();		
	return 0;
}

结果:
image.png
Person类中的display函数是输出人的数据,Student类中的display函数是输出学生的数据,二者的功能相同,实现不同。main函数中定义了两个基类指针p1和p2,p1指向基类对象person,调用p1->display()函数输出基类对象的全部数据,p2指向派生类对象student,调用p2->display()函数输出派生类对象的成员变量,但是实际上只输出了student中的基类的成员变量,说明它没有调用派生类的display函数,而是调用了基类的display函数。
如果要调用student对象中的display函数输出全部数据,可以使用对象名调用,如student.display(),或是定义Student类的指针,指向student对象再调用display函数。但是这就与多态性无关了。在实际应用中,基类会有很多派生类,而每一个派生类又会有自己的派生类,形成一个同一基类的类族。每一个派生类都可能有同名函数display,在程序中要调用不同对象的同名函数,就要定义多种类型的指向不同派生类对象的指针,实际操作很不方便,也不符合多态性。
在这里如果只定义基类指针指向不同的派生类对象,再通过不同的指针对象调用同名的display函数,但调用的是各个派生类自己的display函数,这就真正实现了多态性,也就是实现了“同一接口,不同实现”。
使用虚函数可以达到上述所要求的效果。在基类Person中声明的display函数前面加上关键字virtual,即virtual void display();
如此,Person类的display函数被声明为虚函数,程序其他部分不变动,重新编译运行程序。

#include <iostream>
#include <string>
using namespace std; 
class Person
{ 
public:
	Person(string,int);             
	virtual void display();           
protected:                   
	string name;
	int age;
};
Person::Person(string n,int a) {
	name = n;age = a;
}
void Person::display(){
	cout << "name:" << name << ",age:" << age << endl;
}
class Student : public Person          
{public:
	Student(string,int,int);        
	void display();				
private:
	int num;
}; 
Student::Student(string nam,int ag,int nu):Person(nam,ag),num(nu){}
void Student::display(){
	cout << "name:" << name << ",age:" << age << ",num:" << num << endl;
}
int main()
{   Person person("张三",18);          
	Student student("李四",20,10001); 
	Person * p1 = &person;          
	Person * p2 = &student;         
	p1->display();              
	p2->display();		
	return 0;
}

结果:
image.png
基类指针指向派生类对象,输出了派生类对象的全部数据。由此说明了它调用的是派生类的display函数。程序运行结果达到了预期的效果,即基类指针指向基类对象时就调用基类的同名成员函数,指向派生类对象时就调用派生类同名成员函数。基类指针可以按基类的方式处理事情,也可以按派生类的方式处理事情。它有多种形态,或者说有多种表现,这种现象就称为多态。
如上例基类中的display函数被声明为虚函数后,基类指针如果指向基类对象,调用的display函数就是基类的display函数。基类指针如果指向派生类对象,调用的display函数就是派生类的display函数。基类指针如果指向派生类对象,调用的display函数就是派生类的display函数。但是要注意的是,只有被声明为虚函数才能达到上述的效果。

虚函数使用方法总结

(1)在基类中使用virtual关键字声明成员函数为虚函数。
(2)在派生类中重新定义虚函数时,函数名、函数类型、函数参数个数和类型必须与基类完全一致。
(3)当基类中的成员函数被声明为虚函数后,派生类中的同名函数就自动变为虚函数。派生类重新声明该函数时可以加virtual,也可以不加。
(4)定义基类指针或是基类引用,指向派生类的对象,通过该指针或是引用调用虚函数,此时调用的就是指针或是引用指向的对象的同名函数。

动态多态性是由虚函数来实现的,动态多态性必须满足以下三个条件:
(1)类与类之间满足继承关系,必须是同一类族。
(2)基类中要声明虚函数。
(3)定义基类指针或是引用,指向派生类的对象,通过指针或是引用来访问虚函数。

虚析构函数

C++不能声明虚构造函数,但是可以声明虚析构函数。虚析构函数的声明语法如下:
virtual ~类名();

如果一个基类的析构函数是虚函数,那么所有的子类的析构函数也是虚函数。在动态多态的实现中,将析构函数声明为虚函数,可以保证使用基类指针或引用就能调用合适的析构函数对不同对象进行清理工作。
如果用new运算符建立派生类的临时对象,然后用基类指针指向这个临时对象,当用delete运算符撤销该对象时,系统只会执行基类的析构函数,不会执行派生类的析构函数。

#include <iostream>

using namespace std; 

class Person
{
public:
	~Person();
};

Person::~Person()
{
	cout << "调用父类析构函数" << endl;
}

class Student:public Person
{
public:
	~Student();
};

Student::~Student()
{
	cout << "调用子类析构函数" << endl;
}

int main()
{
	Person *p1 = new Person();
	Person *p2 = new Student();
	delete p1;
	delete p2;
	return 0;
}

结果:image.png
基类指针p2指向动态开辟存储空间的派生类对象Student,从运行结果来看,没有执行派生类的析构函数,只执行了基类的析构函数。在这里,只需要将基类的析构函数声明为虚函数,程序修改如下:
virtual ~Person()

#include <iostream>

using namespace std; 

class Person
{
public:
	virtual ~Person();
};

Person::~Person()
{
	cout << "调用父类析构函数" << endl;
}

class Student:public Person
{
public:
	~Student();
};

Student::~Student()
{
	cout << "调用子类析构函数" << endl;
}

int main()
{
	Person *p1 = new Person();
	Person *p2 = new Student();
	delete p1;
	delete p2;
	return 0;
}

结果:
image.png
该结果说明通过使用虚析构函数,调用了派生类的析构函数,释放了派生类对象动态申请的内存空间。

来源:freebuf.com 2021-06-26 11:32:57 by: Johnson666

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论