c++多态

多态

​ ​1.编译器会增量编译;

​ 2.编译器会在初始化列表先初始化虚表指针,然后虚基表指针;

1.多态的概念

​ 多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态,如:买票不同的身份票价是不同的,支付宝红包对于不同使用支付宝的客群,可获得的金额范围是不同的。

2.多态的定义及实现

2.1多态的构成条件

​ 1.必须通过基类的指针或者引用调用虚函数;

​ 因为指针和引用最终可以访问到的内容还是原来的基类对象或者派生类对象,用基类的对象接收就变成了访问这个新对象,用对象调用函数就固定了,而用指针或者引用接收,如果是基类对象,接收到的就是基类对象,派生类对象就是派生类对象。就像是传值传参传址/传引用传参的区别,这样就无法实现多态的效果,产生不同的状态。真正对多态有效的部分就基类部分。

class X
{
public:
	void virtual test() { cout << "class X"; }
};
class Y:public X
{
public:
	void virtual test() { cout << "class Y:public X"; }
};
int main()
{
	X x;
	Y y;
	X p = x;
	x.test();
	y.test();
}

​ 2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 ;

​ 普通调用根据类型查找类域内函数,和以前一样确定函数声明和定义形成有效符号表,调用时直接访问函数地址;

​ 多态调用是在形成有效符号表之后,调用时会产生虚表指针,会去虚表查找函数。即多态之后要多走一步虚表。

​ 没有虚函数时,同名函数构成隐藏,父类看到的是父类,子类看到的是子类,即用父类切片父子类对象看到的都是父类,普通调用;

​ 有虚函数之后,会在成员变量中生成一个虚函数表指针,然后虚函数的地址会被放到虚表里面,每一个基类只能有一个虚表。如果没有重写,派生类生成的基类部分的虚表内的虚函数不变,不会替换。即用父类切片父子类对象看到的都是父类,普通调用;

​ 而虚函数重写,会将派生类生成的基类对象的虚表内构成重写的虚函数替换覆盖,父类切片子类看到的是子类函数,切片父类看到的父类函数,多态调用。

2.2虚函数

​ 虚函数:即被virtual修饰的类成员函数称为虚函数,在函数名前修饰。注意只有成员函数才可以成为虚函数。只有虚函数才能进行重写。

在这里插入图片描述

2.3虚函数的重写(覆盖)

​ 虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数 ( 即声明部分完全相同 ),称子类的虚函数重写了基类的虚函数。

​ 虚函数的重写,1.派生类可以不加virtual(不建议使用);2.三同有例外;3.协变(基类与派生类虚函数返回值类型不同,但是必须基类的是父类指针或者引用,派生类是子类指针或者引用,指针和指针、引用和引用、const和const配对,可以是其他类);4.析构函数的重写(基类与派生类析构函数的名字不同)

​ 如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

为什么析构函数要重写?

​ person与student,对于对象,student析构时先析构person部分然后析构student部分。对于new delete使用的是指针,在析构时,用person*析构student对象只会析构person部分,不会析构student部分,导致内存泄露。建议基类析构函数虚函数重写。

person *p =new person;
delete person;
p= new student;
delete student;//实际上会根据p类型调用person的析构函数。
//期望实现同一个指针new两个父子对象,用这个指针实现多态,然后用这个指针析构。

​ 虚函数重写的是函数体内部,其他是声明用来编译阶段语法检查。

class A
{
public:
    virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}

    virtual void test(){ func();}//this是A*
};

class B : public A
{
public:
    void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};

int main(int argc ,char* argv[])
{
    B*p = new B;
    p->test();//输出结果是B->1
    return 0;
}

3.c++11 override和final

​ 1.final:修饰虚函数,表示该虚函数不能再被重写。

	2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

4.设计一个不想被继承的类

​ 设计一个不想被继承的类:

​ 1.c++98,将构造函数私有就不可以继承,但是如果基类提供了公有静态函数调用构造,实际上派生类可以看到,但是由于必须调用父类构造所以,无法继承。

​ 2.析构函数私有,也得提供公有静态函数调用析构。

​ 3.c++11,final修饰基类,直接不允许继承;

5.抽象类

​ 车是抽象出来的,奔驰宝马是具体的;

​ 在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

​ 抽象类的使用就是派生类重写,实现多态,因为派生类才是具体的类。

6.函数的继承

​ 普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

7.多态的原理

1.派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。

2.基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

3.另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。

4.虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

5.总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。使用函数指针打印虚表,并且利用虚表指针数组最后是nullptr;

6.虚表在常量区。可以使用打印不同类型变量的地址与虚表地址对比,虚表的地址是对象的第一个位置,再结合虚基表第一个位置存放的是多态虚表相对于基类的偏移量,发现虚表很接近常量区,距离栈上的对象偏移量是FCFFFFFF很远要超过静态区到栈的距离。

8.多态的分类

​ 静态的多态:函数重载,编译时实现了多态的思想;而动态的多态就是指运行时。

9.多继承的虚函数表

​ call 后面的地址一般是jmp的地址,jmp后才是真正的地址。ecx存放的是this的值。

​ 多继承,会为每个基类创建一个虚表,每个派生类虚表都满足之前单继承虚表的语法,都进行了重写。新增加的虚函数会存放在先继承的虚表里。重写替换也是直接替换先继承的表里,而后继承的是先this-先继承类的大小,再替换。因为this-这个大小才能到后继承类的开头。对哪个基类重写,就用哪一个基类指针或引用接收。

10.菱形虚拟继承的虚函数表

​ 普通菱形继承虚函数表和多继承一样。

​ 菱形虚拟继承就会变成BC共用一个虚函数表,BC 虚基表第一行存放距离虚表的偏移量。但是要注意的是BC不能同时对A虚函数重写,会重写不明确,因为不知道最后到底改用哪一个重写,但是D实现虚函数重写,就不会报错。BC也不能在A的虚表添加为重写的虚函数,这样BC还会都再建立一张虚表。

​ 此时vs2019下模型是这样:

在这里插入图片描述

  • 73
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值