继承多态经典笔试题

文章讨论了C++中的多态性,包括通过VisualStudio的Ctrl+D快捷键复制行,以及虚函数的调用机制。它展示了如何通过交换对象的vfptr来改变虚函数调用的行为,解释了即使参数在编译时压栈,调用的仍是派生类的函数但使用基类的默认参数。此外,还提到了私有虚函数的调用以及构造函数中使用`memset`对对象初始化可能导致的问题,特别是当它清除vfptr时,会导致后续的虚函数调用出错。
摘要由CSDN通过智能技术生成

注:visual studio复制当前行粘贴到下一行: CTRL+D

杂项

调用子类重写的虚函数(带默认参数),但参数用的是基类的虚函数中的默认参数:

这是由于参数是在编译时压入

试题一

交换两个基类指针指向的对象的vfptr造成的运行结果变化

class Animal
{
public:
	Animal(string name) :_name(name) {}
	// 纯虚函数
	virtual void bark() = 0;
protected:
	string _name;
};
// 以下是动物实体类
class Cat : public Animal
{
public:
	Cat(string name) :Animal(name) {}
	void bark() { cout << _name << " bark: miao miao!" << endl; }
};
class Dog : public Animal
{
public:
	Dog(string name) :Animal(name) {}
	void bark() { cout << _name << " bark: wang wang!" << endl; }
};

int main()
{
	Animal* p1 = new Cat("加菲猫");
	Animal* p2 = new Dog("二哈");

	int* p11 = (int*)p1;
	int* p22 = (int*)p2;
	int temp = p11[0]; // p11[0]访问Cat的前4个字节,即 指向Cat的vftable的vfptr
	p11[0] = p22[0];  // p22[0]访问Dog的前4个字节,即 指向Dog的vftable的vfptr
	p22[0] = temp;
	// 上面代码相当于交换了虚函数表指针
	// 导致下面调用虚函数时就发生了非预期的情况

	p1->bark(); // 实际调用Dog::bark()
	p2->bark(); // 实际调用Cat::bark()

	delete p1;
	delete p2;
	return 0;
}

试题二

有如下代码

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
	virtual void show(int i = 10)
	{
		cout << "call Base::show i:" << i << endl;
	}
};
class Derive : public Base
{
public:
	void show(int i = 20)
	{
		cout << "call Derive::show i:" << i << endl;
	}
};
int main()
{
	Base* p = new Derive();
	p->show();
	delete p;
	return 0;
}
// 输出结果
// call Derive::show i:10

发现调用的是派生类覆盖的函数,确实是动态绑定,但使用的参数却是基类虚函数的默认参数,而不是派生类重写的虚函数的默认值。

原因是函数调用参数压栈是在编译时期,具体call函数可以在运行时,p->show() 汇编指令大致如下:

push 0Ah # 压入基类的默认实参10
mov eax, dword ptr[p]
mov ecx, dword ptr[eax]
call ecx

试题三

利用多态能调用到派生类private成员函数

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
	virtual void show(int i = 10)
	{
		cout << "call Base::show " << endl;
	}
};
class Derive : public Base
{
private:
	void show(int i = 20)
	{
		cout << "call Derive::show" << endl;
	}
};
int main()
{
	Base* p = new Derive();
	// 成员方法限定符是在【编译时】检查,由于编译时通过p检查的是Base::show(),没有问题
	// 而运行时调用子类覆盖的成员这不影响函数执行
	p->show(); // 最终调用到Derive::show(),是在运行时期确定
	delete p;
	return 0;
}

// 输出结果:call Derive::show

如果把基类的虚函数访问权限设置为private,则在编译阶段无法通过

试题四

说明下面代码段一和二是否正确执行,说明理由

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
	Base()
	{
		cout << "call Base()" << endl;
		clear();
	}
	// !!!!!!!!!!!!!!!!!!!!!!!!
	void clear() { memset(this, 0, sizeof(*this)); }
	virtual void show()
	{
		cout << "call Base::show()" << endl;
	}
};
class Derive : public Base
{
public:
	Derive()
	{
		
		cout << "call Derive()" << endl;
	}
	void show()
	{
		cout << "call Derive::show()" << endl;
	}
};
int main()
{
	// 代码段一
	Base* pb1 = new Base();
	pb1->show();
	delete pb1;

	// 代码段二
	/*Base* pb2 = new Derive();
	pb2->show();
	delete pb2;*/

	return 0;
}

解答:

代码段一执行失败

因为new Base()调用构造函数时,进入第一行代码前会生成vfptr(就是将vftable地址写入其中),指向Base类对应的虚函数表,但是在构造函数中后面又调用clear函数将内存清0,导致vfptr被置零,随后调用 pb1->show(); 时会使用vfptr则会造成非法访问,运行时提示:

引发了异常: 读取访问权限冲突。

pb1->**** 是 nullptr

代码段二执行成功

执行 new Derive(); 时先调用基类构造,虽然在构造函数中也会使vfptr被清零,但是随后调用派生类构造会将子类的vftable地址赋值给vfptr,所以导致最后通过 pb2->show(); 访问vfptr是没问题的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值