《多态》使用和对象模型

使用

概念

由于没有一个较为标准的概念,这里就扼要阐述一下

不同对象在使用相同函数时,具有不同的实现效果。

ps:多态的实现是在继承的基础上。
在这里插入图片描述

虚函数

使用关键词 virtual 修饰的成员函数

如上图的 virtual void func()

  • 虚函数的作用就是被子类重写

  • 而对于析构函数,在使用delete释放资源时,就会使用父子类析构函数。否则就只会调用父类的析构函数。如下图:
    在这里插入图片描述

细心的朋友可以已经发现,为什么~B()没有加virtual也构成多态呢?其他的成员函数也可以这样吗?

答案是肯定的,这是因为继承的原因,**所以只要父类有virtual关键字,子类的成员函数不加,也可以构成多态。**但是我们建议还是加上,这样增加可读性,更加规范。

特例
1. 虚析构函数重写(函数名可以不同)
上例有体现
2. 协变(返回值可以不同)
即基类虚函数返回基类对象的指针或者引用(父用父的),派生类虚函数返回派生类对象的指针或者引用时(子用子的),称为协变。
在这里插入图片描述

返回值函数名形参列表发生的作用域
重载\相同参数类型,数量,位置不同同一级作用域
重写(覆盖)相同(除协变)相同(除虚析构函数)全相同父子类作用域中
隐藏(重定义)\相同\父子类作用域中

在这里插入图片描述

构成条件

前面铺垫这么多,就是为了引入多态的成立条件。

  1. 重写虚函数
  2. 使用父类指针或引用来调用虚函数

结合前面例子我们逐一分析:
在这里插入图片描述
这里说明一下,虚函数的重写规则:

派生类中有一个跟基类完全相同的虚函数
(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)

下面我们来一道题巩固一下所学知识
在这里插入图片描述
请问上述的结果是什么?

下面我们来分析一下:
子类对象作为实参传入,由于子类里面没有test()函数,所以使用父类的test()函数,该函数调用func()函数,但是func()函数是虚函数,这里就要考虑子类有没有发生重写,B类中重写了func(),产生多态,而这里的重写只是函数体的覆盖,而形参还是继承下来了,所以打印"B 0"。
在这里插入图片描述

抽象类

包含纯虚函数在的类就是,抽象类,也叫接口类

在这里插入图片描述
上面这个animal类就是抽象类

  • 特点
  1. 不能实例化对象
  2. 子类必须实现纯虚函数在这里插入图片描述
    ps: 纯虚函数跟虚函数没有任何关系

多态中的对象模型

虚函数表

简称虚表,是用来存储虚函数指针的一个虚函数指针数组

先来一道题,看看输出结果是什么?
在这里插入图片描述

这是基于32位平台的数据

在这里插入图片描述
可能会很奇怪,为什么是8呢,不是只有一个 int 的成员变量_b吗,应该是4才对。
其实不然,我们调试发现,对象b中还有一张虚函数表指针(_vfptr),
在这里插入图片描述
来看张图加深理解
在这里插入图片描述
这就很好理解为什么大小为8,而不是4了。

原理

前文有提到,虚表是用来存储虚函数指针的,也就是说只要是类中的虚函数,虚表都会将他们的地址存储起来。说到这个,我们插入一个题外话:普通成员函数和虚函数的存储地方在哪里?

💡 答案是:都在代码段里,因为虚函数和普通成员函数一样都是函数,只读的代码

所以可不能想当然的认为在虚表里面,那里面只是存储了虚函数的地址而已。

我们言归正传,多态是在继承的基础上进行的,单继承和多继承的多态在虚表方面又有所区别。

单继承虚表

先看下面这段代码,我们带着问题去得出答案

// 此函数用于打印虚表里面的函数个数
typedef void(*VTPtr)();
void print_vtptr(VTPtr table[])
{
	for (int i = 0; table[i]; ++i)
	{
	    printf("[%d]->%p\t", i, table[i]);
	    table[i]();
	    //VTPtr f = table[i];
	    //f();
	}
	cout << endl;
}

