多态
封装、继承、多态是C++的三大利器。
多态性是设计模式的基础:
需求:根据实际对象类型来判断重写函数的调用。
父类指针 指向父类对象 调用父类中定义的函数;
父类指针 指向子类对象 调用子类中定义的重写函数。
多态:同样的调用语句多种不同的表现形态
实现方式:基类中 函数 声明为 virtual。子类中重写该函数。
多态的实用性:
用基类指针传参 写一个函数,后来人新写的功能可以通过把对象传给基类指针,该函数就具有调用后来新加功能的效果
C语言 间接赋值 是指针存在的最大意义
1、一个形参,一个实参
2、实参地址赋给形参
3、形参取*间接改变实参的值
C++ 面向对象3大概念
封装:突破C函数的概念 用类做函数参数的时候,可以使用对象的属性 和对象的方法
继承: 代码复用
多态:可以使用未来,利用基类指针接受子类地址,调用子类的重写函数(该函数在基类中是virtual)
实现多态的三个条件
1、继承
2、同名虚函数重写
3、父类指针 指向 子类对象
静态联编:重载函数不加virtual关键字,就根据指针类型去执行
动态联编:加virtual关键字,运行时候根据具体类型执行不同对象的函数,变现成多态
动态联编:if和switch
虚析构函数:
构造函数不能为虚函数,析构函数可以为虚函数,用于指引delete运算符正确析构动态对象。
void delete(A *base)
{
delete base;
}
希望将A的子类C的对象C* c1赋给base后,可以释放c1,执行C的析构函数,但此时只会释放基类;
而继承中构造函数的调用规则是:
定义C的对象,会自动依次调用A,B,C的构造函数,执行delete(A* base)后只释放了一次基类内存,就会发生内存泄漏。
我们希望:
通过父类指针 把所有的子类对象的析构函数都执行一遍;
通过父类指针 释放所有的子类资源
只需要将基类的析构函数 定义为虚函数即可,C继承自B,B继承自A,只需要要将父类的析构函数定义为虚函数即可,A和B都需要
此时通过delete(A* base)就可以逆序调用析构函数了(不然,程序猿手动逐个析构就不给力了)
重载,重写,重定义
重载:在同一个类中进行。
重写:在父类和子类的不同类中,有两种
加virtual的是多态
不加virtual的是重定义
重要:子类不能重载父类的函数(重载只能发生在一个类中)
所以,当子类中没有父类的同名函数时,子类可以调用父类的函数,
当子类有父类的同名函数(参数(个数或类型)不同),C++认为子类同名函数会覆盖父类的函数,想要调用父类同名函数,需要加域作用符。
不然,C++编译器以为你要调用子类的该函数重载,可没有找到,就报错。
虚函数:
有虚函数时,对象第一个成员是vptr指针,该指针指向虚函数表,虚函数表存放虚成员函数指针
证明vptr指针存在,sizeof(对象);同一个类一个有虚函数一个没有虚函数,建立对象测试即可。
vptr的分步初始化,先将父类的虚函数表赋给vptr,后将子类虚函数表赋给vptr。所以父类构造函数调用虚函数不会发生多态。
父类指针步长与子类指针步长不一致,是在子类新加入成员变量造成的。
指针的大小取决于指向的对象:
class A { public: int a; public: virtual void print() { cout << "asd" << endl; } } class B : class A { public: int b; //造成子类的指针与父类指针大小不一致 public: virtual void print() { cout << "asd" << endl; } }
sizeof(A) //8字节 a 和 隐含的vptr指针(有虚函数时,就存在一个vptr指针的成员变量)
sizeof(B) //12字节 a , b , vptr.
如何实现多态(重要)
1、C++编译器为每个类定义对象时,提前布局的vptr指针
2、通过vptr指针访问虚函数表
3、当有虚函数的调用时,找到虚函数的入口地址来进行动态的值绑定
多态的原理:
1、效果:同样调用语句有多种不同的表现形态(一个函数在父类和不同子类穿梭时有不同的形态)
2、成立的三个条件:继承、virtual函数重写,父类指针指向子类对象
3、多态的C++实现:virtual关键字告诉编译器这个函数要支持多态;
不是根据指针类型判断如何调用;而是通过指针所指向的实际对象类型来判断如何调用。
4、多态的理论基础:
动态联编 静态联编。根据实际的对象类型来判断重写函数的调用。
5、多态的重要意义:设计模式的基础
6、实现多态的基础手段:函数指针做函数参数
多态原理探究:
理论知识:
1、当类中声明虚函数是,编译器会在类中生成一个虚函数表
2、虚函数表是一个存储类成员函数指针的数据结构
3、虚函数表是由编译器自动生成与维护的
4、virtual成员函数会被编译器放入虚函数表中
5、当存在虚函数时,每个对象中都有一个指向虚函数表的指针vptr
c++编译器给父类对象、子类对象提前布局vptr指针;当进行howToPrint(Parent* base)函数时,
C++编译器不需要区分子类对象或者父类对象,只需要在base指针中,找vptr指针即可
vptr一般作为类对象的第一个成员。
注:当类中有多个虚函数时,编译器只给类布局一个vptr指针,计算类的大小时注意。