写点什么

c++ primer -- 第 14 章 C++ 中代码重用

用户头像
Dreamer
关注
发布于: 2020 年 11 月 02 日
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 关系。


  1. 初始化被包含的对象


C++中类的构造函数中使用初始化成员列表的时候,注意,初始化顺序是**按照类的成员对象的声 明顺序,而不是按照初始化函数中的顺序。


  1. 使用包含对象的接口


被包含对象的接口不是公有的,但是可以在类方法中使用它

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 重新定义访问权限


保护派生或私有派生时,要使用基类的方法在派生类外面可用:

  1. 定一个调用基类方法的派生类

  2. 使用 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++引入新技术,虚基类


  1. 虚基类


虚基类从多个类(它们的基类型相同)派生出的对象只继承一个基类对象,此时需要加 virtual 声明


class Singer:virtual public Worker{...};

class Waiter:public virtual Worker{...};

然后定义 SingerWaiter 时,calss SingerWaiter:public Singer, public Waiter{...}.此时就只有一个 work 的副本。本质上来说就是 Singer 和 Waiter 共享共一个基类。


特别说明:


虚函数和虚基类之间不存在明显的联系。这种用法有点类似关键字重载。


至于为什么不摒弃将基类声明为虚的方式,而使虚行为称为 MI 的准则有这几个原因。


1. 在一些情况下,可能需要基类的多个拷贝

2. 将基类作为虚的要求程序完成额外的计算,为不需要的工具付出代价

  1. 新的构造函数规则


如果不希望默认的构造函数来构造虚基类对象。则需要显示的调用所需的基类构造函数

SingingWaiter (const Work & wk, int p = 0, int v = Singer::other): Worker(wk), Waiter(wk), Waiter(wk, p), Singer(wk, v){}


注意点


  1. 混合使用虚基类和非虚基类

  2. 虚基类和支配

关于支配,派生类的名称优先于直接或间接祖先类中的相同名称。


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

  1. 可以递归的使用模版

ArrayTP <ArrayTP<int, 5>, 10> twodee

与之等价的常规数组是int twodee[10][5],需要注意的是,在模版语法中,维顺序与等价的二维数组相反。


  1. 使用多个类型参数


模版可以包含多个类型参数,例如标准库中的 pair


  1. 默认类型模版参数


新特性之一就是可以为类型参数提供默认值。template <class T1, class T2 = int> class Topo {...}.在标准模版库中经常使用这个特性。注意,**可以为模版类型参数提供默认值,不能为模版函数模版提供默认值。


模版的具体化


类模版和函数模版很相似,因为可以隐式实例化,显式实例化和显示具体化,统称为 specialization.


  1. 隐式实例化


编译器在需要对象之前,不会对类的隐式实例化


Array<double, 30> *pt; // a pointer, object needed yet

pt = new ArrayTP<double, 30>; //now an object is needed

  1. 显式实例化


使用关键字 template,并指出所需类型来声明类template class ArrayTP<string, 100>; //generate ArrayTP<stringm 100> class.虽然没有创建或提及类对象,也会生成类声明。


  1. 显示具体化


具体化模版和通用模版斗鱼实例化请求匹配时,编译器将使用具体化版本。


//泛型的版本

template <typename T>

class SortedArray

{

...//details omitted

};

//具体化版本

template <> class SortedArray<const char char *>

{

...//details omitted

};


  1. 部分具体化


部分限制模版的通用性


//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. 模版类的非约束模版友元函数


通过类内部声明模版,可以创建非约束友元函数,即每个函数具体化都是类具体化的友元


用户头像

Dreamer

关注

一个不想做搜索的NLPer不是一个好的CVer 2019.12.18 加入

还未添加个人简介

评论

发布
暂无评论
c++ primer -- 第14章 C++中代码重用