多态的简单介绍:是一种动态的访问函数,比如:你定义了一个一个人类和一个学生类,当你传入的是学生类的时候,你需要有购物优惠,这种情境下用多态就很适用。
1.简单的多态使用:
1.1构造多态的条件:
(1)子类和父类中有同名函数,返回值,参数均相同(构成重写)
(2)基类的函数必须用virtual限定,子类可以不用,但是要注意下面代码的情况
(3)在使用多态的时候,需要使用基类去访问
1.2使用多态的一个小例子
#include <iostream>
class A {
public :
virtual void fun(int val = 1) {
std::cout << "A->" << val << std::endl;
}
virtual void test() {
fun();
}
};
class B:public A{
public:
void fun(int val = 0) {
std::cout << "B->" << val << std::endl;
}
};
int main() {
B* p = new B;
p->test();
return 0;
}
这个代码打印的结果是B->1;
让我们详细地走这个代码的允许过程:
首先使用 new B创建了一个B对象,但是因为B没用显示构造,所以直接使用默认构造(同理基类)然后把B对象的地址给了指针P,然后访问test(),因为在B中没有test函数,所以就传了A的this进去A类中找,成功找到test(),又需要去走fun(),因为现在的this指针是A类的,又fun()均用virtual修饰了,所以构成多态,但是这里又有一个大坑,就是派生类没有使用virtual修饰(正常是需要修饰的),派生类的函数就变成了virtual void fun(int val=1)直接把基类的完成拷贝了一份下来。
2.虚函数:
2.1虚函数的简单介绍
在上面的例子中,出现了virtual关键字,在对象成员函数前面加这个的函数就叫做虚函数,虚函数主要是为了用于实现多态。
2.2虚函数的重写(覆盖)和重定义
当子类和父类中函数的返回值和参数均一致,且父类有virtual时就会构成重写,当子类或者父类中出现一个虚函数,另外一个出现一个不是虚函数的对应函数称之为重定义。
2.3两个与重写相关的关键字
2.3.1 override
把这个放在派生类重写的函数后,如果没有构成重写,就会报错。
2.3.2 final
如果把这个放在基类虚函数的后面,就会限定它不能被重写。
2.4析构函数的多态使用
为什么析构函数最好就要构成多态?
如果不构成多态的话,那么怎么使用p3子类的析构函数?
所以析构需要构造多态!
3.纯虚函数与抽象类:
当你有一种类似于车这种类,这种类在现实中并没有实体,简单来说,现实中是不是说的车都是挂在品牌的?
class Car {
virtual void fun() = 0;
};
这种情况下就有了纯虚函数的定义,也就是 =0的这个,有这个就是纯虚函数,这种类是无法实例出对象的,如果继承它的类没有对他进行重构,那也无法实例出对象。这种类就叫做抽象类。
那这个是怎么用呢?看下面的代码,注意一个地方,虽然无法实例化,但是可以使用指针,因为要构成多态!!
4.从底层来看这个虚函数和多态:
从代码可以看出,虚函数表是不同的,子类和父类均有属于自己的虚函数表,虚函数是在代码段的,虚函数表是在常量段的,很容易就能猜想到一个类定义多个对象共享一个虚函数表。
如何验证虚函数表是在常量段的?取字符串的地址和以下获取的地址进行比较。(虚函数指针数组一般都是在对应对象内存的开头,如果要看地址的时候可以*(int*)对象指针,先int*转化会变成4个字节的大小(x86)的指针,解引用就是函数虚表指针数组的第一个,再解引用就能取得其地址。)
根据这个虚函数指针数组也就能知道其怎么做到动态访问的:
你用子类剪切给父类对象去访问的时候,虚表还是子类的,访问虚函数不还是访问子类的?其次就是如果没有虚函数概念的话,那显然就是该访问啥访问啥