void Test()
{
    class A
    {
        virtual void func1() { cout << "A func1()" << endl; }
        virtual void func2() { cout << "A func2()" << endl; }
    };
    class B: public A {
        virtual void func1() { cout << "B func1()" << endl; }
        virtual void func3() { cout << "B func3()" << endl; }
    };
    A a;
    B b;
	// 运行环境32位的可以用下面两行
    //print_vtptr((VTPtr*)(*(int*)&a));
    //print_vtptr((VTPtr*)(*(int*)&b));
    print_vtptr(*(VTPtr**)&a);
	print_vtptr(*(VTPtr**)&b);
}
int main()
{
	Test();
	return 0;
}

❓问题:父类和子类的虚表是分别装有哪些内容?

我们编译可以看到,在监视窗口中,a,b对象的虚表 _vfptr 只有一个函数指针,括号里面都写着 func1 ,那么事实是否如此,a,b对象的虚表中都只存了一个虚函数指针 func1(),显然事实并非如此。

如下图,我们分析了内存,分别对a,b虚表进行内容查看,图中提前给出答案。
在这里插入图片描述

✔️ 在vs里面,虚表以nullptr结尾,这点就刚好能让我们利用起来。

下面对上述代码进行测验

在这里插入图片描述

至此可以得出答案了

💡子类会对父类的虚表拷贝一份,然后把自己重写的虚函数对父类的进行覆盖,没有重写的就保留下来

多继承虚表

说完了单继承,我们来谈谈多继承的虚表,看看会有什么不同之处。

// 此函数用于打印虚表里面的函数个数
typedef void(*VTPtr)();
void print_vtptr(VTPtr table[])
{
    for (int i = 0; table[i]; ++i)
    {
        printf("[%d]->%p\t", i, table[i]);
        table[i]();
    }
    cout << endl;
}

void main()
{
    class A
    {
        virtual void func1() { cout << "A func1()" << endl; }
        virtual void func2() { cout << "A func2()" << endl; }
    };
    class B
    {
        virtual void func1() { cout << "B func1()" << endl; }
        virtual void func3() { cout << "B func3()" << endl; }
    };
    class C:public A, public B
    {
        virtual void func1() { cout << "C func1()" << endl; }
        virtual void func3() { cout << "C func3()" << endl; }
        virtual void func4() { cout << "C func4()" << endl; }
    };
    
    A a;
    B b;
    C c;

    print_vtptr(*(VTPtr**)&a);
    print_vtptr(*(VTPtr**)&b);
    print_vtptr(*(VTPtr**)&c);
    B* pc = &c; // 第二张虚表的起始位置(利用指针自动偏移)
    print_vtptr(*(VTPtr**)pc);
	return 0;
}

❓问题:子类有几张虚表?分别装有哪些内容?

从单继承的例子中,我们不难分析得出:对象c应该会有两张虚表,分别拷贝a,b的,然后把重写的虚函数指针在c中的虚表覆盖。而对于c中新增的虚函数,该如何处理呢?我们猜测最大的可能性应该是在两张虚表中都新增虚函数的指针,当然这是一种猜测,还有其他很多种情况,那么具体什么情况,我们接着分析:

从这张截图分析:对象a,b分别有两个虚函数,对象c有三个虚函数,分别重写了 a中的 func1() 和 b中的 func3(),当然也拷贝了a,b两张虚表,因为多态所以在虚表中覆盖了父类原有的函数,最后看到,c对象新增的虚函数 func4() 补充到了第一张虚表的末尾。

在这里插入图片描述

💡得出结论:
多继承的虚表就是将父类们的虚表先拷贝过来,然后在进行虚函数的覆盖,最后新增的虚函数就加在第一张虚表上

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值