【C++】多态结束篇

在这里插入图片描述

欢迎来到Cefler的博客
博客主页:那个传说中的man的主页
个人专栏:题目解析
推荐文章:题目大解析(3)


目录

  • 虚表存在内存中哪里?
  • 虚函数的地址一定会被放进类的虚函数表当中吗?
  • 多态的一些问答题
  • 多态选择题

虚表存在内存中哪里?

下面一段代码来验证查看验证一下:

#include 
using namespace std;
class Base
{
public:
	virtual void  func1() { cout << "Base::func1()" << endl; }
	virtual void func2(){ cout << "Base::func2()" << endl; }
};
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; }

};

int main()
{
	Base b1;
	static int a = 0;//静态区
	int b = 0;//栈区
	int* p1 = new int;//堆区
	const char* p2 = "hello";//代码段/字符常量区
	printf("静态区:%p\n", &a);
	printf("栈:%p\n", &b);
	printf("堆区:%p\n", p1);
	printf("代码段:%p\n", p2);
	printf("虚表:%p\n", *((int*)&b1));


	return 0;
}

【C++】多态结束篇_第1张图片
这里我们从地址上来看,可以得出一个大概的结论:虚表应该是存放在常量区中的,这个也好理解,虚表创建之后,本身就不让再修改了。

这里要解释一下这里打印虚表指针的方式

*((int*)&b1)

为什么要这样打印呢?实际上我们知道,b1对象头4字节中存储着虚表指针(因为指针大小为4字节),那么我们如何取出这4字节的指针呢?
如果直接取b1的地址并进行解引用,根本不知道要取多大的字节范围。
回顾指针知识,我们对一个int 类型的数组进行元素解引用使用,靠的就是int类型这个指针偏移量,让我们知道每次指针解引用的大小范围是多少。
所以这里同理,我们可以对b1的地址进行强制类型转为为int*.
这样我们对一个类型为int*类型的地址解引用,自然就只会取到4字节大小的内容。
【C++】多态结束篇_第2张图片

虚函数的地址一定会被放进类的虚函数表当中吗?

下面一段代码来验证查看验证一下:

#include 
using namespace std;
class Base
{
public:
	virtual void  func1() { cout << "Base::func1()" << endl; }
	virtual void func2(){ cout << "Base::func2()" << endl; }
};
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; }

};
class X :public Derive {
public:
	virtual void func3(){ cout << "X::func3()" << endl; }
};
typedef void (*VFUC)();//声明一个函数指针类型
void printVFT(VFUC a[])
{
	for (int i = 0; a[i]!=nullptr; i++)
	{
		printf("[%d]:%p->",i, a[i]);
		VFUC f = a[i];
		f();//(*f)()调用函数也可以
	}
}
int main()
{
	Base b;
	printVFT((VFUC*)*((int*)&b));
	cout << endl;
	Derive d;
	printVFT((VFUC*)*((int*)&d));
	cout << endl;

	X x;
	printVFT((VFUC*)*((int*)&x));



	return 0;
}

【C++】多态结束篇_第3张图片
所以得出结论:虚函数的地址一定会被放进类的虚函数表当中

多重继承的话,会有多个虚表
【C++】多态结束篇_第4张图片

多态的一些问答题

  1. inline函数可以是虚函数吗?
    答: 可以,不过多态调用编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去

  2. 静态成员可以是虚函数吗?
    答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

  3. 构造函数可以是虚函数吗?
    答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。

  4. 对象访问普通函数快还是虚函数更快?
    答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找

  5. 虚函数表是在什么阶段生成的,存在哪的?
    答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

多态选择题

一、
关于虚表说法正确的是( D)
A.一个类只能有一张虚表
B.基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C.虚表是在运行期间动态生成的
D.一个类的不同对象共享该类的虚表
解析
A.多继承的时候,就会可能有多张虚表

B.父类对象的虚表与子类对象的虚表没有任何关系,这是两个不同的对象

C.虚表是在编译期间生成的

D.一个类的不同对象共享该类的虚表

二、

以下程序输出结果是( )

class A

{

public:

  A ():m_iVal(0){test();}

  virtual void func() { std::cout<<m_iVal<<‘ ’;}

  void test(){func();}

public:

  int m_iVal;

};



class B : public A

{

public:

  B(){test();}

  virtual void func()

  {

    ++m_iVal;

    std::cout<<m_iVal<<‘ ’;

  }

};



int main(int argc ,char* argv[])

{

  A*p = new B;

  p->test();

  return 0;

}

结果是:0 1 2

解析:new B时先调用父类A的构造函数,执行test()函数,在调用func()函数,由于此时还处于对象构造阶段,多态机制还没有生效,所以,此时执行的func函数为父类的func函数,打印0,构造完父类后执行子类构造函数,又调用test函数,然后又执行func(),由于父类已经构造完毕,虚表已经生成,func满足多态的条件,所以调用子类的func函数,对成员m_iVal加1,进行打印,所以打印1, 最终通过父类指针p->test(),也是执行子类的func,所以会增加m_iVal的值,最终打印2, 所以答案为C 0 1 2

三、
假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则(B )
A.A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
B.A类对象和B类对象前4个字节存储的都是虚表的地址
C.A类对象和B类对象前4个字节存储的虚表地址相同
D.A类和B类中的内容完全一样,但是A类和B类使用的不是同一张虚表

解析
A.父类对象和子类对象的前4字节都是虚表地址

B.A类对象和B类对象前4个字节存储的都是虚表的地址,只是各自指向各自的虚表

C.不相同,各自有各自的虚表

D.A类和B类不是同一类内容不同

四、

下面函数输出结果是(A )

class A

{

public: 

  virtual void f()

  {

    cout<<"A::f()"<<endl;

  }

};



class B : public A

{

private:

   virtual void f()

  {

    cout<<"B::f()"<<endl;

  }

};



A* pa = (A*)new B;

pa->f();

A.B::f()
B.A::f(),因为子类的f()函数是私有的
C.A::f(),因为强制类型转化后,生成一个基类的临时对象,pa实际指向的是一个基类的临时对象
D.编译错误,私有的成员函数不能在类外调用

解析:
A.正确

B.虽然子类函数为私有,但是多态仅仅是用子类函数的地址覆盖虚表,最终调用的位置不变,只是执行函数发生变化

C.不强制也可以直接赋值,因为赋值兼容规则作出了保证

D.编译正确


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述

你可能感兴趣的:(C++,c++,多态,虚表)