多态(11)(下)

https://blog.csdn.net/qscftqwe/article/details/155774526

这是上节课内容,强烈推荐大家去看一下!

一.单继承和多继承关系的虚函数表

需要注意的是在单继承和多继承关系中,下面我们去关注的是派生类对象的虚表模型,因为基类的虚表模型前面我们已经看过了,没什么需要特别研究的。

1.1 单继承中的虚函数表

cpp 复制代码
class Base 
{ 
public :
    virtual void func1() { cout<<"Base::func1" <<endl;}
    virtual void func2() {cout<<"Base::func2" <<endl;}
private :
	int a;
};

class Derive :public Base 
{ 
public :
    virtual void func1() {cout<<"Derive::func1" <<endl;}
    virtual void func3() {cout<<"Derive::func3" <<endl;}
    virtual void func4() {cout<<"Derive::func4" <<endl;}
private :
	int b;
};

观察下图中的监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这两个函数,也可以认为是他的一个小bug。只能看到和父类有关的虚函数

真实的虚表应该是下面这样

1.2 多继承中的虚函数表

cpp 复制代码
class Base1 
{
public:
    virtual void func1() {cout << "Base1::func1" << endl;}
    virtual void func2() {cout << "Base1::func2" << endl;}
private:
	int b1;
};

class Base2 
{
public:
    virtual void func1() {cout << "Base2::func1" << endl;}
    virtual void func2() {cout << "Base2::func2" << endl;}
private:
	int b2;
};

class Derive : public Base1, public Base2 
{
public:
    virtual void func1() {cout << "Derive::func1" << endl;}
    virtual void func3() {cout << "Derive::func3" << endl;}
private:
	int d1;
};

观察下图可以看出继承几个虚表那么子类就有几个虚表:

多继承的派生类未重写的虚函数放在第一个继承基类部分的虚函数表中

1 .3. 菱形继承、菱形虚拟继承

实际中我们不建议设计出菱形继承及菱形虚拟继承,所以这边不进行考究

二.多态知识补充

2.1 为什么不能用父类对象去调用虚函数

为什么不能是父类对象,而是其指针或引用:

因为子类赋值给父类不会拷贝虚表,因此不能

当子类可以拷贝虚表给父类,那么拷贝完,父类的虚函数就变成了子类,如果生成了一个父类的指针或引用去调用父类的虚函数,此时调用的是子类的虚函数。

2.2 小知识

  1. 内联函数可以是虚函数:但是会被舍弃其inline的属性,因为要放到虚表中

  2. 静态成员函数不可以是虚函数:因为静态成员函数没有this指针

  3. 构造不可以是虚函数:因为虚函数指针是在初始化列表才初始化的

  4. 析构函数是虚函数:因为它们重写成了一个destructor名字

三.继承和多态常见的问题

思考题

1. 下面哪种面向对象的方法可以让你变得富有( )
A: 继承 B: 封装 C: 多态 D: 抽象
2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关, 而对方法的调用则可以关联于具体的对象。
A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定
3. 面向对象设计中的继承和组合,下面说法错误的是?()
A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复
用,也称为白盒复用
B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动
态复用,也称为黑盒复用
C:优先使用继承,而不是组合,是面向对象设计的第二原则
D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封
装性的表现
4. 以下关于纯虚函数的说法,正确的是( )
A:声明纯虚函数的类不能实例化对象
B:声明纯虚函数的类是虚基类
C:子类必须实现基类的纯虚函数
D:纯虚函数必须是空函数
5. 关于虚函数的描述正确的是( )
A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型
B:内联函数不能是虚函数
C:派生类必须重新定义基类的虚函数
D:虚函数可以是一个 static 型的函数
6. 关于虚表说法正确的是( )
A:一个类只能有一张虚表
B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C:虚表是在运行期间动态生成的
D:一个类的不同对象共享该类的虚表
7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
A: A 类对象的前 4 个字节存储虚表地址, B 类对象前 4 个字节不是虚表地址
B: A 类对象和 B 类对象前 4 个字节存储的都是虚基表的地址
C: A 类对象和 B 类对象前 4 个字节存储的虚表地址相同
D: A 类和 B 类虚表中虚函数个数相同,但 A 类和 B 类使用的不是同一张虚表
8. 下面程序输出结果是什么? ()

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(char *s) { cout<<s<<endl; }
	~A(){}
};

class B:virtual public A
{
public:
	B(char *s1,char*s2)
	:A(s1)
	{ cout<<s2<<endl; }
};

class C:virtual public A
{
public:
	C(char *s1,char*s2)
	:A(s1) 
	{ cout<<s2<<endl; }
};
class D:public B,public C
{
public:
	D(char *s1,char *s2,char *s3,char *s4)
	:B(s1,s2)
	,C(s1,s3)
	,A(s1)
	{ cout<<s4<<endl;}
};

int main() 
{
    D *p=new D("class A","class B","class C","class D");
    delete p;
    return 0;
}

