《C++ Primer Plus》14.2 私有继承 学习笔记

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声明,应使用它来使派生类可以使用私有基类中的方法。

时间: 2024-10-14 13:18:02

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

C#中面向对象编程机制之继承学习笔记

继承反应了类和类之间的关系. 世界上很多事物都是有共性的,共性的那一部分我们就抽象为基类,用于派生其它类,这样提高了代码的复用性,使得代码的结构清晰易读,而且易于代码的扩展和维护. C#的继承只能继承自一个基类,这一点不同于C++的继承. C#的继承具有传递性,即B继承自A,C继承自B,则C具有A的所有特性. C#的继承隐式为public的. 假如不在派生类构造器中显示调用一个基类构造器,编译器会自动插入对基类的默认构造器的一个调用,然后才会执行派生类构造器中的代码, 如果基类没有默认的构造器,

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

阮一峰这篇文章写的很好 http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html 笔记如下: 一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(p

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

14.4.1 定义类模板下面以第10章的Stack类为基础来建立模板.原来的类声明如下:typedef unsigned long Item; class Stack{private:    enum {MAX = 10};    // constant specific to class    Item items[MAX];    // holds stack items    int top;            // index for top stack itempublic:   

原型与继承学习笔记1

浅谈对象 面向对象原型链继承这块,应该算是javascript中最难理解的部分了,小弟脑子比较难转弯,也是看了好久视频,博文,慢慢的才有了自己的理解,现在记录一下学习的内容和总结.首先第一节应该说说对象这个东西了,js中对象和其他语言还是有所不同的,现在切入正题,开始浅谈对象. 什么是对象 定义(ECMA-262):无序属性的集合,其属性可以包含基本值.对象或者函数. 通过定义可以看出来,对象是属性的集合,这些属性又会是一个基本值,一个函数或者又是一个新的对象.记住,函数也是对象,了解这点以后原

JavaScript继承学习笔记

JavaScript作为一个面向对象语言(JS是基于对象的),可以实现继承是必不可少的,但是由于本身并没有类的概念,所以不会像真正的面向对象编程语言通过类实现继承,但可以通过其他方法实现继承.(javascript中的继承是通过原型链来体现的http://www.cnblogs.com/amumustyle/p/5435653.html)实现继承的方法很多,下面就只是其中的几种. 一.原型链继承 1 function Person() { //被继承的函数叫做超类型(父类,基类) 2 this.

原型与继承学习笔记4

经过前三节的研究,我们终于可以对js这门无法实现接口继承的语言进行实现继承,为了更好的面向对象..... 原型链继承 这个原型链我们上节引用了一下,用它实现继承的基本思想是利用原型让一个引用类型引用另一个引用类型的属性和方法.即把一个函数(狭义上来举例)的所有属性和方法(这个函数的实例)赋值给另一个函数的prototype,使一个函数的实例可以通过__proto__原型一层层的调用另一个函数的所有属性. 有点绕,简单的说就是把超类的实例赋值给子类的prototype.看个例子: 1 functi

c++ primer(第五版)学习笔记及习题答案代码版(第十四章)重载运算与类型转换

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.h 和.cc 中,需要演示某一题直接修改 #define NUM****, 如运行14.30题为#define NUM1430: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful f

c++ primer(第五版)学习笔记及习题答案代码版(第十一章)关联容器

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.cc 中,包含Chapter7.h头文件,读入文件包括./test ./rules .需要演示某一题直接修改 #define NUM****, 如运行11.23题为#define NUM1123: chapter 11 1.  关联容器不支持顺序容器的位置相关的操作,例如push_front或push_back.原因是关联容器中元素是根据关键字存储的,这些操作对 关联容器没有意义.而且关联容器也不支持构造函数或插入操作这些接收一个元素值和

c++ primer(第五版)学习笔记及习题答案代码版(第六章)函数

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.cc 中,编译需要包含Chapter6.h头文件. 需要演示某一题直接修改 #define NUM***, 如运行6.23题为#define NUM623: chapter 6 1. 形参初始化的机理与变量初始化一样. 当形参是引用类型时,它对应的实参被引用传递或者函数被传引用调用. 2. const和实参 void fcn(const int i){ /*fcn能够读取i,但是不能向i写值*/} void fcn(int i){ /*.