1.1.1 C++中的抽象类
由于在某些情况下,定义那些不能被实例出对象的类是很有用的,这种类就称为抽象类(Abstract Class)。能够被实例化为对象的类称为具体类(Concrete Class)。抽象类的唯一目的就是让它的派生类继承并且实现它的函数接口,因此它通常也被称为抽象基类(Abstract Base Class)。
如果将基类的虚函数声明为纯虚函数,那么该类就成为抽象基类。纯虚函数是在声明时将其“初始”化为0的函数,例如:
class Shape
{
public:
virtual void Draw(void) = 0;
}
抽象基类Shape的纯虚函数并不知道它自己能干什么,具体功能必须由派生类对应的Draw函数来实现。由于将一个函数初始化为0意味着函数的地址将为0,这就是告诉编译器:不要给该函数编址,从而阻止了该类的实例化。
如果一个C++类拥有至少一个纯虚拟函数,那么它就是个抽象类。我们知道,在C++中,一个成员函数必须声明为虚拟函数才具有多态的行为,如果一个虚拟函数同时具有“纯”的性质,那这个函数就是一个抽象函数——它意味着我们不需要为这个函数生命提供实现代码。编译器期望在派生类中见到这个函数的实现代码。
抽象基类的主要作用是“接口与实现分离”;不仅要把数据成员隐藏起来,而且还要把实现完全隐藏起来,只留一些接口给外部调用。即使将来实现改变了,仍然可以保持接口不变。具体如下例所示:
#include<iostream>
using namespace std;
class IRectangle
{
public:
virtual void SetLength(float newLength)=0;
virtual void SetWidth(float newWidth)=0;
virtual float GetLength()const=0;
virtual float GetWidth()const=0;
static IRectangle *CreateRectangle();
void DestroyRectangle()
{
cout<<"IRectangle::DestroyRectangle()"<<endl;
delete this;
};
};
class RectangleImpl:public IRectangle
{
public:
RectangleImpl():length(1),width(1){}
virtual void SetLength(float newLength) {length = newLength;}
virtual void SetWidth(float newWidth) {width = newWidth;}
virtual float GetLength() const {return length;}
virtual float GetWidth() const {return width;}
private:
float length;
float width;
};
IRectangle *IRectangle::CreateRectangle()
{
return new(nothrow)RectangleImpl;
};
void main(void)
{
IRectangle *pRect = IRectangle::CreateRectangle();
if(pRect == NULL) exit(-1);
pRect->SetLength(100);
pRect->SetWidth(10);
cout<<"Length: "<<pRect->GetLength()<<endl;
cout<<"Width: "<<pRect->GetWidth()<<endl;
pRect->DestroyRectangle();
pRect = NULL;
}
程序运行结果:
Length: 100
Width: 10
IRectangle::DestroyRectangle()
Press any key to continue
以上程序的基类IRectangle实现了对派生类的隐藏。基类作为接口提供了CreateRectangle()静态成员函数来创建一个RectangleImpl对象,从而实现了对其派生类所有数据以及函数成员的隐藏。
1.1.2 C#中的抽象类
C#中的抽象类的作用与C++中抽象类的作用是相同的,概念也基本相同。但是也有几点区别:
1) 在C#中,抽象类必须进行显示的声明(既在类的头部显示地声明为抽象类):采用关键字abstract表示。
2) 一个C#类即使它并不拥有任何抽象方法的情况下仍然可以是个抽象类,只要它用abstract关键字进行了声明即可。
C#和C++一样,如果一个类派生于一个抽象类但是并没有为基类的所有抽象方法提供实现代码,那么这个派生类也被认为是一个抽象类。C#要求这种类型的类也必须在类的头部显示地使用abstract关键字进行声明。我们来看下面的例子:
abstract public class Contact {
protected string name;
public Contact() {
// statements...
}
public abstract void generateReport();
abstract public string Name {
get;
set;
}
}
Contact是一个抽象类。Contact有两个抽象成员,其中有一个是抽象方法,名为generateReport()。这个方法使用了abstract修饰符进行声明,这个声明没有实现(没有花括号)并以分号结束。属性Name也被声明为抽象的。属性访问器也是以分号结束。
public class Customer : Contact {
string gender;
decimal income;
int nuberOfVisits;
public Customer() {
// statements
}
public override void generateReport() {
// unique report
}
public override string Name {
get {
numberOfVisits++;
return name;
}
set {
name = value;
nuberOfVisits = 0;
}
}
}
public class SiteOwner : Contact {
int siteHits;
string mySite;
public SiteOwner() {
// statements...
}
public override void generateReport() {
// unique report
}
public override string Name {
get {
siteHits++;
return name;
}
set {
name = value;
siteHits = 0;
}
}
}
抽象基类有两个派生类——Customer和SiteOwner。这些派生类都实现了基类Contact中的抽象成员。每个派生类中的generateReport()方法声明中都有一个override修饰符。同样,Customer和SiteOwner中的Name属性的声明也都带有override修饰符。当重写方法时,C#有意地要求显式的声明。这种方法可以跳代码的安全性,因为它可以防止意外的方法重写,这在其他语言中确实发生过。省略override修饰符是错误的。同样,添加new修饰符也是错误的。抽象方法必须被重写,不能隐藏。
所有抽象类中最出名的就是Object类(.NET Framework中的Object类并不是抽象类)。Object是C#中的关键字,用于声明Object类的一个对象;Object是指.NET Framework类库中的System.Object类,但它们都是同一个类。Object类是C#中所有其他类的基类。它同时也是没有指定基类时的默认基类。下面的这些类声明产生同样的结果:
abstract public class Contact : Object {
// class members
}
abstract public class Contact {
// class members
}
如果没有声明基类,Object会隐式地成为基类。除了将C#类框架中的所有类联系在一起,Object类还提供了一些内建的功能,这些需要派生类来实现。