C++的一个主要目标是促进代码重用。公有继承是实现这种目标的机制之一,但并不是唯一的机制。例如使用这样的类成员:本身是另一个类的对象。这种方法称为包含(containment)组合(composition)或层次化(layering)。另一种方法是使用私有或保护继承。通常,包含、私有继承和保护继承用于实现has-a关系,即新的类将包含另一个类的对象。
类模板:类模板使我们能够使用通用术语定义类,然后使用模板来创建针对特定类型定义的特殊类。例如,可以定义一个通用的栈模板,然后使用该模板创建一个用于表示int值栈的类和一个用于表示double值栈的类,甚至可以创建一个这样的类,即用于表示由栈组成的栈。
包含对象成员的类:
valarray类是由头文件valarray支持的。顾名思义,这个类用于处理数值(或具有类似特性的类),它支持诸如将数组中所有元素的值相加以及在数组中找出最大和最小的值等操作。valarray被定义为一个模板类,以便能够处理不同的数据类型。
接口和实现:使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
C++和约束:C++包含让程序员能够限制程序结构的特征——使用explicit防止单参数构造函数的隐式转换,使用const限制方法修改数据,等等。这样做的根本原因是:在编译阶段出现错误优于在运行阶段出现错误。
初始化顺序:当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。
使用被包含对象的接口:被包含对象的接口不是公有的,但可以在类方法中使用它。
私有继承
C++还有另一种实现has-a关系的途径——私有继承。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
使用公有继承,基类的公有方法将成为派生类的公有方法。总之,派生类将继承基类的接口:这是is-a关系的一部分。使用私有继承,基类的公有方法将成为派生类的私有方法。总之,派生类不继承基类的接口。正如从被包含对象中看到的,这种不完全继承是has-a关系的一部分。
使用私有继承,类将继承实现。例如,如果从String类派生出Student类,后者将有一个String类组件,可用于保存字符串。另外,Student方法可以使用String方法来访问String组件。
包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。我们将使用术语子对象(subobject)来表示通过继承或包含添加的对象。因此私有继承提供的特性与包含相同:获得实现,但不获得接口。所以,私有继承也可以用来实现has-a关系。
使用多个基类的继承被称为多重继承(multiple inheritance,MI)。通常,MI尤其是公有MI将导致一些问题,必须使用额外的语法规则来解决它们。
访问基类的方法:使用私有继承时,只能在派生类的方法中使用基类的方法。然而,私有继承使得能够使用类名和作用域解析运算符来调用基类的方法。
总之,使用包含时将使用对象名来调用方法,而使用私有继承时将使用类名和作用于解析运算符来调用方法。
访问基类对象:使用作用域解析运算符可以访问基类的方法,但如果要使用基类对象本身时,该如何做?例如,Student类的包含版本实现了Name()方法,它返回string对象成员name;但使用私有继承时,该string对象没有名称。那么,Student类的代码如何访问内部的string对象呢?
答案是使用强制类型转换。由于Student类是从string类派生而来的,因此可以通过强制类型转换,将Student对象转换为string对象;结果为继承而来的string对象。
访问基类的友元函数:用类名显式地限定函数名不适合于友元函数,这是因为友元不属于类。然而,可以通过显式地转换为基类来调用正确地函数。
在私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用或指针。
#pragma once
#include<iostream>
#include<valarray>
#include<string>
class Student :
private std::string, private std::valarray<double>
{
private:
typedef std::valarray<double> ArrayDb;
//private method for scores output
std::ostream & arr_out(std::ostream &os) const;
public:
Student() :std::string("Null Student"), ArrayDb() {}
explicit Student(const std::string &s) :std::string(s), ArrayDb() {}
explicit Student(int n) :std::string("Nully"), ArrayDb(n) {}
Student(const std::string &s, int n) :std::string(s), ArrayDb(n) {}
Student(const std::string &s, const ArrayDb & a) :std::string(s), ArrayDb(a) {}
Student(const char *str, const double *pd, int n) :std::string(str), ArrayDb(pd, n) {}
~Student() {}
double Average()const;
double &operator[](int i);
double operator[](int i) const;
const std::string &Name() const;
//friend
//input
friend std::istream &operator>>(std::istream &is, Student &stu);
friend std::istream &getline(std::istream &is, Student &stu);
friend std::ostream &operator<<(std::ostream &os,const Student &stu);
};
#include "Student.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
//public methods
double Student::Average() const {
if (ArrayDb::size > 0) {
return ArrayDb::sum() / ArrayDb::size();
}
else return 0;
}
const string &Student::Name() const {
return (const string &)*this;
}
double & Student::operator[](int i) {
return ArrayDb::operator[](i);
}
double & Student::operator[](int i) const{
return ArrayDb::operator[](i);
}
ostream &Student::arr_out(ostream &os) const {
int i;
int lim = ArrayDb::size();
if (lim > 0) {
for (i = 0; i < lim; i++) {
os << ArrayDb::operator[](i) << " ";
if (i % 5 == 4) os << endl;
}
if (i % 5 != 0) os << endl;
}
else os << " empty array";
return os;
}
istream &operator>>(istream &is, Student &stu) {
is >> (string &)stu;
return is;
}
istream &getline(istream &is, Student &stu) {
getline(is, (string &)stu);
return is;
}
ostream &operator<<(ostream &os, const Student &stu) {
os << "Scores for " << (const string &)stu << ":\n";
stu.arr_out(os);
return os;
}