C++及反汇编 剖析多态及虚表(上:多态的实现)

多态

什么是虚函数,有什么用途?

虚函数主要用于多态中。

◼ 多态是面向对象非常重要的一个特性
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
在运行时,可以识别出真正的对象类型,调用对应子类中的函数


注意:此处非常重要!!!!!!!!!!!!!
多态的要素

子类重写父类的成员函数(override)
父类指针指向子类对象
利用父类指针调用重写的成员函数


非多态的弊

比如我有一个 猫, 狗, 猪三个类,它们都具有speak(叫) 和run(跑)的功能,即他们都具有相同的成员函数speak和run。我要实现分别调用每个动物的speak和run的功能,让他们一边叫,一边跑,那么你该如何创建类来调用他们呢?

这简单啊,三个动物类,每个类都有speak和run成员函数,在创建每个类的对象,分别调用不就好了??

如果我们不知道多态,我们确实会这样实现他们的功能:(但是这样有什么弊端呢??)

class Dog	//狗
{
public:
	void speak()
	{
		cout << "Dog::speak()\n";
	}
	void run()
	{
		cout << "Dog::ran()\n";
	}
};
class Cat	//猫
{
public:
	void speak()
	{
		cout << "Cat::speak()\n";
	}
	void run()
	{
		cout << "Cat::ran()\n";
	}
};
class Pig	//猪
{
public:
	void speak()
	{
		cout << "Pig::speak()\n";
	}
	void run()
	{
		cout << "Pig::ran()\n";
	}
};


//每个动物都具有特定的行为
void action(Dog* d)
{
	d->speak();
	d->run();
}
void action(Cat* d)
{
	d->speak();
	d->run();
}
void action(Pig* d)
{
	d->speak();
	d->run();
}
int main()
{	
	action(new Dog);
	action(new Cat);
	action(new Pig);
	return 0;
}

如上所示,我们定义了三个子函数,为了实现每一个动物的speak和run的功能,每一个action函数的形参都是特定化的,我们只需要写调用三次action函数,传递每一个对象都对应相应的指针形参,就好了:

在这里插入图片描述
这样我们就可以实现分别调用每个动物的speak和run的功能。

但是,这样写你会不会觉得太麻烦了如果我们有100个动物,你岂不是要写100个不同的action函数? 这样我们就引入了多态。

多态的利

我们能否把好多个动物都具有的相似功能集中到一个类上去? 这样我们就不必写100个action函数,然后每一个action都指定一只动物了。


多态的重要知识点:

  • 父类指针可以指向子类对象
  • 子类对象不可以指向父类指针

父类指针可以指向子类对象: 啥意思???

例如:

class Father
{
public:
	int money;	//钱
};

class Child :public Father
{
public:
	int toys;	//玩具
};
int main()
{
	Father* a = new Child();	//父类指针指向子类对象
	Child* b = new Father();	//错误(ERROR): 子类指针指向父类对象
	return 0;
}
  • 父类指针指向一个孩子,孩子有的,父亲也有,父类指针没有影响

  • 子类指针指向父类,子类有的,父类不一定有,父类没有此物品,但是孩子指针可以指向此物品,这就是造成了访问越界的问题。

我们来调用一下:

Father* a = new Child();
cout<< a->money<<endl;

Child* b = (Child* )new Father();
cout << b->money << " " << b->toys << endl;

在这里插入图片描述
子类继承的父类指针,父类指针没有toys的属性,但是子类对象可以访问这个属性,相当于子类指针只存储前四个字节(即父类有的属性),而父类没有的则不会存储。因此,子类对象一旦调用这个父类没有的属性则就会往后面找四个,但是此时它访问的已经越界了!!!

-33686019:即是它访问的越界的后面的字节组成的垃圾值
在这里插入图片描述
在这里插入图片描述

因此:我们可以得出结论


  • 父类指针可以指向子类对象(而且还经常使用)
  • 子类指针不可以指向父类对象。

这就是多态实现的本质
即通过继承同一个父类,让父类指针指向多个不同的子类对象,通过父类对象来调用他们公共的成员函数。

代码实现多态

