📔 第 14 章 C++中的代码重用 学习笔记
👉【复习题】【编程练习题】¶
C++的主要目标:促进代码重用。通过公有继承来实现这种目标机制。
1. 包含对象成员的类¶
通过动态内存分配来表示数组,使得数组长度不被限制。
1.1 valarray类简介¶
valarrat类
由头文件 valarray
支持。valarray被定义为一个模板类,以能够处理不同类型的数据类型。
模板特性意味着声明对象时,必须指定具体的数据类型。所以。valarray类来声明一个对象时,需要在标识符valarray
后面加上一对尖括号
,并在其中包含所需的数据类型。
可以创建长度为0的数组、指定长度的空数组、所有元素被初始化为指定值的数组、用常规数组中的值来进行初始化的数组。
valarray类的一些方法:
- operator[]()
:访问各个元素
- size()
:返回包含的元素个数
- sum()
:返回所有元素的总和
- max()
:返回最大的元素
- min()
:返回最小的元素
1.2 Student类的设计¶
学生和其它类之间不是 is-a关系
,而是 has-a关系
。学生有姓名和一组考试分数。
has-a 关系
的C++技术是组合(包含),即创建一个包含其他类对象的类。
1.3 接口和实现¶
- 使用
公有继承
时,类可以继承接口
。还会有实现
(基类的纯虚函数提供接口,但不提供实现) -
获得接口
是is-a
关系的组成部分。 -
使用
组合
,类可以获得实现,但不能获得接口。 不继承接口
是has-a
关系的组成部分has-a
关系上,类对象不能自动获得被包含对象的接口。
1.4 C++和约束¶
C++包含让程序员限制程序结构的特性 ---- 使用 explicit 防止单参数构造函数的隐式转换。使用 const限制方法修改数据等。
原因:在编译阶段出现错误优于在运行阶段出现错误。
2. 私有继承¶
实现 has-a
关系的另外一个途径 ---- 私有继承
。
使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。意味着基类方法将不会成为派生对象公有接口的一部分,但可在派生类的成员函数中使用。
派生类不基础基类的接口。
2.1 私有继承和组合的异同¶
- 相同点
- 私有继承
- 获得实现,但不获得接口。
- 将对象作为一个未被命名的继承对象添加到类中。
-
组合
- 将对象作为一个命名的成员对象添加到类中。
-
不同点
- 类将继承实现
- 组合提供两个被显式命名的对象成员。
- 私有继承提供两个无名称的子对象成员。
- 组合使用对象名来调用方法
- 私有继承使用类名 和 作用域解析运算符来调用方法。
要进行私有继承,使用关键字 private
而不是 public
来定义类。
2.2 初始化基类组件¶
隐式继承组件而不是成员对象,会影响代码的编写。所以使用公有继承。
2.3 访问基类的方法¶
使用私有继承时,只能在派生类的方法中使用基类的方法。
- 在私有继承使得使用
类名
和作用域解析运算符
来调用基类的方法
- 组合是直接使用对象名来调用方法。
2.4 访问基类对象¶
通过强制类型转换
。将Student对象转换为string对象。
指针this
指向用来调用方法的对象,因此 *this
为用来调用方法的对象。
2.5 访问基类的友元函数¶
用类名显式地限定函数名不适合友元函数。 友元不属于类,但是可以通过显式转换为基类来调用正确的函数。
2.5 使用组合还是私有继承¶
多数选择程序员选择使用 组合
。原因:
- 易于理解。
- 类声明中组合表示被包含类的显式命名对象。代码可以通过名称引用对象
- 继承会使得关系更抽象难懂。
- 继承会引起很多问题
- 组合可以包含多个同类的子对象。
- 继承只能使用一个对象(当对象都没有名称时,会难以区分)。
通常情况下:
- 应使用组合来建立 has-a
关系
- 如果新类需要访问原有类的保护成员
,或者需要重新定义虚函数
,则应使用私有继承
。
3. 保护继承¶
保护继承是私有继承的变体。
在基类中使用 关键字protected
来进行声明。
- 使用
私有继承
时,第三代类将不能使用基类的接口。 - 基类的公有方法在派生类中将变成私有方法。
- 使用
保护继承
时,基类的公有方法在第二代中将变成受保护的,所以第三代派生类可以使用。
4. 使用using重新定义访问权限¶
使用保护派生或私有派生时,基类的公有成员将成为保护成员或私有成员。
让基类的方法在派生类外可用的两种方式:
- 定义一个使用该基类方法的派生类方法
- 将函数调用包封装在另一个函数调用中。通过using声明
来指出派生类可以使用特定的基类成员。(类似 using namespace std; 名次空间的方式)
using
声明只使用成员名
---- 没有圆括号、函数特征标和返回类型
。
5. 多重继承(MI)¶
使用多个基类的继承被称为 多重继承(multiple inheritance,MI)
。
MI描述的是is-a
关系。
MI必须使用 关键字public
来限定每一个基类。原因:非特别指出,编译会认为是私有派生。
例子1 : 代码
5.1 MI可能带来的问题¶
- 从两个不同的基类基础同名方法
- 从两个或者更多基类那里继承了同一个类的多个实例
5.2 程序worker的分析¶
把基类指针设置为派生对象中的基类对象的地址,有两种地址可选择,应使用类型转换来指定对象。
- 虚基类
- 虚基类使得从多个类(基类相同)派生出的对象只继承一个基类对象。在类声明中使用关键字
virtual
来指示派生时,基类则成为虚基类
。 - C++在基类是虚的时,禁止信息通过中间类自动传递给基类。
- 如果不希望
默认构造函数
来构造虚基类对象
,则需要显式地调用所需的基类构造函数。
如果类有间接虚基类,则除非只需要使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。
在多重继承中,每个直接祖先都有一个相同的函数
(如Show()),使得派生类调用此函数时存在二义性
。此时需要使用作用域解析运算
符来澄清编程者的意图。
- 如果基类是虚基类,派生类将包含基类的一个子对象。
- 如果基类不是虚基类,派生类将包含多个子对象。
如果类从不同的类哪里继承了两个或者更多的同名成员(数据或方法)则使用该成员名时,通过使用虚基类,某个名称优先于其他所有名称,则使用,如果不使用限定符,也不会导致二义性。
6. 类模板¶
C++模板提供参数化(parameterised)类型:能够让类型名作为参数传递给接收方来建立类或函数。
6.1 定义类模板¶
模板类的代码定义:
-template
告诉编译器,将要定义一个模板,尖括号中的内容相当于函数的参数列表。
- 关键字 class
看作变量的类型名,变量接受类型作为其值,把Type看作是该变量的名称。
- Type 表示一个通用的类型说明符。在使用模板时,使用实际的类型替换它。
如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。
模板类不是函数,不能单独编译。
模板必须和特定的模板实例化
请求一起使用。一般是使将所有模板信息放在一个头文件
中。
6.2 使用模板类¶
声明模板类的对象
,必须显式地提供所需的类型
。
6.3 数组模板和非类型参数¶
类型参数
,int指出n的类型。这种参数称为非类型(non-type)
或表达式参数
。
- 表达式参数可以是整型
、枚举
、引用
或指针
。
模板代码不能修改参数的值,也不能使用参数的地址。
实例化模板时,用做表达式参数的值必须是常量表达式
(#define
或者const
定义的常数)。
- 表达式参数的优缺点
- 优点
- 构造函数使用new和delete来管理堆内存,但它为自动变量维护的内存栈,使得执行速度更快。
- 缺点
- 每种数组大小都会生成自己的模板。
6.4 模板多功能性¶
模板类的三个功能: - 用作基类
- 用作组件类
- 模板可以包含多个类型参数。
- 默认类型模板参数
- 可以为类型参数提供默认值
- 不能为
函数模板参数
提供默认值。但可为非类型参数
提供默认值。
6.5 模板的具体化¶
隐式实例化
、显式实例化
和显式具体化
,统称为 具体化
。
模板
使用 泛型
的方式描述类
,具体化
是使用具体化的类型
生成类声明
。
- 隐式实例化
-
声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义。
- 显式实例化 - 当使用关键字 template 并指出所需类型来声明类时,编译器将生成类声明的显式实例化。 - 声明必须位于模板定义所在的名称空间中。 -
显式具体化
-
格式如下:
-
部分具体化
- 部分限制模板的通用性。
- 关键字
template
后面的<>声明
的是没有被具体化的类型参数。 - 如果多个模板可供选择,编译器将优先使用具体化程度最高的模板。
- 如果提供的类型不是指针,则编译器将使用通用版本。
- 日过提供的是指针,则编译器将使用指针具体化版本
6.6 成员模板¶
模板可用作结构、类或模板类的成员。
6.7 使用模板作为参数¶
模板可以包含 类型参数(typename T) 和 非类型参数(int n)。
示例:
- 模板参数是 template6.8 模板类和友元¶
模板类声明也可以友元。模板的友元分为3类: - 非模板友元 - 在模板类中声明的一个常规友元函数,称为模板所有实例化的友元。
- 当为友元函数提供模板类参数,必须指明具体化。类外声明
的模板具体化
。
- 2个步骤:
- 在类定义的前面声明每个模板函数
- 在函数中再次将模板声明为友元。根据类模板参数的类型声明具体化。
- 非约束模板友元:友元的所有具体化都是类的每一个具体化的友元。
- 在
类内部
声明模板,可以创建非约束友元函数
。
6.9 模板别名(C++11)¶
-
使用
typedef
为模板具体化指定别名 -
使用
using name = type
于非模板,此时与常规的typedef等价。