1、C++中通过virtual关键字对多态进行支持,使用virtual声明的函数被重写后即可展现多态特性。
注意和虚继承的区别。虽然用的是同一个关键字,但意义完全不同。
2、多态的意义:多态可以使用未来,80年代写了一个框架,可以调用90年代写的代码。
多态是设计模式的基础,多态是框架的基础。
3、多态成立的条件
3.1 要有继承
3.2 要有虚函数重写
3.3 要有父类指针(父类引用)指向子类对象
4、演示代码:
class Parent
{
public:
Parent(int a)
{
this->a = a;
}
virtual void print() //给一个成员函数 定义为 虚函数。
{
cout << "Parent ::print() : a = " << a << endl;
}
private:
int a;
};
class Child :public Parent
{
public:
Child(int a, int b) :Parent(a)
{
this->b = b;
}
//重定义父类函数: 发生在子类和父类之间
//当子类重写父类的成员函数,如果父类中这个函数不是虚函数, 是函数的重定义
//当子类重写父类的成员函数,如果父类中这个函数是 虚函数。 是函数的重写。
virtual void print()
{
cout << "Child: print b:" << b << endl;
//Parent::print();
}
private:
int b;
};
void myPrintFunc(Parent *p) // 让父类指针指向子类对象的时候,
{
p->print();// 再此时 print函数 发生了多态现象
}
int main(void)
{
Child c(10, 20);
c.print(); //调用的子类的print
Parent p(100);
p.print(); //调用的父类的print
cout << " ----- " << endl;
myPrintFunc(&p); //希望调用父类的print
cout << " ------ " << endl;
myPrintFunc(&c); //希望调用子类的print
return 0;
}
5、析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象 。
class A
{
public:
A() {
cout << "A() ..." << endl;
this->p = new char[64]; //给p开辟了一个空间
memset(p, 0, 64);
strcpy(p, "A string ");
}
virtual void print()
{
cout << p << endl;
}
virtual ~A()
{
cout << "~A()...." << endl;
if (p != NULL) {
delete[] p;
p = NULL;
}
}
private:
char *p;
};
class B : public A
{
public:
B() {
cout << "B()..." << endl;
this->p = new char[64];
memset(p, 0, 64);
strcpy(p, "B string");
}
//重写
virtual void print()
{
cout << p << endl;
}
virtual ~B()
{
cout << "~B()...." << endl;
if (p != NULL) {
delete[] p;
p = NULL;
}
}
private:
char *p;//和A类的p一样,都是个字的私有成员
};
void func(A *p)//p = cp; p = &c //用父类指针指向子类对象。
{
p->print(); //在此处发生多态。
}
void myDelete(A*p) //p->cp 父类指针指向子类对象
{
delete p; //p->~() //如果~() 不加virtual关键 不会发生多态。
//此时如果传入B,会先析构B再析构A
}
6、多态的实现原理:
6.1 当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类 成员函数指针 的数据结构,是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中。
6.2 存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。
6.3 通过虚函数表,指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。 而普通成员函数是在编译时就确定了调用的函数。在效率上, 虚函数的效率要低很多。出于效率考虑,没有必要将所有成员函数都声明为虚函数。
6.4 一个类中不管有几个虚函数,vptr指针只会存在一个。
7、构造函数中能否调用虚函数,实现多态??
不能,因为对象在创建的时,由编译器对VPTR指针进行初始化,只有当对象的构造完全结束后VPTR的指向才最终确定。
父类对象的VPTR指向父类虚函数表,子类对象的VPTR指向子类虚函数表。
8、纯虚函数:
8.1 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本。
8.2 纯虚函数不需要定义,只需要申明即可。
8.3 纯虚函数的语法:virtual 类型 函数名(参数表) = 0;
8.4 含有纯虚函数的类,称为抽象基类,不可实列化。 即不能创建对象,存在的意义就是被继承,提供族类的公共接口。
8.5 如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,
则该虚函数在派生类中仍然为纯虚函数,派生类仍然为纯虚基类。
9、纯虚函数相关代码展示:
//图形类
//拥有纯虚函数的类, 就叫抽象类
class Shape
{
public:
//是一个抽象的接口,说明图形是有一个得到面积方法
virtual double getArea() = 0;//代表一个接口,一个求图形面积的接口
//定义一个个打印面积的接口
virtual void print() = 0;
};
//圆类
//如果 一个子类继承了抽象类, 那么一定要重写这个纯虚函数。
class Circle :public Shape
{
public:
Circle(double r)
{
this->r = r;
}
//重写父类抽象类的纯虚函数
virtual double getArea()
{
return 3.14 * r * r;
}
virtual void print() {
cout << "圆的面积是" << endl;
cout << this->getArea() << endl;
}
private:
double r;//半径
};
//实现一个正方形
class Rect :public Shape
{
public:
Rect(double a)
{
this->a = a;
}
//是一个抽象的接口,说明图形是有一个得到面积方法
virtual double getArea()
{
return a*a;
}
//顶一个打印面积的接口
virtual void print() {
cout << "正方形的面积是" << endl;
cout << this->getArea() << endl;
}
private:
double a;//边长
};
//一个传递抽象类 指针的架构函数
void printArea(Shape *p)
{
p->print();
}
//业务层
int main(void)
{
//Shape p;//抽象类不能够实例化
Shape *sp = new Circle(10.0);
//抽象类的指针就可以调用纯虚函数, 接口
printArea(sp);
delete sp;
//创建一个正方形的对象。用抽象类指针(父类指针)指向子类对象
Shape *sp1 = new Rect(10.0);
printArea(sp1);
delete sp1;
cout << " ------ " << endl;
return 0;
}