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到目标位置
,是不是听不太懂??
没关系,我们下一节在讲解什么是虚表,以及虚表的实现原理。
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209702.html