本篇的目录
************************************
* 静多态 *
* 动多态 *
* 多态产生的条件 *
* 抽象类 *
*************************************
静多态
(1)静多态就是静态的多态,又称编译时的绑定。
(2)在C++中,模板(模板实例化在编译期间)和函数重载都属于静多态。
(3)对于编译时期的绑定,在编译期间,可以看到call (某个作用域下的函数名)
动多态
(1)动多态就是动态的多态,又称运行时的绑定。
(2)运行时的绑定在有虚函数时发生,如果一个指针或者引用调用一个虚函数时,在编译期间不能确定调用那个函数,会将虚函数表的地址放在寄存器中,因此只有在运行时才知道放的谁的地址,才会调用那个函数。查看反汇编可以看到:call eax
多态
(1)多态:多种形态的概念。同样的代码被调用,执行不同的操作,运行不同的代码。
(2)多态产生的条件:基于动态的绑定。
(3)动态的绑定怎么产生:指针或者引用调用虚函数时产生动态的绑定。
举个例子:我们想实现一个动物类,提供一个动物叫声方法;对于具体不同的动物对动物叫声有不同的处理方式。比如有两个动物猫和狗,它俩对继承下来的叫声有不同的处理(猫叫:miao miao miao!!;狗叫:wnag wang wang !!)
分析:对于这样的一个问题,我们采用继承来实现。
在继承中需要明白以下几点
1、基类指针(引用)可以指向派生类对象 2、派生类指针(引用)不可以指向基类对象 3、基类对象不可以赋值给派生类对象 4、派生类对象可以赋值给基类对象 |
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal()
{
cout<<"Animal construction"<<endl;
}
~Animal()
{
cout<<"Animal destructing"<<endl;
}
void makesound()//提供一个动物叫声的成员方法
{
cout<<"Animal make sound"<<endl;
}
};
class Cat:public Animal
{
public:
Cat(string name):_name(name)
{
cout<<"Cat construction"<<endl;
}
~Cat()
{
cout<<"Cat destructing"<<endl;
}
void makesound()//猫叫
{
cout<<_name<<"miao miao miao!!"<<endl;
}
private:
string _name;
};
class Dog:public Animal
{
public:
Dog(string name):_name(name)
{
cout<<"Dog construction"<<endl;
}
~Dog()
{
cout<<"Dog destrcting"<<endl;
}
void makesound()//狗叫
{
cout<<_name<<"wang wang wang!!"<<endl;
}
private:
string _name;
};
void Func(Animal* animal)
{
animal->makesound();
}
int main()
{
Cat cat("cat");
Dog dog("dog");
Func(&cat);
Func(&dog);
return 0;
}
打印:
由打印的结构可以看出,这个并不是我们想要的结果。
产看反汇编
可以看出:在我们提供的函数接口中,在汇编的时候是:call Animal::makesound也就是说,此时发生的是编译期间的绑定,在编译期间就已经确定好要调用那个函数,因此并没有实现猫叫和狗叫。
修改代码,实现我们所说的运行时的绑定:
将Animal类中的方法写成虚函数
virtual void makesound()//写成了虚函数
{
cout<<"Animal make sound"<<endl;
}
刚才说了多态基于动态的绑定发生,动态的绑定又基于指针指向虚函数发生,并且基类的指针(引用)可以指向派生类的对象,对我们提供的接口可以验证一下:
void Func1(Animal* animal)//1 ok 指针调用虚函数
{
animal->makesound();//call eax
}
void Func2(Animal& animal)//2 ok 引用调用虚函数
{
animal.makesound();//call eax
}
void Func3(Animal animal)//3 no 对象调用虚函数
{
animal.makesound();//call Animal::makesound()
}
打印:
查看Func1的反汇编:
过程是这样的:
animal对象是一个Animal类型的指针,在编译期间指针根据自己的类型调用函数makesound(),发现该函数是一个虚函数,虚函数存在于虚表中,因此编译期间不知道调用哪个函数,最后它将animal对象的前四个字节(虚函数表的地址)放在了寄存器eax中,call eax,等到运行时候,该指针指向的是一个cat对象或者dog对象,在cat/dog对象中存在与基类方法相同的void makesound()函数,在虚表中产生覆盖,因此调用相应的函数,运行时的绑定发生,达到我们所期望的效果。
不过,在上面的例子中,我们不愿意看到有这样的写法出现:Animal animal;也就是说不愿意看到用Animal实例化一个对象,因为Animal是一个笼统的概念,它并不是一个具体的动物,写成这样并没有什么作用,在C++中有一个抽象类,不允许实例化对象出来。
抽象类
(1)拥有纯虚函数的类叫做抽象类。
(2)纯虚函数:将一个虚函数写成=0的形式
virtual void makesound()=0;
这样就完美了,我们通过在基类提供一些接口,并将这些接口写成纯虚函数形式,既保证了基类不能实例化出对象,又能实现多态。