虚函数

虚函数

虚函数实现多态的条件

被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用就是实现多态性——以共同的方法,对不同的对象采取不同的策略

1.虚函数的定义

虚函数只能是类中成员函数,且不能是静态的。在成员函数定义或声明前面加上关键字virtual,即定义了虚函数:

class 类名 { //类体
	...
	virtual 返回类型 函数名(形式参数列表); //虚函数
	...
};
class Point { //Point类表示平面上的点
	...
	virtual double area(); //虚函数声明
	virtual double volumn(); { return 0; } //虚函数定义
};

需要注意,virtual只在类体中使用

当在一个派生类中定义了一个同名的成员函数时,只要该成员函数的参数个数,参数类型以及返回类型与基类中同名的虚函数完全一样,则派生类的这个成员函数无论是否使用virtual,它都将成为一个虚函数

程序员习惯给派生类的同名函数也加上virtual,便于阅读理解

利用虚函数,可在基类和派生类中使用相同的函数名定义函数的不同实现,从而实现“一个接口,多种方式”。 当基类指针或引用对虚函数进行访问时,系统将根据运行时指针或引用所指向或引用的实际对象来确定调用对象所在类的虚函数版本

关键字virtual指示C++编译器在调用虚函数时进行动态联编。这种多态性是程序运行到相应的语句时才动态确定的,所以称为运行时的多态性

不过,使用虚函数并不一定产生多态性,也不一定使用动态联编。例如,在调用中对虚函数使用成员名限定,可以强制C++对该函数的调用使用静态联编

例:

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

class Point { //Point类表示平面上的点
	private:
		double x,y; //坐标值
	public:
		Point(double x1=0,double y1=0) : x(x1),y(y1) { }
		virtual double area() { //虚函数 
			return 0;
		} 
};

class Circle:public Point { //Circle类表示圆
	private:
		double r; //半径
	public:
		Circle(double x,double y,double r1):Point(x,y),r(r1) { }
		virtual double area() { //虚函数 
			return 3.14*r*r;
		} 
};

int main()
{
	Point a(2.5,2.5);
	Circle c(2.5,2.5,1);
	Point *pc=&c;
	cout<<"Circle area="<<pc->area()<<endl; //动态联编
	cout<<"Circle area="<<pc->Point::area()<<endl; //静态联编
	return 0; 
}

2.虚函数实现多态的条件

(1)类之间的继承关系满足赋值兼容性规则
(2)改写了同名的虚函数
(3)根据赋值兼容性规则使用指针(或引用)

注: 满足前两条并不一定产生动态联编,必须同时满足第3条才能保证实现动态联编

第3条分为两种情况:
(1)使用基类指针(或引用)访问虚函数。例如:

Point *p=new Circle; //基类指针指向派生类
cout<<p->area(); //动态联编

(2)把指针(或引用)作为函数参数,这个函数不一定是类的成员函数,可以是普通函数,而且可以重载。例如:

void fun(Point *p) {
	cout<<p->area(); //动态联编
}

类成员函数的指针与多态性

在派生类中,当一个指向基类成员函数的指针指向一个虚函数,并且通过指向对象的基类指针(或引用)访问这个虚函数时,仍将发生多态性

例:

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

class Base {
	public:
		virtual void print() {
			cout<<"Base"<<endl; //虚函数 
		}
};

class Derived: public Base {
	public:
		void print() {
			cout<<"Derived"<<endl; //虚函数 
		}
}; 

void display(Base *p,void(Base::*pf)()) {
	(p->*pf)();
}

int main()
{
	Derived d;
	Base b;
	display(&d,&Base::print); //输出Derived 
	display(&b,&Base::print); //输出Base 
	return 0;
}

何时需要虚函数?

(1)首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望派生类更改其功能的,一般应该将它声明为虚函数

(2)如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。不要仅仅考虑到要作为基类而 把类中的所有成员函数都声明为虚函数

(3)应考虑对成员函数的调用是通过对象名还是通过基类指针或 引用去访间,如果是通过基类指针或引用去访问的,则应当声明为虚函数

使用虚函数,系统要增加一定的空间开销用来存储虚函数表,但系统在进行动态联编时的时间开销是很少的,因此,多态性是高效的

虚析构函数

派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数

但是,如果用new运算符建立了派生类对象,且定义了一个基类的指针指向这个对象,那么当用delete运算符撤销对象时,系统会只执行基类的析构函数,而不执行派生类的析构函数,因而也无法对派生类对象进行真正的撤销清理操作

例如:

Point *pp=new Circle; //基类指针指向派生类
delete pp; //仅执行基类析构函数

如果希望“delete pp”执行Circle的析构函数,那么基类Point的析构函数要声明为虚函数,称为虚析构函数

如果将基类的析构函数声明为虚函数,由该基类所派生的所有派生类的析构函数也都自动称为虚函数,即使派生类的析构函数与基类的析构函数名字不相同

例:

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

class Base {
	public:
		Base() { }
		virtual ~Base() { }
		virtual void fun() {
			cout<<"in class Base!"<<endl;
		}
};

class Derived : public Base {
	public:
		Derived() { }
		~Derived() {
			cout<<"the destructor of class Derived!"<<endl;
		}
		void fun() {
			cout<<"in class Derived!"<<endl;
		}
};

int main()
{
	Base *pTest = new Derived;
	pTest->fun(); //调用派生类fun
	delete pTest; //调用派生类析构函数
	return 0; 
}

当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统总会采用动态联编,调用正确的析构函数,对该对象进行清理

C++支持虚析构函数,但不支持虚构造函数,即构造函数不能声明为虚函数

纯虚函数和抽象类

1.纯虚函数

在许多情况下,不能在基类中为虚函数给出一个有意义的定义,这时可以将它说明为纯虚函数(pure virtual function),将具体定义留给派生类去做。纯虚函数的定义形式为:

virtual 返回类型 函数名(形式参数列表)=0;

即在虚函数的原型声明后加上“=0”,表示纯虚函数根本就没有函数体

纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数

包含有纯虚函数的类称为抽象类(abstract class)。一个抽象类只能作为基类来派生新类,所有又称为抽象基类(abstract base class)。抽象类不能定义对象

如果在派生类中给出了抽象类的纯虚函数的实现,则该派生类不在是抽象类。否则只要派生类仍然有纯虚函数,则派生类依然是抽象类。抽象类至少含有一个虚函数,而且至少有一个虚函数是纯虚函数

例:

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

class Sharp { //Sharp类,抽象类
	public:
		virtual double area() =0; //纯虚函数
		virtual double volumn() =0; //纯虚函数 
};

class Circle : public Sharp { //Circle类表示圆
	public:
		Circle(double a) : r(a) { }
		virtual double area() { //虚函数 
			return 2*3.1415926*r;
		} 
		virtual double volumn() { //虚函数 
			return 0;
		}
	private:
		double r;
};

class Cylinder : public Circle { //Cylinder类表示圆柱体
	public:
		Cylinder(double a,double b) : Circle(a),h(b) { }
		virtual double volumn() { //虚函数 
			return area()*h;
		} 
	private:
		double h;
};

int main()
{
	Circle a(10.0); //定义Circle对象
	Cylinder b(5.6,10.5); //定义Cylinder对象
	cout<<a.area()<<","<<b.volumn()<<endl; //静态联编
	Sharp *pb; //定义基类指针
	pb=&b; //指向Circle对象
	cout<<pb->area()<<","<<pb->volumn()<<endl; //动态联编
	return 0; 
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值