赞
踩
前言 前面4节我们已经完成了对4种C++对象布局的分析,本文试图覆盖更多的,常见的C++面向对象的概念。所以,最后2节将继续阐述2个主题:接口和抽象类以及构造函数、虚构函数和虚析构函数。
接口 这里我准备只主要阐述接口,而不谈一般的抽象类。因为在C++中,是没有“接口”这种类型的,所有的接口事实上是定义为纯抽象类。所谓纯抽象类,就是没有成员变量,没有实现了的函数,只有纯虚函数的抽象类。我相信,理解了接口这种特殊的抽象类,再去理解一般的抽象类是很容易的。
来看一个例子,我们有接口IAnimal来表示一般的动物行为,代码如下:
class IAnimal
{
public:
virtual void Eat()=0;
virtual void Move()=0;
virtual void Sleep()=0;
};
这个接口在内存中的布局可以看作下图中的样子:
接口在内存中的布局可以看作一个虚函数指针指向一个虚函数表,虚函数表中的所有元素指向的地址为0,因为所有纯虚函数都没有被实现。
上面的描述只是为了大家理解方便,事实上这种说法是很不准确的。因为,一个接口是绝对不能被构造的,这一点很重要。接口中的纯虚函数都没有被实现,如果允许构造他们,那么在调用这些方法的时候,将造成非法访问异常。
我们的问题来了,既然接口不允许被构造,那么我们为什么经常看到各种接口类型的对象呢。很简单,这些接口类型的对象都是由从该接口继承的子类对象通过类型转换而来的。在子类中,我们需要实现接口的所有方法,否则,该子类仍是一个抽象类。下图中,我们描述了继承了IAnimal接口的Horse类的内存结构图,这个类还继承了IVehicle接口:
关于Horse类如果进行指针调整,怎么转换为IAnimal接口类型,并实现多态特性的,我们在前面的章节中不止一次涉及到,即使是接口,也没有什么不同,我们这里不打算再重复。
下面,我们要谈的是,为什么我们要使用接口呢? 由于本文主要研究的是对象的内存布局,所以,关于这个涉及设计模式的主题,我只准备简单的讲述,这个主题太大了,因此我只希望下面的描述能给你一些关于接口应用场景的灵感。我们经常听到关于接口的描述是“只要实现了某某接口,对象就能实现某某功能/流程”,或者“就能被某某方法/模块调用来实现某某功能/流程”。 我们来看下面的代码:
void AnimalHappyDay(IAnimal* animal)
{
animal->Eat();
animal->Move();
animal->Sleep();
}
类似的,我们要说,任何类只要实现了IAnimal接口,AnimalHappyDay函数就可以调用该类的对象,来实现动物快乐的一天!我们不用关心这个类是猫,狗还是独角兽,不用关心它是否还实现了其他的接口,不用关心它是直接实现IAnimal接口的,还是间接的。他们只有一个共同点,就是实现了IAnimal接口。那么,它就可以被AnimalHappyDay函数调用,Eat,Move,Sleep,来实现动物快乐的一天这个功能。
一个接口,应该是最小粒度的。所谓最小粒度,首先,它没有任何的实现,只是用来描述一种标准的调用规范,完全由子类去实现它。其次,它只包含实现流程需要的最小的方法集合,比如,我们在IAnimal接口中不应该加入Grow方法,因为AnimalHappyDay流程不需要该方法,如果我们加入Grow方法,所有继承IAnimal的类就不得不去实现它。应该把Grow方法放在其他接口中,比如ILIfe接口,这个接口中或许定义了Both,Dead方法,用来实现动物的生命周期,类作者可以选择是否要去实现该接口。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。