里氏转换原则
原文是派生的子类可以用于直接替代其基类
这个是一个很有意思也很常用的原则,当然也很重要,一般在面向对象设计里面我们最常看到的就是策略模式
//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的,而多重继承往往会出现比如二义性,菱形继承,调用不明确啊,乱七八糟的…通常情况还是喜欢使用复合类型,也就是在类里面嵌入其他类,我觉得这样的设计是趋于合理的。