A:class A class B class C class D

B:class D class B class C class A
C: class D class C class B class A
D: class A class C class B class D
9. 多继承中指针偏移问题?下面说法正确的是 ( )

cpp 复制代码
class Base1 
{  
public:  
	int _b1; 
};

class Base2 
{
public: 
	int _b2; 
};

class Derive : public Base1, public Base2 
{ 
public: 
	int _d; 
};

int main()
{
    Derive d;
    Base1* p1 = &d;
    Base2* p2 = &d;
    Derive* p3 = &d;
    return 0;
}

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

10. 以下程序输出结果是什么()

cpp 复制代码
class A
{
public:
    virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
    virtual void test(){ func();}
};

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();
    return 0;
}

A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

cpp 复制代码
  参考答案:
   1. A   2. D   3. C   4. 全错   5. B
   6. D   7. D   8. A   9. C   10. B

讲解

1:变得富有,不就是继承吗?

2:只需要记得动态绑定就是多态即可

3:讲继承的时候有讲到这个方面,是优先用组合而不是继承

**4:B:虚基类是虚继承得来的,C:如果子类没有实例化同样可以不实现,****D:**纯虚函数可以有定义

5: A:重写的定义,B:内联可以是虚函数不过会舍弃内联属性,C:要实现多态才必须重新定义,D:静态函数不能是虚函数,因为无this指针

6: A:多继承可能有多虚表,B:虚表是独立的,C:虚表是在编译期生成的, 运行时只是将对象的 vptr 指向对应的虚表地址不会动态创建虚表

**7:A:只要有虚表前4/8字节都是__vfptr,B:虚基表是和虚继承有关和虚函数无关,C:**虚表是独立的

8:在虚继承中,最派生类(如 D)负责构造虚基类(A) 。当 A 没有默认构造函数时,D 必须在其初始化列表中显式调用 A 的构造函数 ;而 B 和 C 虽然也写了对 A 的初始化,但在 D 被实例化时这些调用不会生效,不过保留它们是为了让 B 或 C 能被独立实例化。"

9:因为 Derive 第一个继承的是 Base1,所以 Base1 子对象位于 Derive 对象的起始地址,因此p1和p3具有相同的地址值。

10: 因为 func() 是在 A::test() 里调用的,编译器用的是 A 中定义的默认参数 1;虽然实际执行的是 B::func,但它收到的参数已经是 1 了,所以输出 B->1

问答题

**1. 什么是多态?**答:参考本节课件内容
2. 什么是重载、重写(覆盖)、重定义(隐藏) **?**答:参考本节课件内容
**3. 多态的实现原理?**答:参考本节课件内容
**4. inline函数可以是虚函数吗?**答:可以,不过编译器就忽略 inline 属性,这个函数就不再是
inline ,因为虚函数要放到虚表中去。
**5. 静态成员可以是虚函数吗?**答:不能,因为静态成员函数没有 this 指针,使用类型 :: 成员函数
的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
**6. 构造函数可以是虚函数吗?**答:不能,因为对象中的虚函数表指针是在构造函数初始化列表
阶段才初始化的。
**7. 析构函数可以是虚函数吗?**什么场景下析构函数是虚函数?答:可以,并且最好把基类的析
构函数定义成虚函数。参考本节课件内容
8. **对象访问普通函数快还是虚函数更快?**答:首先如果是普通对象,是一样快的。如果是指针
对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函
数表中去查找。
9. **虚函数表是在什么阶段生成的,存在哪的?**答:虚函数表是在编译阶段就生成的,一般情况
下存在代码段 ( 常量区 ) 的。
10. C++ **菱形继承的问题?虚继承的原理?**答:参考继承课件。注意这里不要把虚函数表和虚基
表搞混了。
11. **什么是抽象类?抽象类的作用?**答:参考( 3. 抽象类)。抽象类强制重写了虚函数,另外抽
象类体现出了接口继承关系。

本节同样是需要多思考而且需要多看几遍,掌握这些知识是很有帮助的!

相关推荐
yangpipi-2 小时前
《C++并发编程实战》 第4章 并发操作的同步
开发语言·c++
Chance_to_win3 小时前
C++基础知识
c++
有趣的我3 小时前
C++ 多态介绍
开发语言·c++
WBluuue3 小时前
Codeforces 1068 Div2(ABCD)
c++·算法
全栈陈序员4 小时前
【Python】基础语法入门(十七)——文件操作与数据持久化:安全读写本地数据
开发语言·人工智能·python·学习
啄缘之间4 小时前
11. UVM Test [uvm_test]
经验分享·笔记·学习·uvm·总结
RisunJan4 小时前
【行测】类比推理-自称他称全同
学习
阿沁QWQ4 小时前
C++的map和set
开发语言·c++
石像鬼₧魂石4 小时前
Termux ↔ Windows 靶机 反向连接实操命令清单
linux·windows·学习