一、多态
1.多态的概念
多态的概念:通俗来说,多态就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态) ,这⾥我们重点讲运⾏时多态。编译时多态主要就是函数模板与函数重载,他们传不同的参数就会调用不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配实在编译时就完成的,我们把编译时⼀般归为静态,运行时归为动态。
运⾏时多态,具体点就是去完成某个行为(函数),可以传不同的对象就会完成不同的⾏为,就达到多种形态。比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是优惠买票(5折或75折);军人买票时是优先买票。再比如,同样是动物叫的⼀个行为(函数),传猫对象过去,就是”(>^ω^<)喵“,传狗对象过去,就是"汪汪"。
2.多态的定义及实现
1.多态的构成条件
(1)必须是父类的指针或引用去调用虚函数
(2)被调用的函数必须是虚函数,且虚函数必须完成重写(覆盖)
说明:
- 为什么必须是父类的指针/引用去调用虚函数:因为只有基类的指针/引用才能既指向基类对象,又指向派生类对象
- 必须完成重写/覆盖:只有重写/覆盖了,积累和派生类才能又不同的函数,多态的不同效果才能达到。
示例:
先定义三个类
再使用不同的对象去调用函数
最终得到的结果也是不一样的
3.虚函数
(1)定义:类的成员函数前加virtual修饰,那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修饰,只能用来修饰非静态成员函数
(2)虚函数的重写/覆盖:派生类中有一个与基类完全相同的虚函数(函数名,返回值,参数列表全部相同(注意函数列表中参数默认值可以不同)),称派⽣类的虚函数重写了基类的虚函数。
(3)注意:在重写基类虚函数时,派⽣类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使⽤,不过在考试选择题中,经常会故意买这个坑,让你判断是否构成多态。
(4)简单例题
下面我们来分析一下得到他的输出结果
最终结果是 (5)虚函数重写的一些特殊情况
- 虚函数的协变:派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时,称为协变。
- 析构函数的重写:基类的析构函数为虚函数,此时的派生类析构函数只要定义,无论是否加virtual关键字,都构成重写。虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor
示例:
可以看到,如果我们没有去构成多态,派生类析构时只会调用基类的析构函数,这样就会造成内存泄露,这是因为编译后析构函数的名称统⼀处理成destructor(),这样就构成了隐藏,只调用了派生类中属于基类的析构函数,那为什么要写成多态呢,是因为如果调用了派生类的析构函数,编译器其会自动调用基类的析构函数,这是为了保证基类比派生类先析构(具体原因是怕父类先析构了,子类析构时会用到父类的成员)。
设计成多态后,delete只会调用类对象指向的析构函数,也就是派生类的析构函数,然后派生类的析构函数中会自动调用基类的析构函数保证基类比派生类先析构。
4.override 和 final 关键字
从上⾯可以看出,C++对虚函数重写的要求⽐较严格,但是有些情况下由于疏忽,⽐如函数名写错参数 写错等导致⽆法构成重写,⽽这种错误在编译期间是不会报出的,只有在程序运⾏时没有得到预期结 果才来debug会得不偿失,因此C++11提供了override,可以帮助⽤⼾检测是否重写。如果我们不想让 派⽣类重写这个虚函数,那么可以⽤final去修饰。
示例
比如说我这里函数名写错了,就会报语法错误(用在派生类函数头后面)
不想让你重写就可以用final (用在基类函数头后面)
5.重载/重写/隐藏的对比
(1)函数重载
- 两个函数必须在同一个作用域
- 返回值可相同可不同,函数名相同,参数类型,参数个数,参数顺序不同构成重载(但是仅仅返回值不同不能构成重载)、
(2)重写/覆盖
- 两个函数在不同作用域,一个在基类作用域,一个在派生类作用域
- 函数名,参数列表,返回值都必须相同(协变除外)
- 两个函数都必须是虚函数
(3)隐藏/重定义
- 两个函数在不同作用域,一个在基类作用域,一个在派生类作用域
- 函数名相同,其他随意
- 两个函数只要不构成重写,就是隐藏(部分语境正确)
- 父子类的成员变量也叫做隐藏
6.抽象类纯虚函数
1.纯虚函数:在虚函数后面加上一个=0,则这个虚函数被称为纯虚函数,纯虚函数不需要定义实现,只需声明即可。有纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派生类继承后不重写纯虚函数,那么派生类也是抽象类。纯虚函数某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象
纯虚函数未实例化,不能实例化出对象(包括匿名对象),匿名对象也不能调用抽象类中的函数
纯虚函数被子类重写后,派生类对象可调用被重写后的虚函数
7.多态的原理