c++ primer -- 第 14 章 C++ 中代码重用
本章内容是对《C++primer》第六版,十四章的学习笔记,仅作为自己的学习总结而用。
本章内容包括:
has-a 关系
包含对象成员的类
模版类 valarray
私有和保护继承
多重继承
虚基类
创建类模版
使用类模版
模版的具体化
C++的一个主要目标就是代码重用。公有继承机制是实现这种目标的机制之一。还有包含,私有继承,保护继承着三种方式。
1 包含对象成员的类
类将数据声明为私有的,意味着类的成员函数可以使用 string 和 valarray<double>的公有接口来访问 name 和 score 对象。这种行为称为,类 Strudent 获得了成员对象的实现,但没有继承接口。
class Student
{
private:
string name; // use a string object for name
valarray<double> scores; // use a valarray<double> object for scores
...
};
使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供借口,但不提供实现),获得接口说 is-a 关系。而使用组合,类可以获得实现,但不能获得接口,是 has-a 关系。
初始化被包含的对象
C++中类的构造函数中使用初始化成员列表的时候,注意,初始化顺序是**按照类的成员对象的声 明顺序,而不是按照初始化函数中的顺序。
使用包含对象的接口
被包含对象的接口不是公有的,但是可以在类方法中使用它
2 私有继承
私有继承,基类的共有成员和保护成员都将成为派生类的私有成员,这意味着基类方法不会称为派生类对象共有接口的一部分。但可以在派生类的成员函数中使用。
小结:
共有继承 :is-a
私有继承: has-a
注意一点,多重继承(multiple inheritance)的时候,可能会导致一些问题,需要使用特定的语法规则来实现。
对比使用公有继承方式和私有继承方式实现初始化成员列表的区别:
//公有继承时
Student(const char str, const double pd, int n)
: name(str), scores(pd, n){}
//私有继承时
Student(const char str, const double pd, int n)
:std::string(str), ArrayDb(pd, n){}
私有继承时,只能使用类名,而不是成员名来构造。
访问基类的友元函数
私有继承中,子啊不进行显示类型转换的情况下,不能将指向派生类的引用或指针指向基类的引用或指针。而且在多重继承的时候没有显示类型转换的情况下,将导致递归调用。
保护继承
使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。基类的接口在派生类中是可用的。但在继承结构外无法使用。当派生类又派生一个类后,私有继承和保护继承的主要区别就出现了。使用私有继承时,第三代类将不能使用基类的接口。使用保护继承时,基类方法在第二代类中将变成受保护的。
各种继承方式表如下:
|特征|公有继承|保护继承|私有继承|
|------------|------------|------------|------------|
| 公有成员变成|派生类的公有成员|派生类的保护成员|派生类的私有成员|
|保护成员变成|派生类的保护成员|派生类的保护成员|派生类的私有成员|
|私有成员变成|派生类的私有成员|派生类的私有成员|派生类的私有成员|
|能否隐式向上转换|是|是(只在派生类中)|否|
使用 using 重新定义访问权限
保护派生或私有派生时,要使用基类的方法在派生类外面可用:
定一个调用基类方法的派生类
使用 using 声明,这种声明,只能在继承中使用。
3 多重继承
多重继承,使用时,必须使用关键字来界定。因为除非特别指出,否则编译器将认为是私有派生。
在多重继承的菱形继承结构中,将基类的指针设置为派生类对象中的基类对象的地址时,将包含两次基类的对象,此时用使用类型转换来制定对象。例如, class SingerWaiter:public Singer, public Waiter{...}
中,Singing 和 Waiter 都是 Worker 的派生类
解决方法:
Worker pw1 = (Waiter ) & ed; // the worker in Waiter
Worker pw2 = (Singer ) & ed; // the worker in Singer
这不是一个好的解决方案。这是 C++引入新技术,虚基类
虚基类
虚基类从多个类(它们的基类型相同)派生出的对象只继承一个基类对象,此时需要加 virtual 声明
class Singer:virtual public Worker{...};
class Waiter:public virtual Worker{...};
然后定义 SingerWaiter 时,calss SingerWaiter:public Singer, public Waiter{...}
.此时就只有一个 work 的副本。本质上来说就是 Singer 和 Waiter 共享共一个基类。
特别说明:
虚函数和虚基类之间不存在明显的联系。这种用法有点类似关键字重载。
至于为什么不摒弃将基类声明为虚的方式,而使虚行为称为 MI 的准则有这几个原因。
1. 在一些情况下,可能需要基类的多个拷贝
2. 将基类作为虚的要求程序完成额外的计算,为不需要的工具付出代价
新的构造函数规则
如果不希望默认的构造函数来构造虚基类对象。则需要显示的调用所需的基类构造函数
SingingWaiter (const Work & wk, int p = 0, int v = Singer::other): Worker(wk), Waiter(wk), Waiter(wk, p), Singer(wk, v){}
注意点
混合使用虚基类和非虚基类
虚基类和支配
关于支配,派生类的名称优先于直接或间接祖先类中的相同名称。
4 类模版
4.1 定义类模版
template <class Type>
,模版必须与特定的模版实例化请求一起使用。最简单的办法是将所有的模版信息放在一个头文件中,并在要使用这些模版的头文件中包含该头文件。
4.2 使用模版类
必须信啊是的提供所需的类型
4.3 深入的讨论模版类
可以将内置的类型或类对象用作类模版的类型。同时也可以使用类指针栈,但是如果不对程序做重大的修改,将无法很好的工作。
template <class T, int n>
.关键字 class 指出 T 为类型参数,int 指出 n 的类型为 int。这种参数为非类型或表达式参数。表达式参数有一些限制。表达式参数可以是整形,枚举或指针。double m 就是不合法的。但是 double rm, double rm,是合法的。另外模版代码不能修改参数的值,也不能使用参数的地址,所以不能使用 n++, &n。还有,实例化模版的时候,用作表达式参数的值必须是常量表达式
模版多功能性
可以将常规类的技术用于模版类。模版类可用作基类,也可以用作组建类,还有其他模版的类型参数。
template <typename T> // or <class T>
class Array
{
private:
T entry;
...
};
template <typename Type>
class GrowArray: public Array<type> {...}; // inheritance
template <typename Tp>
class Stack
{
Array<tp> ar; //use an Array<> as a component
...
};
Array <Stack<int> > asi; // an array of stacks of int
可以递归的使用模版
ArrayTP <ArrayTP<int, 5>, 10> twodee
与之等价的常规数组是int twodee[10][5]
,需要注意的是,在模版语法中,维顺序与等价的二维数组相反。
使用多个类型参数
模版可以包含多个类型参数,例如标准库中的 pair
默认类型模版参数
新特性之一就是可以为类型参数提供默认值。template <class T1, class T2 = int> class Topo {...}
.在标准模版库中经常使用这个特性。注意,**可以为模版类型参数提供默认值,不能为模版函数模版提供默认值。
模版的具体化
类模版和函数模版很相似,因为可以隐式实例化,显式实例化和显示具体化,统称为 specialization.
隐式实例化
编译器在需要对象之前,不会对类的隐式实例化
Array<double, 30> *pt; // a pointer, object needed yet
pt = new ArrayTP<double, 30>; //now an object is needed
显式实例化
使用关键字 template,并指出所需类型来声明类template class ArrayTP<string, 100>; //generate ArrayTP<stringm 100> class
.虽然没有创建或提及类对象,也会生成类声明。
显示具体化
具体化模版和通用模版斗鱼实例化请求匹配时,编译器将使用具体化版本。
//泛型的版本
template <typename T>
class SortedArray
{
...//details omitted
};
//具体化版本
template <> class SortedArray<const char char *>
{
...//details omitted
};
部分具体化
部分限制模版的通用性
//general template
template <calss T1, class T2> class Pair {...}
//specilization with T2 set to int
template <class T1> class Pair<T1, int> {...}
关键字 template 后面的<>声明的是没有被具体化的类型参数。如果制定了所有的类型,则<>为空。
//specialization with T1 and T2 set to int
template <> class Pait<int, int> {...};
如果多个模版可供选择,编译器将使用具体化程度最高的模版
Pair<double, double> p1; // use general Pair template
Pair<double, int> p2; // use Pair<T1, int> partial specialization
Pair<int, int> p3; // use Pair<int, int> explicit specialization
成员模版
模版可以用作结构,类,模版类的成员
template <typename T>
template <typename U>
U beta<T>::blab(U u, T t)
{
return (n.value() + q.value())* u / t;
}
将模版用作参数
模版类和友元
模版类也可以有友元。模版的友元友三种:
非模版友元
约束(bound)模版友元,即友元的类型取决于类被实例化时的类型
非约束(unbound)模版友元,友元的所有具体化都是类的每一个具体化的友元
1. 模版类的非模版友元函数
template <class T>
class HasFriend
{
public:
friend void counts(); // friend to all HasFriend instantiations
...
};
2. 模版类的约束模版友元函数
首先,在类定义的前面声明每个模版函数,然后在函数中在此将模版声明为友元
3. 模版类的非约束模版友元函数
通过类内部声明模版,可以创建非约束友元函数,即每个函数具体化都是类具体化的友元
评论