OOP思想--多态

本篇的目录

************************************

*      静多态                                    *

*      动多态                                    *

*      多态产生的条件                       *

*       抽象类                                   *

*************************************

静多态

(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;
这样就完美了,我们通过在基类提供一些接口,并将这些接口写成纯虚函数形式,既保证了基类不能实例化出对象,又能实现多态。

猜你喜欢

转载自blog.csdn.net/zhuoya_/article/details/80294067