C++ primer plus读书笔记——第14章 C++中的代码重用
第14章 C++中的代码重用
1. 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
2. C++还有另一种实现has-a关系的途径——私有继承。使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员。这意味着基类方法将不会称为派生类对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
3. 包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未命名的继承对象添加到类中。我们将使用术语子对象(subobject)来表示通过继承或包含添加的对象。
因此,私有继承提供的特性和包含相同:获得实现,但不获得接口。所以,私有继承也可以用来实现has-a关系。
4. 要进行私有继承,请使用关键字private来定义类。实际上,private是默认值,因此省略访问限定符也将导致私有继承。
5. 使用包含还是继承来建立has-a关系
大多数C++程序员倾向于包含。首先,它易于理解。类声明中包含表示被包含类的显示命名对象,代码可以通过名称引用这些对象,而使用继承将使关系更抽象。其次,继承会引起很多问题,尤其从多个基类继承时,可能必须处理很多问题,如包含同名方法的独立的基类或共享祖先的独立基类。总之,使用包含不太可能遇到这样的麻烦。另外,包含能够包括多个同类的子对象。如果某个类需要3个string对象,可以使用包含声明3个独立的string成员。而继承只能使用一个这样的对象(当对象都没有名称时,将难以区分)。
6. 通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。
7. 使用保护继承时,基类的公有成员和保护成员都将称为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承的主要区别便呈现出来了。使用私有继承时,第三代类将不能使用基类的接口,是因为基类的公有方法在派生类中将变成私有方法;使用保护继承时,基类的公有方法和保护方法在第二代类中将变成保护的,因此第三代类可以使用它们。保护继承和私有继承一样,都是表示has-a关系。
8. 隐式向上转换意味着无需进行显式类型转换,就可以将基类指针或引用指向派生类对象。
公有继承能隐式向上转换,保护继承只能在派生类中才能隐式类型转换,私有继承不能隐式类型转换。
9. 使用using重新定义访问权限
使用保护派生或私有派生时,基类的公有成员将称为保护成员或私有成员。假如要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法。另一种方法是使用using声明,来指出派生类可以使用的特定基类成员,即使采用的是私有派生(注意:using声明只使用函数名,没有圆括号、函数特征标和返回类型)。P550
10. 多重继承MI可能会给程序员带来很多问题。其中两个主要的问题是:从两个不同的基类继承同名方法;从两个或更多相关基类那里继承同一个类的多个实例。
11. 虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。例如:通过在类声明中使用virtual,可以使Worker被用作Singer和Waiter的虚基类(virtual和public的次序无关紧要):
class Singer : virtual public Worker{…}
class Waiter : public virtual Worker{…}
然后,可以将SingingWaiter类定义为:
class SingingWaiter : public Singer, public Waiter{…}
12. P557-P558虚基类新的构造函数规则
C++在基类是虚的时,为避免信息传递冲突,禁止信息通过中间类自动传递给基类。然而,编译器必须在构造派生对象之前构造基类对象组件。如果没有显示指出构造函数,编译器将调用虚基类的默认构造函数。如果不希望通过默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数。
13. P558哪个方法
多重继承可能导致函数调用的二义性。可以使用作用域解析运算符来澄清编程者的意图。
SingingWaiter newhire(“Elise Hawks”, 2005, 6, soprano);
newhire.Singer::Show();
然而,更好的方法是在SingingWaiter中重新定义Show(),并指出要使用哪个Show()。
14. 混合使用虚基类和非虚基类
当类通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象。
15. 模板类以template <class Type>(或template <typename Type>)打头,如果在类声明外定义类方法,每个函数头还需加上模板前缀template <class Type>和类限定符(Stack<Type>::)。
16. 知道模板类不是类和成员函数定义至关重要。它们是C++编译器指令,说明了如何生成类和成员函数定义。不能将模板成员函数放在独立的实现文件中。由于模板不是函数,它们不能单独编译。为此,最简单的方法是将所有模板信息放在一个单独的头文件中,并在要使用这些模板的文件中包含该头文件。
17. P574-P575,在类声明中,都可以使用Stack;在类外,在模板函数定义内(包括形参)可以使用Stack,指定返回类型或使用作用域解析运算符时,必须使用完整的Stack<Type>。
18. template <class T, int n>中,T为类型参数,n为非类型参数或表达式参数。表达式参数有一些限制。表达式参数可以是整形、枚举、引用或指针。因此,double m是不合法的,double *rm和double *pm是合法的。另外,模版代码不能修改参数的值,也不能使用参数的地址。即不能使用n++或&n等表达式。另外,实例化模板时,用作表达式参数的值必须为常量表达式。
19. P578-P582模板多功能性(将模板类用作基类、组件类、或其他模板的类型参数)、递归使用模板、使用多个类型参数。
20. 模板的具体化
类模板和函数模板很相似,因为可以有隐式实例化、显式实例化和显式具体化,它们统称为具体化。
1) 隐式实例化:
ArrayTP<int, 100> stuff;
编译器在需要对象之前,不会生成类的隐式实例化:
ArrayTP<int, 100> *pt;//不需要对象
pt = new ArrayTP<double, 30>//需要对象
2) 显式实例化
template class ArrayTP<string, 100>;
在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。
3) 显式具体化
显式具体化是特定类型(用于替换模板中的泛型)的定义。
template <> class SortedArray(const char *);
21. P583部分具体化
//通用模板
template <class T1, class T2> class Pair {…};
//具体化T2为int
template <class T1> Pair<T1, int>{…};
关键字template后面的<>声明的是没有被具体化的类型参数,如果都被具体化,则<>为空,这将导致显式具体化。
22. P584成员模板:在模板类内声明模板类或函数模板
23. P586将模板用作模板参数
24. P588-P593模板类和友元
模板类声明也可以有友元。模板的友元分3类:
非模板友元,友元本身不是模板函数,而只是使用了隐式实例化的模板类作为参数。
约束模板友元,类的每一个具体化都获得匹配的友元,友元的类型取决于具体化的类型。在类声明外面声明友元函数模板。
非约束模板友元,友元的每一具体化都是类的所有具体化的友元。在类声明里面声明友元函数模板。