14.2使用私有继承实现has-a关系

子类只可以访问父类中公共和保护的内容,但是私有的内容以任何方式都是访问不到的,但是确实继承下去了,只不过被编译器隐藏了,访问不到

  • 公共继承方式:父类中 公有和保护权限 到子类中都仍为 公有和保护权限
  • 保护继承方式:父类中 公有和保护权限 到子类中都变为 保护权限
  • 私有继承方式:父类中 公有和保护权限 到子类中都变为 私有权限

各种继承方式对比

特征公有继承保护继承私有继承
公有成员变成派生类的公有成员派生类的保护成员派生类的私有成员
保护成员变成派生类的保护成员派生类的保护成员派生类的私有成员
私有成员只能通过基类接口访问只能通过基类接口访问只能通过基类接口访问
能否隐式的向上转换是(但是只能在派生类中使用)

另外一种实现has-a关系的方法——私有继承

使用是私有继承的方式继承基类的实现,但是不继承基类的接口。基类的公有方法和保护方法将成为派生类的私有方法,派生类不继承基类的接口,这种不完全继承是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) {}
私有继承的具体使用方法
  1. 初始化基类组件–成员初始化列表----使用类名而不是成员名来标识构造函数
  2. 使用包含时使用对象名来调用方法,而使用私有继承时将使用类名和作用于解析运算符来调用基类方法。(因为没有对象名,所以只能通过类名和::来调用基类方法
// 比如使用包含时
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;
  1. 派生类想要调用基类的友元函数时,可以通过显示类型转换为基类对象来调用正确的友元函数。
// 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;
    ...
}
  • 原因二,这个类使用的是多重继承,如果两个基类都提供了函数operator<<(),编译器将无法确定转换成哪个基类。
使用包含还是私有继承
  • 使用包含可以包括多个同类的子对象,但使用私有继承由于没有名称将难以区分。
  • 私有继承可以访问保护成员,但是使用包含访问不了子对象保护权限的内容。

通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。

保护继承

保护继承是私有继承的变体。
和私有继承是一样的,基类的保护成员和公有成员都将成为派生类的保护成员。基类的接口在派生类内可用,在类外不可用。
使用私有继承时,第三代类将不能使用基类的接口,因为基类的公有方法在派生类中变成私有方法;
但是使用保护继承时,基类的公有方法将在第二代中变成保护的,因此第三代派生类可以使用他们

使用using声明重新定义访问权限

使用保护或私有派生时,假设想要是基类的方法在派生类外面可能,有下面两种方法。

  • 方法一:定义一个使用该基类方法的派生类方法
double Student::sum() const
{
    return std::valarray<double>::sum(); 
}
  • 方法二:使用using声明(就像名称空间中)来指出派生类可以使用特定的基类成员,即使采用的是私有派生。(using声明只适用于继承,不适用于包含)
// 假设希望通过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<double>::min()和valarray<double>::max()可用,就像它们是Student的公有方法一样。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值