第十三课¶
在上一节中,我们了解了一个编程范式,其围绕对象的设计,构建以及交互来执行一个或多个任务。我们也探究了 C++ 中对象称之为类的原因,对象的组成,以及一些其他的基本内容,但是为了成为一个高效的 Haiku 开发人员,我们需要学习更多。
继承¶
Haiku 开发人员编写的很多代码都和继承相关,继承即创建一个基于其他类(基类)的类。例如,如果我们拥有一个矩形类(Rectangle),其具有高度(Height),宽度(Width),并且提供了一组用于计算面积(Area)和周长(Perimeter)的函数。创建一个长方体类(RectangularSolid)类也非常容易,仅需添加一个长度(Depth)和一个计算体积(Volume)的函数即可。
如果长方体类的第三个参数是它与长方形的唯一区别,那么就不必要为它们编写用于计算面积和周长的代码。如果我们仅需要为它们不同的地方编写代码,那这就太方便了。继承让这成为了可能:长方体类就称为长方形类的子类。和孩子继承父母的基因一样,子类继承了其父类的属性和方法,而我们的长方体类就继承了长方形类的所有方法和属性。
长方形 | 长方体 | |
---|---|---|
属性 | Height, Width | Height(继承),Width(继承), Depth |
方法 | Area,Perimeter | Area(继承),Perimeter(继承), Volume |
继承允许我们复用已有的代码。代码复用是 C++ 编程的基础之一。再次提醒,work smarter, not harder。
我们将编写一下两个类定义来分别介绍这两个例子:
class Rectangle
{
public:
Rectangle(int width, int height);
void SetWidth(int width);
int Width(void);
void SetHeight(int height);
int Height(void);
int Area(void);
int Perimeter(void);
private:
int fHeight;
int fWidth;
};
// 这行代码说明了 RectangularSolid 是Rectangle的子类。
class RectangularSolid : public Rectangle
{
public:
RectangularSolid(int width, int height, int depth);
// 我们不需要列出从Rectangle继承来的类,
// 除了那些新出现的类。
void SetDepth(int depth);
int Depth(void);
int Volume(void);
private:
int fDepth;
};
长方体类仅声明了新使用的方法和属性。编写具体的类实现将要比它们的定义需要更多的思考。这是我们第一次编写有关类的代码,所以,别走神,仔细研究一下这些代码和注释。
// Rectangle的构造函数。它将会将这些值赋给对象的属性。在为
// 类的方法编写代码时,类名和其后的两个冒号置于方法名之前。
Rectangle::Rectangle(int width, int height)
{
fWidth = width;
fHeight = heigh;
}
void
Rectangle::SetWidth(int width)
{
fWidth = width;
}
int
Rectangle::Width(void)
{
return fWidth;
}
void
Rectangle::SetHeight(int height)
{
fHeight = height;
}
int
Rectangle::Height(void)
{
return fHeight;
}
int
Rectangle::Area(void)
{
return fWidth * fHeight;
}
int
Rectangle::Perimeter(void)
{
return (2 * fWidth) + (2 * fHeight);
}
// 下面是 RectangularSolid 构造函数。在创建 RectangularSolid 的
// 同时,它第一次使用 Rectangle 的构造函数创建了一个 Rectangle 对象。
// 我们将跳过 height 和 width,而使用 depth 初始化 fDepth。
RectangularSolid::RectangularSolid(int width, int height, int depth)
{
fDepth = depth;
}
Void
RectangularSolid::SetDepth(int depth)
{
fDepth = depth;
}
int
RectangularSolid::Depth(void)
{
return fDepth;
}
int
RectangularSolid::Volume(void)
{
// 我们调用了 Width() 和 Height() 而不是使用 fWidth 和 fHeight,
// 这是因为每个子类都没有访问该对象的私有方法和属性。
return Width() * Height() * fDepth;
}
这段代码和之前我们所写的代码之间的真正不同点在于,对对象的思考强制我们去关心代码的组织。编写紧凑,优雅的代码可以让它的管理和维护非常容易。
在这段代码中,唯一比较陌生的部分莫过于在 Volume() 中调用 Height() 和 Width()。如果子类能够访问 fWidth 和 fHeight,那岂不是更加方便,这就是 protected 的领地了。尽管您可能并不需要或者不会经常用到。
还有一处值得注意的地方是继承的处理,也就是 Rectangle 的 public 部分。这就是继承的类型。通常您所使用的都是公共(public)继承。这也就是说,父类方法和属性的权限不会发生改变。选择其他两种类型将会限制对基类的访问。选择保护(protected)继承将使基类的所有公共属性和方法成为 protected 而非 public,将使它们对所有的子类都可用,但是对外界则不可见。私有(private)继承将使子类所有的属性和方法成为 private,使父类对外界和任何 “grandchild” 完全无法访问。
虚函数¶
子类不仅可以添加新的方法和属性,而且也可以修改已有方法的行为。但是仅当基类允许修改时才行。而在方法声明的返回值类型前添加 virtual 关键字就提供了这种保证。
// 子类可以重新定义该方法的行为。
Virtual void MyChangeableMethod(int someInt);
// 子类必须要重定义该方法。
Virtual void ThisMethodMustBeDefined(float someFloat) = 0;
虽然允许改变方法的行为方式,但是这并允许我们改变方法的参数类型和数量,以及其返回值类型。父类中的初始版本也并不完全消失。它可以通过作用域操作符进行指定,如下所示:
void
ChildClass::DoSomething(void)
{
Printf(“Child class did something\n);
ParentClass::DoSomething();
}
静态函数¶
通常,您需要调用对象的实例才能够使用其方法。有时候,这太麻烦了,难道所有的函数都必须有其“寄主”?我们可以在类的方法前面加上 static 关键字来解除该“魔咒”。静态函数和我们上述的虚函数一样,在调用时需要使用作用域操作符。
class Myclass
{
public:
MyClass(void);
int DoSomething(void);
static int DoSomethingStatic(void);
};
int
main(void)
{
MyClass myClassInstance;
// 调用函数的常用方法
MyClassInstance.DoSomething();
// 下面的函数不需要实例化类。
// 和常规的函数调用有略微的不同。
MyClass::DoSomethingStatic();
return 0;
}
重载:具有相同名称的函数¶
使用 C 进行编程时有一个限制,几多英豪曾为此烦恼,任何两个函数都不能够同名,即使它们的参数也不相同。C++ 移除了这个枷锁,因为编译器能够根据参数的数量和类型的不同辨别出它们的区别。如下:
int MyFunction(int oneWay);
int MyFunction(char *anotherWay);
int MyFunction(float aThirdWay);
但是,下述的例子则不行:
int SomeMethod(const char *oneConstString);
int SomeMethod(const char *anotherString, const char *optionalString = NULL);
为何不行呢?如果您忽略 optionalString 参数,编译器将无法理解您的意图。