现在,我们来实现多态:
即分别调用猫,狗,猪的各自的行为,使用多态形式:

我们引入了虚函数 virtual:

class Animals
{
public:
	virtual void speak()
	{
		cout << "Animal::speak()\n";
	}
	virtual void run()
	{
		cout << "Animal::ran()\n";
	}
};

我们定义了一个动物们的基类:他们都具有speak和run的功能,所以我们把他们放在一个统一的基类Animals中
接着我们为每个动物来继承这个基类

class Dog :public Animals
{
public:
	void speak()
	{
		cout << "Dog::speak()\n";
	}
	void run()
	{
		cout << "Dog::ran()\n";
	}
};
class Cat :public Animals
{
public:
	void speak()
	{
		cout << "Cat::speak()\n";
	}
	void run()
	{
		cout << "Cat::ran()\n";
	}
};
class Pig :public Animals
{
public:
	void speak()
	{
		cout << "Pig::speak()\n";
	}
	void run()
	{
		cout << "Pig::ran()\n";
	}
};

然后我们再利用一个统一的action函数,只需要一个就行,我们它的参数写作父类指针的形式:

//一个统一的动物行为
void action(Animals* ani)		//注意形参为父类指针   使用父类指针调用子类对象
{
	ani->speak();
	ani->run();
}

注意:我们的形参为Animals,他们共同的父类,所以我们就可以利用我们刚才讲的那一个重要的特性:
父类指针可以指向子类对象,来调用他们:

int main()
{
	action(new Dog);
	action(new Cat);
	action(new Pig);
	return 0;
}

注意,我们所写的这个实现代码和上面有什么不同? 我们把action函数的用作多态的形式,通过调用父类指针来指向他们不同的成员函数,这样便实现了一个函数根据形参的不同实现了多个不同的函数行为,本质就是父类指针可以指向子类的对象,并且调用子类的特定函数:
在这里插入图片描述

如果有1000个动物,他们都具有speak和run,如果不用多态,我们岂不是要写action函数1000次? 现在,我们只需要写1次action函数就好了但是你的类还是要写1000次,这个没办法,我们只需要把他们都继承自父类Animals类)。

virtual是啥 ---->虚表的引入

我们可能会好奇,virtual是啥,为啥要在父类的函数前面加上virtual??

注意:不是每个成员函数我们都加virtaul,只有多个子类同时具有的行为函数,我们才在他们的父类中加上virtaul,表示的大概含义就是说: 我这个函数实际上是虚的,空的,具体的实现行为还是要看子类的此函数的具体实现,父类中利用virtaul只是让你知道有这个函数,它在多个子类中都同时具有。

加不加virtaul有什么区别??


如果我们把virtual去掉:

class Animals
{
public:
	void speak()
	{
		cout << "Animal::speak()\n";
	}
	void run()
	{
		cout << "Animal::ran()\n";
	}
};

 ....此处省略动物类

int main()
{
	//如果不是多态
	Animals* dog = new Dog;	//父类指针调用子类对象
	dog->speak();
	dog->run();
	return 0;
}

注意我们仍然子类继承父类Animals,我们查看一下反汇编
看不懂的可以先看我的这篇博客:反汇编分析C/C++常见语句的底层实现
在这里插入图片描述
我们可以看到,虽然你使用了父类指针指向子类对象这一多态的基本条件,但是call的却是一个准确的地址,我们相想一想,不应该call的是一个随着你创建的对象不同而不同的地址吗??


如果我们在加上virtaul ,在Animals的准备要虚的函数里面:

那么你就会发现,惊喜的一幕:

WDF?????????????
这他妈什么鬼

在这里插入图片描述
怎么莫名其妙加上了这么多行??????? 闹鬼了???

我们产生了虚表!!!
并不是:这就是多态virtual实现的妙处:我们只要加上virtual,我们就为这个类创建了一张** 虚表 **,使得它可以根据虚表和创建的对象的类型来查找对应的speak和run函数 所在的位置,然后根据他们对应的地址,call到目标位置,是不是听不太懂??

没关系,我们下一节在讲解什么是虚表,以及虚表的实现原理

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuleo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值