第14章 C++中的代码重用
公有继承、私有继承、保护继承,还有之前介绍的模板,都属于代码的重用。
包含对象成员的类
细看原文
私有继承
私有继承是一种实现has-a关系途径。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会称为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
公有继承:基类的公有方法将成为派生类的公有方法
私有继承:基类的公有方法将成为派生类的私有方法
新的Student类示例
class Student : private std::string, private std::valarray<double>
{
public:
...
};
这里使用了多重继承,往往多重继承会引发一些问题,但是这里不会出现这种问题。
我们可以看到,新的Student类不需要私有数据,因为两个基类已经提供了所需的所有数据成员。包含版本提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的子对象成员。这是两种方法的第一个主要区别。
-
初始化基类组件
隐式地继承组件而不是对象成员将影响代码的编写,因为再也不能使用name和scores来描述对象了,而必须使用用于公有继承的技术。例如,对于构造函数,包含将使这样的构造函数:
Student(const char * str, const double * pd, int n) : name(str), scores(pd, n) { }; // use object names for containment
对于继承类,新版本的构造函数将使用成员初始化列表语法,它使用类名而不是成员名来标识构造函数:
Student(const char * str, const double * pd, int n) : std::string(str), ArrayDb(pd, n) { }; // use class names for inheritance
这就是第二个区别,可以看到新版本的成员初始化 列表使用std::string(str),而不是name(str)。
可以看原文的例子
-
访问基类的方法
我们要想使用基类的方法,只能在派生类的方法中使用基类的方法,并不能像公有继承一样直接使用了。
但是新版本的Student类中的“name”和“scores”都是没有名称的,是std::string和std::valarray,那我们该如何访问呢?我们需要使用作用域解析运算符进行访问。
double Student::Average() const { if (scores.size) > 0 return scores.sum()/scores.size(); else return 0; } // private inheritance double Student::Average() const { if (scores.size) > 0 return ArrayDb::sum()/ArrayDb::size(); else return 0; }
-
访问基类对象
那么我们需要访问基类对象怎么办呢?答案是使用强制类型转换。由于Student派生于String类
const string & Student::Name() const { return (const string &) *this; }
-
访问基类的友元函数
用类名显式地限定函数名不适合友元函数,这是因为友元不属于类。但是可以通过显式地转换为基类来调用正确的函数。
ostream & operator<<(ostream & os, const Student & stu) { os << "Scores for " << (const String &) stu << ":\n"; ... }
保护继承
保护继承是私有继承的变体。保护继承在列出基类时使用关键字protected
class Student : protected std::string, protected std::valarray<double> {};
使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承之间的主要区别变呈现出来了。使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中将变成私有方法;使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。
特征 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能否隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |
使用using重新定义访问权限
看原文
多重继承
必须使用关键字public进行限定每个基类。不然的话,编译器就会认为是私有派生
class SingerWaiter : public Singer, Waiter // 这里Waiter就是私有继承
多重继承会引起一部分问题,加入基类和派生类之间的关系如下
Worker是抽象基类,Singer和Waiter是派生于Worker的,而SingingWaiter又是派生于Singer和Waiter的
SingingWaiter ed;
Worker *pw = &ed; // ambiguous
这样会产生二义性,因为SingingWaiter有两个Worker,所以需要下面这样写
Worker *pw1 = (Waiter *) &ed;
Worker *pw2 = (Waiter *) &ed;
但是c++提供了一个新技术,虚基类
class Singer : virtual public Worker {};
class Singer : public virtual Worker {}; // public和virtual顺序无关
类模板
// stacktp.h -- a stack template
#ifndef STACKTP_H_
#define STACKTP_H_
template <class Type>
class Stack
{
private:
enum {MAX = 10}; // constant specific to class
Type items[MAX]; // holds stack items
int top; // index for top stack item
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type & item); // add item to stack
bool pop(Type & item); // pop top into item
};
template <class Type>
Stack<Type>::Stack()
{
top = 0;
}
template <class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}
template <class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template <class Type>
bool Stack<Type>::push(const Type & item)
{
if (top < MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
template <class Type>
bool Stack<Type>::pop(Type & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
#endif
对于模板,指针可以吗?答案是可以创建指针栈。细看原文例子。