C++继承与多态五:继承多态与虚函数案例深入分析

我们这里来看几个问题来更加深入了解继承多态与虚函数。

题目一:猫狗叫声问题

//动物基类  泛指  类-》抽象一个实体的类型
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;
	}
};

class Pig : public Animal
{
	public:
	Pig(string name):Animal(name){}
	void bark()
	{
		cout << _name << "bark:heng heng!" << endl;
	}
};

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

	int *p11 = (int*)p1;
	int *p22 = (int*)p2;
	int tmp = p11[0];
	p11[0] = p22[0];
	p22[0] = tmp;

	p1->bark();
	p2->bark();

	delete p1;
	delete p2;

	return 0;
}

输出结果:
在这里插入图片描述
分析一下:

int *p11 = (int*)p1;
int *p22 = (int*)p2;
int tmp = p11[0];//p11[0]访问的Cat的前4个字节
p11[0] = p22[0];//p22[0]访问的Dog的前4个字节
p22[0] = tmp;

主要原因是Cat的vfptr存放Dog的vftable地址,Dog的vfptr存放Cat的vftable地址。因此发生动态绑定时,输出的结果被交换。

题目二:继承结构中基类派生类同名覆盖方法不同默认值问题

class Base
{
public:
	virtual void show(int i = 10)
	{
		cout << "call Base::show i:" << i << endl;
	}
};

class Derive : public Base
{
public:
	virtual void show(int i = 20)
	{
		cout << "call Derive::show i:" << i << endl;
	}
};

int main()
{
	Base *p = new Derive();
	p->show();
	delete p;

	return 0;
}

输出结果:
在这里插入图片描述
分析一下:
我们简单看一下,有virtual,是动态绑定,指向派生类对象。会调用派生类的show,但是为什么调用的派生类的方法,函数的形参默认值却使用了基类的参数的值?

push 0Ah =》函数调用,参数压栈在编译时期确定好的
mov eax, dword ptr[p]
mov ecx, dword ptr[eax]
call ecx

我们来看一下函数调用过程,调用一个函数时要先压参数,若没有传入实参,会将形参默认值压栈。运行时候才发生动态绑定调用派生类的show方法,编译阶段编译器只能看见基类的show,将基类的10压栈,参数压栈在编译时期确定好的。真正调用时不管是静态还是动态绑定,只要是show()方法形参压栈的值是固定的10。

题目三:派生类的方法写成私有的可以正常调用吗

class Base
{
public:
	virtual void show()
	{
		cout << "call Base::show" << endl;
	}
};

class Derive : public Base
{
private:
	virtual void show()
	{
		cout << "call Derive::show" << endl;
	}
};

int main()
{
	Base *p = new Derive();
	p->show();
	delete p;

	return 0;
}

输出结果:成功调用
在这里插入图片描述
分析一下:p->show()最终能够调用Derive的show,是在运行时期才确定的。但是成员方法的访问权限是不是public,是在编译阶段就需要确定好的。编译阶段编译器只能看见Base中的show为public,编译器可以调用;但最终调用的是基类的方法还是派生类的方法取决于形成的汇编指令是静态绑定还是动态绑定,因为为动态绑定最后成功调用派生类的show。

但是如果我们交换一下基类与派生类的访问限定符:

class Base
{
private:
	virtual void show()
	{
		cout << "call Base::show" << endl;
	}
};

class Derive : public Base
{
public:
	virtual void show()
	{
		cout << "call Derive::show" << endl;
	}
};

输出结果:编译出错。
在这里插入图片描述
只有使用派生类指针此时才可以调用:道理与前面相同。

Derive *p = new Derive();
p->show();
delete p;

题目四:下面两段代码是否正确

class Base
{
public:
	Base()
	{
		cout << "call Base()" << endl;
		clear();
	}
	void clear()
	{
		memset(this, 0, sizeof(*this));//Base大小赋为0
	}
	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()
{
	//1
	Base *pb1 = new Base();
	pb1->show();//动态绑定
	delete pb1;

	//2
	Base *pb2 = new Derive();
	pb2->show();//动态绑定
	delete pb2;

	return 0;
}

1输出结果: 执行失败
在这里插入图片描述
分析一下1: 基类的对象内存如图,vfptr指向Base vftable,当调用clear()时,将基类的对象内存清为0,虚函数指针也变为0地址,进行动态绑定时,访问不到,调用时出错,程序崩溃。
在这里插入图片描述
2输出结果:执行成功
在这里插入图片描述
分析一下2: 派生类中也有一个vfptr,首先调用基类的构造,构造基类部分,然后调用派生类构造,构造派生类部分。我们vfptr里面存储的是vftable的地址,底层来看指令一定有一个地方将vfptr写入vftable中。

push ebp
mov ebp,esp
sub esp,4Ch
rep stos esp<->ebp 0xCCCCCCCC

先了解一下这个:每一个函数进来首先push ebp,将调用方函数站点地址压进来,调用方函数站点地址放入当前函数栈底。再将esp赋给ebp,ebp指向当前函数栈底,sub esp 4Ch为当前函数开辟栈帧,rep sots esp<->ebp当前函数栈帧初始化为0;准备工作做完之后,指向当前函数第一行指令。若该类中有虚函数,生成的对象前4个字节有vfptr,会在函数栈帧开辟完之后将vfptr写入vftable,才执行函数第一行指令。vfptr <—>&Base::vftable,每次层构造函数都会执行刚才的步骤,派生类中会将vfptr<—>&Derive::vftable。
当我们new一个Derive对象时,首先调用基类构造,基类构造首先会将基类的vftable写入vfptr,再调用clear会将虚函数的值清为0;再调用派生类的构造函数,派生类构造函数压完栈初始化后会将&Derive::vftable的地址写入到虚函数指针中。当我们用指针调用show()vfptr是有效的,能够从虚函数表中取出来派生类重写的show(),因此执行成功。
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的继承多态虚函数是面向对象编程的重要概念。 继承是指一个类可以从另一个类继承属性和方法。子类可以继承父类的公有成员和保护成员,但不能继承私有成员。通过继承,子类可以重用父类的代码,并且可以添加自己的特定功能。继承可以实现代码的重用和层次化的设计。 多态是指同一个函数可以根据不同的对象调用不同的实现。多态可以通过虚函数来实现。虚函数是在基类中声明为虚拟的函数,它可以在派生类中被重写。当通过基类指针或引用调用虚函数时,实际调用的是派生类中的实现。这样可以实现动态绑定,即在运行时确定调用的函数。 虚函数的原理是通过虚函数表来实现的。每个包含虚函数的类都有一个虚函数表,其中存储了虚函数的地址。当调用虚函数时,编译器会根据对象的类型在虚函数表中查找对应的函数地址并调用。 综上所述,C++中的继承多态虚函数是实现面向对象编程的重要机制,它们可以提高代码的灵活性和可扩展性。 #### 引用[.reference_title] - *1* *3* [C++多态虚函数虚函数表](https://blog.csdn.net/weixin_46053588/article/details/121231465)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [c++多态虚函数表内部原理实战详解](https://blog.csdn.net/bitcarmanlee/article/details/124830241)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值