C++(除了成员变量之外)还有另一种实现has-a关系的途径——私有继承。
使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。
(如果使用保护继承,基类的公有成员和保护成员都将称为派生类的保护成员。)
这意味着基类方法将不会称为派生类对象共有接口的一部分,但可以在派生类的成员函数中使用它们。
14.2.1 Student类示例(新版本)
Student类应从两个类派生而来,因此声明将列出这两个类:
class Student : private std::string, private std::valarray<double>
{
public:
    ...
};
使用多个基类的继承被称为多重继承(multiple inheritance,MI)。
新的Student类不需要私有数据,因为两个基类已经提供了所需的所有数据成员。包含版本(14.1节中的)提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的子对象成员。这是两个方法的第一个主要区别。
1.初始化基类组件
隐式地继承组件而不是成员对象将影响代码的编写,因为再也不能使用name和scores来描述对象了,而必须使用用于公有继承的技术。例如,对于构造函数,包含将使用这样的构造函数:
Student(cosnt char * str, const double * pd, int n)
    : name(str), scored(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)。这是包含和私有继承之间的第二个主要区别。
2.访问基类的方法
使用私有继承时,只能在派生类的方法中使用基类的方法。但有时候可能希望基类工具是公有的。例如,在类声明中提出可以使用average()函数。和包含一样,要实现这样的目的,可以在公有Student::average()函数中使用私有Student::Average()函数,包含使用对象来调用方法:
double Student::Average() const
{
    if (scores.size() > 0)
        return scores.sum() / scoresh.size();
    else
        return 0;
}
然而,私有继承使得能够使用类名和作用于解析运算符来调用基类的方法:
double Student::Average() const
{
    if (ArrayDb::size() > 0)
        return ArrayDb::sum() / ArrayDb::size();
    else
        return 0;
}
总之,使用包含是将使用对象名来调用方法,而使用私有继承时将使用类名和作用域解析运算符来调用方法。
3.访问基类对象
当派生类要使用基类对象本身,例如,Student类的包含版本实现了Name()方法,它返回string对象成员name;但使用私有继承时,该string对象没有名称。那么,Student类的代码如何访问内部的string对象呢?
答案是使用强制类型转换。由於Student类是从string类派生而来的,因此可以通过强制类型转换,将Student对象转换为string对象:结果为继承而来的string对象。本书前面介绍过,指针this指向用来调用方法的对象,因此*this为用来调用方法的对象,在这个例子中,为类型为Student的对象。为避免调用构造函数创建新的对象,可使用强制类型转换来创建一个引用。
const string & Student::Name() cosnt
{
    return (cosnt string &) *this;
}
上述方法返回一个引用,该引用指向用于调用该方法的Student对象中的继承而来的string对象。
4.访问基类的友元函数
用类名显式地限定函数名不适合于友元函数,这时因为友元不属于类。然而,可以通过显式地转换为基类来调用正确的函数。例如,对于下面的友元函数定义:
ostream & operator<<(ostream & os, const Student & stu)
{
    os << "Scores for " << (const string &) str << ":\n";
...
}
如果plato是一个Student对象,则下面的语句将调用上述函数,stu将是指向plato的引用,而
将是指向cout的引用:
cout << plato;
下面的代码:
os << "Scores for " << (const string &) stu << ":\n";
显式地将stu转换为string对象引用,进而调用函数operator<<(oshtream & const string &)。
引用stu不会自动转换为string对象引用。根本原因在于,在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。
然而,即使这个例子使用的是公有继承,也必须使用显式类型转换,原因之一是,如果不使用类型转换,下述代码将与友元函数原型匹配,从而导致递归调用:
os << stu;
(注:我觉得这里的意思是:当我os << stu时,他发现是一个Student类型,但是Student类型没有operator<<()方法,所以函数回去找他的基类的方法,但是他的基类会发现它实际上是一个Student类型,于是又会调用Student的<<方法……)
5.使用修改后的Student类
……

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

14.2.3 保护继承
保护继承是私有继承的变体。保护及成在列出基类时使用关键字protected:
class Student : protected std::string, protected std::valarray<double>
{ ... };

使用保护继承时,积累的共有成员和保护成员都将成为派生类的保护成员。和私有继承一样,积累的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承之间的主要区别便呈现出来了。使用私有继承时,第三代类将不能使用积累的接口,这时因为基类的公有方法在类中将变成私有方法;使用保护继承时,基类的公有方法在第二代中将编程受保护的,因此第三代派生类可以使用它们。

14.2.4 使用using重新定义访问权限
使用保护派生或私有派生时,基类的公有成员将称为保护成员或私有成员。假设要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法。例如,假设希望Student能够使用valarray类的sum()方法,可以在Student类的声明中生命一个sum()方法,然后像下面这样定义该方法:
double Student::sum() const     // public Student method
{
    return std::valarray<double>::sum();    // use privately-inherited method
}
这样Student类便能够调用Student::sum(),后者进而将valarray<double>::sum()方法应用于被包含的valarray对象(如果ArrayDb typedef在作用于中,也可以使用ArrayDb而不是std::valarray<double>)。
另一种方法是,将函数调用包装在另一个函数调用中,即使用一个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的共有方法一样:
cout << "high score: " << ada[i].max << endl;
注意,using声明只使用成员名——没有圆括号、函数特征标和返回类型。例如,为使Student类可以使用valarray的operator[]()方法拇指虚在Student类声明的公有部分包含下面的using声明:
using std::valarray>double<::operator[];
这将使两个版本(xonst和非const)都可用。这样,便可以删除Student::operator[]()的原型和定义。using声明只适用于继承,而不适用于包含。
有一种老式方式可用于在私有派生类中重新声明基类方法,即将方法名放在派生类的公有部分,如下所示:
class Student : private std:stirng, private std:valarray<double>
{
public:
    std::valarray<double>::operator[];  // redeclare as public, just use name
}
这看起来像不包含关键字using的using声明。这种方式已被废弃,即将停止使用。因此,如果编译器支持using声明,应使用它来使派生类可以使用私有基类中的方法。

