子类只可以访问父类中公共和保护的内容,但是私有的内容以任何方式都是访问不到的,但是确实继承下去了,只不过被编译器隐藏了,访问不到
各种继承方式对比
特征 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能否隐式的向上转换 | 是 | 是(但是只能在派生类中使用) | 否 |
使用是私有继承的方式继承基类的实现,但是不继承基类的接口。基类的公有方法和保护方法将成为派生类的私有方法,派生类不继承基类的接口,这种不完全继承是has-a关系的一部分
这里新的Student类不需要私有数据,因为两个基类已经提供了所需的所有数据成员
(**但是是基类的私有数据,派生类只能通过基类提供的公有和保护方法来访问)**)
// 私有继承方式
class Student : private std::string, private std::valarray<double>
{
punlic:
...
}
// 包含方式
class Student
{
private:
typedef std::valarray<double> ArrayDb
string name;
ArrayDb scores;
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() : name("Null Student"), scores() {}
explicit Student(const std::string & s)
: name(s), scores() {}
// 这里的参数int n是指数组元素的个数,不是数组中的值
// 而一个参数的构造函数有可能会被用于隐式类型转换,发生不必要的错误。 // 使用关键字explicit关闭隐式转换,避免发生未经允许的隐式类型转换
explicit Student(int n) : name("Nully"), scores(n) {}
Student(const std::string & s, int n)
: name(s), scores(n) {}
Student(const std::string & s, const ArrayDb & a)
: name(s), scores(a) {}
Student(const char * str, const double * pd, int n)
: name(str), scores(pd, n) {}
// 比如使用包含时
double Student::Average() const
{
if (scores.size() > 0)
return scores.sum() / scores.size();
...
}
// 换成使用私有继承
double Student::Average() const
{
if (ArrayDb::size() > 0)
return ArrayDb.sum() / ArrayDb.size();
...
}
3.使用私有继承时,假如要访问基类对象,由于该基类对象没有名称,这里应该使用强制类型转换将派生类对象显式的向上转换为基类对象,为避免调用构造函数创建新的对象,这里应该创建一个引用。
// 以下方法返回一个引用,该引用指向调用该方法的Student对象中的继承而来的string对象。
const string & Student::Name() const
{
return (const string &) *this;
}
// FAO这里的ada.Name()是一个string对象的引用,相当于string name; cout << name << endl;
// 这就是使用私有继承时使用对象与使用对象组合(包含)的区别
for (i = 0; i < pupils; ++i)
cout << ada[i].Name() << endl;
// os << Student将自动调用下面的函数
// 进而调用 os << string
// 进而调用 string的友元函数operator<<(ostream &, const string &)
ostream & operator<<(ostream & os, const Student & stu)
{
os << "Scores for "<<(const string &)stu << ":\n;
...
}
在私有继承中,未进行显示类型转换的派生类引用或指针,无法赋值给基类的引用或指针
即使这个例子中使用的是公有继承必须使用显示类型转换,
// 发生递归调用,错误!
ostream & operator<<(ostream & os, const Student & stu)
{
os << stu;
...
}
通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。
保护继承是私有继承的变体。
和私有继承是一样的,基类的保护成员和公有成员都将成为派生类的保护成员。基类的接口在派生类内可用,在类外不可用。
使用私有继承时,第三代类将不能使用基类的接口,因为基类的公有方法在派生类中变成私有方法;
但是使用保护继承时,基类的公有方法将在第二代中变成保护的,因此第三代派生类可以使用他们。
使用保护或私有派生时,假设想要是基类的方法在派生类外面可能,有下面两种方法。
double Student::sum() const
{
return std::valarray<double>::sum();
}
// 假设希望通过Student类能够使用valarray类的方法min()和max(),可以在studenti.h的公有部分加入如下的using声明:
class Student : private std::string, private std::valarray<double>
{
...
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
...
}
// 上述using声明使得valarray::min()和valarray::max()可用,就像它们是Student的公有方法一样。