第八章 多态性和虚函数
**大家想一起学习交流的可以加群,QQ:755422568。**
一、多态性
静态联编所支持的多态性称为编译时的多态性。
动态联编所支持的多态性称为运行时的多态性。
(1)、静态联编中的赋值兼容性及名字支配规律
类的对象和调用的函数一一对应,编译时即可确定调用关系,从而产生编译时的多态性。
#include <iostream>
using namespace std;
const double PI = 3.14159;
class Point{
private:
double x,y;
public:
Point(double i,double j){
x = i;
y = j;
}
double area(){
return 0;
}
};
class Circle : public Point{
private:
double radius;
public:
Circle(double a,double b,double r) :Point(a,b){
radius = r;
}
double area(){
return PI*radius*radius;
}
};
void main(){
Point a(1.5,6.7);
Circle c(1.5,6.7,2.5);
cout << "area of Point is " << a.area() << endl;
cout << "area of Point is " << c.area() << endl;
Point *p = &c; //使用Point的指针只能调用对象c的基类的area函数。
cout << "area of Circle is " << p-> area() << endl;
Point &rc = c;
cout << "area of Circle is " << rc.area() << endl;
/**
*输出结果:
*area of Point is 0
*area of Point is 19.6349
*area of Circle is 0
*area of Circle is 0
*/
}
(2)、动态联编的多态性
1)编译“Point *p = &c;”时,根据兼容性规则检查它的合理性,也就是检查是否符合“派生类对象的地址可以赋给基类的指针”的指针。使用关键字virtual声明Point类的area函数,称为虚函数。
虚函数(
看点
)
当系统编译含有虚函数的类时,建立一个虚函数表,表中每一个元素都指向一个虚函数地址。此外也增加了一个数据成员,是一个指向该虚函数表的指针,称为vptr。
虚函数的地址翻译取决于对象的内存地址。
2)、派生类能继承基类的虚函数表,而且只要是和基类同名的成员函数,无论是否使用virtual声明,它们都自动成为虚函数。如果派生类没有改写继承基类的虚函数,则函数指针调用基类的虚函数。如果派生类改写了基类的虚函数,编译器将重新为派生类的虚函数建立地址,函数指针会调用改写过的虚函数。(看点
)
3)、虚函数的调用规则:
根据当前对象,优先调用对象本身的虚成员函数。
在执行期“间接”调用实际上欲联编的函数。当运行到语句,p -> area(); 时,才能确定p指向的是派生类Circle的对象,调用Circle ::area()函数。
二、虚函数
(1)、虚函数定义
一旦基类定义了虚函数,该基类的派生类中的同名函数也自动成为虚函数。(
看点
)
虚函数只能是类中的一个成员函数,但不能是静态成员,关键字virtual声明。(选择题
)
当派生类中定义了一个同名的成员函数时,只要该成员函数的参数个数和相应类型以及它的返回类型与基类中同名的虚函数完成一样,则无论是否为该成员函数使用virtual,都将成为一个虚函数。
(2)、虚函数实现多态性的条件
产生运行时的多态性有如下3个前提:
1、类之间的继承关系满足赋值兼容性规则。
2、改写了同名虚函数。
3、根据赋值兼容性规则使用指针或引用。
满足三条才能保证实现动态联编。
第三条分为两种情况:
1、按赋值兼容性规则使用基类指针或引用访问虚函数。
2、把指针或引用作为函数参数,即函数不一定是类的成员函数,可以是普通函数,而且可以重载
#include <iostream>
using namespace std;
const double PI = 3.14159;
class Point{
private:
double x,y;
public:
Point(double i,double j){
x = i;
y = j;
}
virtual double area(){
return 0;
}
};
class Circle : public Point{
private:
double radius;
public:
Circle(double a,double b,double r) :Point(a,b){
radius = r;
}
double area(){
return PI*radius*radius;
}
};
void display(Point *p){
cout << p -> area() << endl;
}
void display(Point &p){
cout << a.area() << endl;
}
void main(){
Point a(1.5,6.7);
Circle c(1.5,6.7,2.5);
Point *p = &c; //使用Point的指针只能调用对象c的基类的area函数。
Point &rc = c;
display(a);
display(p);
display(rc);
/**
*输出结果:
*0
*19.6349
*19.6349
*/
}
(3)、构造函数和析构函数调用虚函数
构造函数和析构函数中调用虚函数采用静态联编,即它们所调用的虚函数是自己的类或基类中定义的函数,但不是任何在派生类中重定义的虚函数。
#include <iostream>
using namespcae std;
class A{
public:
A(){}
virtual void func(){
cout << "Constructing A" << endl;
}
~A(){}
virtual void fund(){
cout << "Destructor A" << endl;
}
};
class B ::public A{
public :
B(){
func();
}
void fun(){
cout << "Cone here and go... " << endl;
func();
}
~B(){
fund();
}
};
class C ::public B{
public:
C(){}
void func(){
cout << "Class C" << endl;
}
~C(){
fund();
}
void fundI(){
cout << "Destructor C" << endl;
}
};
void main(){
C c;
c.fun();
/**
*输出结果:
*Constructing A //建立对象c调用B()产生
*Cone here and go...Class C //c.fun()输出
*Destructor C //析构对象c时,由~C()产生
*Destructor A ////析构对象c时,由~B()产生
*/
}