C++之多态

下面多态调用有一个问题就是多态调用的条件,这里要用到一个切片的问题,理解一下为啥多态的条件第二个是父类的指针或者引用去调用虚函数,因为多态的调用是跟指向的对象有关的,就这么说,根据切片的原理,如果我们没有用父类的指针或者引用,而只是简单用, 一个父类的对象,然后传参的时候Func(普通的子类对象),那么仅仅是将子类对象中父类的那一部分切片拷贝给父类对象而已,最终还是指向的父类对象啊,但是如果弄成指针或者引用的话,最后父类对象就会指向子类对象中父类的那一部分,最后调用的就是子类的那个虚函数

 

这个就是协变,注意哈加入父类的返回值改成父类的指针或引用,那么所有继承这个父类的子类里面的虚函数都得改返回值,改成父子类的指针或者引用(这个协变非常不常用!

当然这个协变里面的父子关系的指针或引用也可以是其他类型的父子关系的指针引用就像下面这个一样

 

 

 还有下面这个析构函数是一个面试题就是说,为啥我们建议析构函数也写成虚函数的形式,还是因为如果是普通调用的话那么就跟调用对象的类型有关,但如果是多态调用的话那么就跟指针/引用指向的对象类型有关了,所以这里如果是普通调用的话我们ptr1和ptr2都是Person类型的那么就都会调用这个父类的析构函数,但如果改成多态调用的话,那么这个就会分别调用ptr1和ptr2所指向的对象的析构函数(也就是正常调用了即先调用子类的ptr2的析构析构完以后调用它父类的析构,然后再是ptr1的析构)

 

这个是三者的对比

如何实现一个不能被继承的类??

  1. 构造私有

因为子类的构造函数必须去调用父类的构造(因为从父类继承下来的成员子类不能自己初始化而是必须调用父类的构造去初始化)但是现在父类的构造已经私有化了,所以调用不了父类的构造函数了已经

        2.类的定义后面加final

 

这个类就叫做最终类不能被继承

这个final还有一个场景就是下面这个修饰一个虚函数,那么这个虚函数就不能被重写了,但是这个场景就很不常见,因为虚函数的作用就是为了要被重写啊

 

 还有一个override是放在子类的虚函数后面为了检查是否实现了对父类虚函数的重写(在编译时检查的

 

抽象类(不能实例化)和纯虚函数(包含纯虚函数的类就是抽象类)

 

像这种一个虚函数后面加了一个赋值那么就变成了一个纯虚函数

 

像这个BMW继承下来car这个抽象类以后,而且BMW里面也没有重写这个纯虚函数而是直接继承下来,所以这个BMW这个类也是一个抽象类也不不能进行实例化

但是如果我们对BMW类里面的这个纯虚函数进行重写的话那么就可以进行实例化了就像下面这个样

 

所以说这个抽象类无形之中就也起一个强制子类去重写虚函数的作用,因为如果子类不去重写的话,那么就是一个抽象类最终也实例不出来一个对象或者说当我们不需要去实例化一个类的对象的时候也可以这样写成一个抽象类

还有就是这个子类的virtual也是可以不加的

接口继承和实现继承

普通的继承都是实现继承(相当于子类没有进行虚函数的重写而是直接把父类的实现继承下来),虚函数的重写是接口继承(就拿抽象类的继承来说,子类要想实例化就得对父类的纯虚函数进行重写,而这个就相当于仅仅继承了父类的这个函数的接口,然后函数实现是父类自己去实现的

 

 

这个题就是两个点

第一个点就是这个this指针的类型到底是B*呢还是A*呢,我们在main函数里面去调用的时候是用一个子类对象p去调用test(),但是这个test它是父类里面的函数,相当于是一个父类的this指针指向这个函数,而我们子类只是继承下来这个函数而已,this指针的类型还是父类的类型也就是A*,这个继承并不会改变从父类继承下来的this指针的类型,所以呢综上就是说这个this指针的类型还是父类的,那么也就是说这个调用还是遵循多态调用的原则,首先就是满足虚函数,然后就是满足用父类的指针去调用虚函数。

第二个点就是很坑的一个东西,要是只是按照第一个点这么说的话,那么要是按照多态的调用法则,多态的调用和指针/引用所指向的对象有关,那么现在指向的对象是子类呀,所以要调用子类的这个func()啊,结果应该是D啊

但是这个时候我们要注意这个题,虽然子类对父类的func函数进行了重写操作,但是,原先父类的func函数的参数是有一个缺省值的,重写并不会影响参数缺省值,这里最终调用的是B类重写后的func函数,不过参数的缺省值依然是之前的值

所以最终答案是B选项

看这个还是上面那个题,对于p->test()就是上面那个调用父类的this指针符合多态的调用规则,但是下面这个p->func()由于p是子类对象的指针,所以它不符合多态的调用原则

所以下面这个打印的是B->0

注意一下这个题就是也是切片的问题,Derive继承了两个父类一个是Base1一个是Base2,Base1在前面,Base2在下面,所以右图可以看出来三者的指向问题

这个是一个笔试题就是sizeof(Base)最终结果不是8而是12,因为这里存在一个虚函数指针,所以我们引出下面的这两个东西。。。

下面是虚函数表指针和虚函数表

看上面这个Derive类(子类)里面的虚表,完成重写的虚函数,虚表对应定位置覆盖成重写的虚函数

class Base

{

public:

virtual void Func1()

{

cout << "Base::Func1()" << endl;

}

virtual void Func2()

{

cout << "Base::Func2()" << endl;

}

void Func3()

{

cout << "Base::Func3()" << endl;

}

private:

int _b = 1;

char _ch;

};

class Derive : public Base

{

public:

virtual void Func1()

{

cout << "Derive::Func1()" << endl;

}

void Func3()

{

cout << "Derive::Func3()" << endl;

}

private:

int _d = 2;

};

//int main()

//{

// cout << sizeof(Base) << endl;

// Base b;

// Derive d;

//

// // 普通调用 -- 编译时/静态 绑定

// Base* ptr = &b;

// ptr->Func3();

// ptr = &d;

// ptr->Func3();

//

// // 多态调用 -- 运行时/动态 绑定

// ptr = &b;

// ptr->Func1();

// ptr = &d;

// ptr->Func1();

//

// return 0;

//}

这个普通调用就叫编译时/编译 绑定

多态调用就叫运行时/动态 绑定

 

所以说这个继承的多态的实现原理就是一个运行时绑定,都是取到这个虚表指针,然后去虚表里面找到那个对应的虚函数进行调用(比如我指向的是子类对象,那么就去子类的虚表里面找到对应的虚函数,指向的是父类对象,那么就去父类的虚表里面找到对应的虚函数)

所以说虚表是放在哪个位置呢???

 虚函数表属于类,类的所有对象共享这个类的虚函数表
        虚函数表存储在只读数据段(.rodata),也就是说虚函数表在编译阶段就已经形成了,虚函数表指针是在构造函数中赋值的。

我们可以通过打印的方法去找到,32位下就是vfptr前四个字节所以就先强转成(int*)类型的指针然后再解引用取前四个字节的内容,然后为了以指针的形式打印就再强转成void*类型的

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值