c++Lab-虚函数

2023-10-29

里氏转换原则

原文是派生的子类可以用于直接替代其基类

这个是一个很有意思也很常用的原则,当然也很重要,一般在面向对象设计里面我们最常看到的就是策略模式

//c#
interface IMonster{
	void run()
}
class Skeleton: IMonster{
	override void run(){
		Console.WriteLine("im Skeleton");
	}
}
class Ogre:IMonster{
	override void run(){
		Console.WriteLine("im Ogre");
	}
}

void main(){
	IMonster mt;
	mt = new Skeleton();
	mt.run();
	mt = new Ogre();
	mt.runt();
}

策略模式的大致意思就是这样,策略的实现过程可以交给很多人写,主城只需要定义一个策略的实现接口,然后由策划提需求,分配任务的程序员自动实现run逻辑,然后在main中根据条件实例出接口,比如你在刷骷髅的副本,则实例Skeleton对象,如果你在刷食人魔的副本,则实例Ogre的对象

这是一个多态的基本用法,类似的操作你可以使用virtural,也可以使用abstract

这是在c#中的使用,很简单,在c++中其实用法也差不多

c++的虚函数

class Monster {
public:
	virtual void run() {
		cout << "im monster runing"<<endl;
	}
};

class Skeleton : public Monster {
public:
	virtual void run() override {
		cout << "im Skeleton " << endl;
	}
};
int main()
{
	Monster* mt = new Skeleton();
	mt->run();

	
}

测试结果
在这里插入图片描述
显然达成了我们想要的效果

虚函数的实现机制

去掉virtual关键字

我们现在去掉virtual,然后看看效果
class Monster {
public:
	void run() {
		cout << "im monster runing"<<endl;
	}
};

class Skeleton : public Monster {
public:
	void run() {
		cout << "im Skeleton " << endl;
	}
};
int main()
{
	Monster* mt = new Skeleton();
	mt->run();

	
}

在这里插入图片描述
哇…好神奇

为什么是这样的呢?

获取普通函数和虚函数的地址

我们知道,对于普通函数,每一个类都会有一个自己的函数空间用于存放函数,对于普通函数来讲,这个空间是唯一的,即使你实例100w个对象,每个对象的函数调用都是从这个空间里面去找函数,在调用的时候其实编译器帮你做了一件事情,就是将this传进函数里面。

所以我们现在知道每一个类都只有一个函数,所以地址是固定不变的

class Monster {
public:
	void run() {
		cout << "im monster runing"<<endl;
	}


	void test() {

	}
};

class Skeleton : public Monster {
public:
	void run() {
		cout << "im Skeleton " << endl;
	}


};
template<typename dst_type, typename src_type>
dst_type pointer_cast(src_type src)
{
	return *static_cast<dst_type*>(static_cast<void*>(&src));
}

int main()
{

	cout << pointer_cast<void*>(&Monster::run)<<endl;
	cout << pointer_cast<void*>(&Skeleton::run);
}

在这里插入图片描述

class Monster {
public:
	virtual void run() {
		cout << "im monster runing"<<endl;
	}


	void test() {

	}
};

class Skeleton : public Monster {
public:
	virtual void run() override{
		cout << "im Skeleton " << endl;
	}


};
template<typename dst_type, typename src_type>
dst_type pointer_cast(src_type src)
{
	return *static_cast<dst_type*>(static_cast<void*>(&src));
}

int main()
{
	cout << pointer_cast<void*>(&Monster::run)<<endl;
	cout << pointer_cast<void*>(&Skeleton::run);
}

在这里插入图片描述
发现问题了吗?
我们其实很容易发现虚函数是一个连续的地址,而普通函数则是一个散列
当我们通过父类去调用子类时,显然c++内部某种机制去检索了子类在虚表中的函数位置,然后传入this调用函数

机制

首先我们要了解一个vtpr,虚函数指针的概念,它指向虚函数,当你存在一个virtual关键字之后,vtpr就会产生,但你赋值之后,vtpr也会相应的初始化,当你调用虚函数的时候,其实就是调用vtpr指向的函数。
读完就感觉,这虚函数确实就挺简单的,当然更复杂的情况我们没有讨论,个人认为在c++编程过程中尽量避免多重继承,毕竟把握不住,我之前一直在使用c#或者java,认为接口机制是非常nice的,而多重继承往往会出现比如二义性,菱形继承,调用不明确啊,乱七八糟的…通常情况还是喜欢使用复合类型,也就是在类里面嵌入其他类,我觉得这样的设计是趋于合理的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++Lab-虚函数 的相关文章

随机推荐