(38.1)虚函数之多态的条件及多态性

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

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

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

class 类名 
{ //类体
	…
	virtual 返回类型 函数名(形式参数列表); //虚函数
	…
};

eg:
class Point 
{ //Point类表示平面上的点
	…
	virtual double area(); //虚函数声明
	virtual double volumn() { return 0; } //虚函数定义
};

  • 需要注意, virtual只在类体中使用。(如果类定义在类外,则其在类中的声明前加virtual)

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

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

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

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

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

  • eg:

#include <iostream>
using namespace std;
class Point//Point类表示平面上的点
{
	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类表示圆
{
	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;
}

运行结果:
Circle area=3.14
Circle area=0


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

  • 满足前两条并不一定产生动态联编, 必须同时满足第3条才能保证实现动态联编。
    ①类之间的继承关系满足赋值兼容性规则;(必须是公有继承关系)
    ②改写了同名的虚函数;(派生类改写了虚函数)
    ③根据赋值兼容性规则使用指针(或引用) 。

  • 第3条分为两种情况:
    (1)使用基类指针(或引用) 访问虚函数。 例如:
    (2)把指针(或引用) 作为函数参数, 这个函数不一定是类的成员函数, 可以是普通函数, 而且可以重载。 例如:

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

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

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

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

  • eg:

#include <iostream>
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)())//指向基类成员函数的指针,参数为空,返回类型为void
{
	(p->*pf)();//p指向,pf所指向的成员函数
}

int main()
{
	Derived d;
	Base b;
	display(&d, &Base::print);//输出Derived,形参p指向派生类对象d,产生多态,指向派生类的print
	display(&b, &Base::print);//输出Base,形参p指向基类对象d
	return 0;
}
  • 何时需要虚函数?
    (1) 首先看成员函数所在的类是否会作为基类。
    然后看成员函数在类的继承后有无可能被更改功能, 如果希望派生类更改其功能的,一般应该将它声明为虚函数。
    (2) 如果成员函数在类被继承后功能不需修改, 或派生类用不到该函数, 则不要把它声明为虚函数。
    不要仅仅考虑到要作为基类而把类中的所有成员函数都声明为虚函数。
    (3)应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访间, 如果是通过基类指针或引用去访问的, 则应当声明为虚函数。
    (4)使用虚函数, 系统要增加一定的空间开销用来存储虚函数表, 但系统在进行动态联编时的时间开销是很少的, 因此, 多态性是高效的。
发布了556 篇原创文章 · 获赞 140 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/u011436427/article/details/104175203