《C++ Primer Plus》14.2 私有继承 学习笔记的更多相关文章

  1. 【09-23】js原型继承学习笔记

    js原型继承学习笔记 function funcA(){ this.a="prototype a"; } var b=new funcA(); b.a="object a ...

  2. c++ 继承学习笔记

    三大继承原则(由我杜撰) 基类的私有成员被继承后不可见(优先级最高) 公有继承不改变基类成员属性 保护继承(私有继承)把基类成员变为保护成员(私有成员)

  3. 【转载】Javascript原型继承-学习笔记

    阮一峰这篇文章写的很好 http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javas ...

  4. 《C++ Primer Plus》14.4 类模板 学习笔记

    14.4.1 定义类模板下面以第10章的Stack类为基础来建立模板.原来的类声明如下:typedef unsigned long Item; class Stack{private:    enum ...

  5. JavaScript 类型、原型与继承学习笔记

    目录 一.概览 二.数据类型 1. JavaScript中的数据类型 2. 什么是基本类型(Primitive Data Type) 2.1 概念 2.2 七个基本类型 2.3 基本类型封装对象 3. ...

  6. JavaScript继承学习笔记

    JavaScript作为一个面向对象语言(JS是基于对象的),可以实现继承是必不可少的,但是由于本身并没有类的概念,所以不会像真正的面向对象编程语言通过类实现继承,但可以通过其他方法实现继承.(jav ...

  7. 《C++ Primer Plus》15.1 友元 学习笔记

    15.1.1 友元类假定需要编写一个模拟电视机和遥控器的简单程序.决定定义一个Tv类和一个Remote类,来分别表示电视机和遥控器.遥控器和电视机之间既不是is-a关系也不是has-a关系.事实上,遥 ...

  8. 《C++ Primer Plus 第6版》学习笔记

    第三章.基本数据类型 整形 short:至少16位 int:至少与short一样长 long:至少32位,且至少与int一样长 long long:至少64位,且至少与long一样长 字符类型 cha ...

  9. 《C++ Primer Plus》16.4 泛型编程 学习笔记

    STL是一种泛型编程(generic programming).面向对象编程关注的是编成的数据方面,而泛型编程关注的是算法.它们之间的共同点是抽象和创建可重用代码,单他们的理念决然不同.泛型编程旨在编 ...

随机推荐

  1. SVN导入maven项目

    在项目中,曾今遇到过这种问题,用eclipse将项目从svn下载下来,maven去自动下载jar包怎么都报错,本来时间就很紧张, 还特么遇到这种坑爹的问题.不过,整了我一天,最后终于在同事的帮助下,搞 ...

  2. java并发编程()阻塞方法与中断方法

    看完这篇,我感觉我对java多线程又懵逼了. 线程可能会阻塞或暂停执行,原因有多种: 等待I/O操作结束 等待获得一个锁 等待从Thread.sleep方法中醒来 等待另一个线程计算的结果 当线程阻塞 ...

  3. QT4.8.5 QComboBox 增加选择菜单记录

    QT4.8.5 QComboBox 增加选择菜单记录 因为软件需要测试多个UART ,多个LAN,当要测试多个同样功能的时候就可以使用QComboBox类实现一个菜单选择功能. 步骤如下: 1. 在U ...

  4. nginx的centos和rhel的yum配置安装

    Official Red Hat/CentOS packages To add NGINX yum repository, create a file named /etc/yum.repos.d/n ...

  5. Hive UDF IP解析(一):依赖包兼容性问题

    Java依赖环境: <dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-e ...

  6. C#里面的三种定时计时器:Timer

    在.NET中有三种计时器:1.System.Windows.Forms命名空间下的Timer控件,它直接继承自Componet.Timer控件只有绑定了Tick事件和设置Enabled=True后才会 ...

  7. Hibernate- 动态实例查询

    什么是动态实例查询: 就是将查询出的单一列的字段,重新封装成对象,如果不适用特殊方法,会返回Object对象数组. 01.搭建环境 02.动态实例查询 需要使用相应的构造方法: public Book ...

  8. 【安装Python环境】之安装Selenium2时报UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc8 in position 12: invalid continuation byte问题

    问题描述: windows8.1系统,Python3环境安装Selenium2时报错,错误如下: ..... ..... File "F:\软件\python3.6.1\lib\site-p ...

  9. Java LinkedHashMap工作原理及实现

    Java LinkedHashMap工作原理及实现 原文出处: Yikun 1. 概述 在理解了#7 介绍的HashMap后,我们来学习LinkedHashMap的工作原理及实现.首先还是类似的,我们 ...

  10. Qt 线程基础(QThread、QtConcurrent等)

    [-] 使用线程 何时使用其他技术替代线程 应该使用 Qt 线程的哪种技术 Qt线程基础 QObject与线程 使用互斥量保护数据的完整 使用事件循环防止数据破坏 处理异步执行 昨晚看Qt的Manua ...