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. setTimeout解读

    看一个简单的例子: for(var i=0; i<4; i++){ setTimeout(function(){console.log(i)}, 0); } 请问下这段代码会输出什么呢? 如果你 ...

  2. 基于jQuery/CSS3实现拼图效果的相册插件

    今天我们要来分享一款很酷的jQuery相册插件,首先相册中的图片会以一定的角度倾斜放置在页面上,点击图片缩略图就可以展开图片,并且图片是由所有缩略图拼接而成,图片展开和收拢的动画效果也非常不错.当然图 ...

  3. Linux 网络子系统之结构介绍

    Linux 网络设备驱动程序的体系结构 图片说明如下: 网络协议接口层 网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议是ARP还是IP,都通过 dev_queue_xmit() 函数 ...

  4. NFS根文件系统

    按照以前文档可以正确制作根文件系统,并且开发板可正确nfs挂测主机目录. 现只需修改bootargs,使内核启动时挂测文件系统即可.setenv bootargs mem=64M console=tt ...

  5. [转]RosBridge小结

    1.rosbridge介绍 rosbridge(rosbridge_suite)是ros官方为开发者提供的一个用于非ros系统和ros系统进行交互通信的功能包.rosbridge主要包含两个部分,Ro ...

  6. C++/C语言的标准库函数与运算符的区别new/delete malloc/free

    malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符.它们都可用于申请动态内存和释放内存.下面来看他们的区别. 一.操作对象有所不同 malloc与free是C++ ...

  7. div 点击展开

    <script type="text/javascript" src="http://libs.baidu.com/jquery/2.1.1/jquery.min. ...

  8. ERROR 1045 (28000): Access denied for user 'root'@'127.0.0.1' (using password: YES)

    我的原因是在配置文件my.ini [mysqld]项,在其后加入了一句:skip-name-resolve 导致授权出现这个错误,把skip-name-resolve这项屏蔽了就好了. 场景2:对所有 ...

  9. e557. 在Applet中显示图片

    See also e551 精简的Applet. Image image; public void init() { // Load image image = getImage(getDocumen ...

  10. Wellner 自适应阈值二值化算法

    参考文档: Adaptive Thresholding for the DigitalDesk.pdf       Adaptive Thresholding Using the Integral I ...