多态

继承看类型,多态开对象。

1.多态的概念:
多态就是多种形态。具体点也就是完成每个行为,不同的对象去完成就会产生不同的状态。或者执行同一种行为产生不同的结果。多态看实际执行的对象,调用对应的代码,继承看类型。
2.多态的前提:继承
1)调用函数的类型必须是指针类型或引用
2)被调用函数必须为虚函数,并且被重写(函数名,参数,返回值全部相同)
3)函数重写(覆写):函数分别存在于子类和父类,函数名参数,返回值都相同(可以是协变)。
协变:返回值的类型可以不同,但是必须分别是基类指针和派生类指针或者基类引用和派生类引用。

class A{};
class B : public A {};
class Person {
public:
	virtual A* f() { return new A; }
};
class Student : public Person {
public:
	virtual B* f() { return new B; }
};

析构函数最好写成虚函数,让子类和父类的析构函数构成多态
编译器在底层会把父类和子类的析构函数改名为同类函数(destructor)。
接口继承(多态)和实现继承(继承):
普通函数的继承是一种实现继承,派生类继承了基类的函数,可以调用基类的函数,继承是函数是实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了完成函数的重写,达成多态,继承的是接口。所有如果不实现多态,不要把函数定义成虚函数。
重载:两个函数在同一个作用域,函数名相同,参数不相同
重写(覆盖):两个函数分别在基类和派生类作用域种,函数名/参数/返回值相同(协变除外)。两个函数必须为虚函数。
重定义(隐藏):两个函数分别在基类和派生类的作用域,函数名相同,基类和派生类的同名函数不构成重写就是重定义。
抽象类

  • 包含纯虚函数的类为抽象类:不完整的类,不能实例化出对象。
  • 抽象类:接口继承
  • 派生类继承后也不能实例化出对象,只有重写虚函数,派生类才能实例化出对象,纯虚函数规范了派生类必须重写,另外纯虚函数更体现了接口继承

fianl修饰基类的虚函数不能被继承
override纯虚函数+override 修饰基类函数,迫使派生类函数强制完成基类函数的重写,如果没有完全重写虚函数就会编译出错。

多态的原理:
在这里插入图片描述

virtual function pointer -->_vfptrtr---->虚函数表指针

我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就
    是存在部分的另一部分是自己的成员。
  2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重
    写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的
    叫法,覆盖是原理层的叫法。
  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会
    放进虚表。
  4. 虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr。
  5. 总结一下派生类的虚表生成:
  • 先将基类中的虚表内容拷贝一份到派生类虚表中
  • 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
  • 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。。

虚函数存放在代码段(vs下)
栈:又叫堆栈,存放非静态局部变量/函数参数/返回值等,栈是向下增长的。
内存映射段:类似于I/O映射方式,用于装载一个共享的内存空间。用户可以使用系统接口创建共享内存,做进程通信。
堆:用于程序运行时动态内存分配,堆是向上增长的。
数据段:储存全局数据和静态数据。
代码区:可执行的代码/只读常量。

多态的实现:
多态看对象,父类对象在调用它的虚函数时,访问的是父类中的虚表,从这里面拿到它对应的函数。子类同理;

  • 只要类中有虚函数,对象模型中就会存放一个虚表指针,虚表指针指向虚表,虚表本质上为函数指针数组,虚表在vs下存放在代码段
  • 本质上是通过虚表实现:程序在运行的时候,根据引用或者指针指向的对象,访问对象模型中的虚表指针,获取虚表中对应位置的函数指针,调用对应的函数
  • 子类的虚表和父类的虚表个数相同,子类自己定义的虚表放在第一个继承的虚表中
    **静态绑定:**又称为前期绑定,在程序编译期间确定了程序的行为。如,函数重载。
    **动态绑定:**又称为后期绑定,在程序运行期间,根据具体拿到的类型确定成雪的具体行为,调用具体的函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值