【C++】代码重用(valarray、私有继承、多重继承、类模板、具体化、模板别名)

C++促进代码重用:公有继承;包含、组合或层次化(类成员本身是另一个类的对象)

valarray类简介

valarray类是由头文件valarray支持的。这个类用于处理数值(或具有类似特性的类),它支持诸如将数组中所有元素的值相加以及在数组中找出最大和最小的值等操作。其构造函数的几个例子:

double gpa[5] = { 3.1, 3.5, 5.3, 5.5, 7.2 };
valarray <double> v1; //an array of double , size 0
valarray <int> v2(8); //an array of 8 int elements
valarray <int> v3(10,8); //an array of 8 int elements, each set to 10
valarray <double> v4(gpa, 4); //an array of 4 elements, initialized to the first 4 elements of gpa

在C++11中,也可使用初始化列表:
valarray <double> v5 = { 20, 43, 16, 5, 53 }; //C++11

该类的一些方法:
operator[]():访问各个元素
size():返回包含的元素数
sum():返回所有元素的总和
max():返回最大的元素
min():返回最小的元素

valarray被定义为一个模板类,一遍能够处理不同的数据类型;模板特性意味着声明对象时,必须指定具体的数据类型。

  1. C++包含让程序员能够限制程序结构的特性—使用explicit防止但参数构造函数的隐式转换,使用const限制方法修改数据等等。这样做的原因是:在编译阶段出现错误优于在运行阶段出现错误。
  2. 对于继承的对象,构造函数在成员初始化列表中使用类名来调用特定的基类构造函数。对于成员对象,构造函数则使用成员名。
  3. 初始化顺序
    当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。例如,假设Student构造函数如下:
    Student (const char*str, const double *pd, int n): scores(pd, n ), name(str) { }
    则 name 成员仍将首先被初始化,因为在类定义中它首先被声明。对这个例子来说初始化顺序并不重要,但如果代码使用一个成员的值作为另一个成员的初始化表达式的一部分时,初始化顺序就非常重要了。

私有继承

包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。因此私有继承提供的特性与包含相同:获得实现,但不获得接口。总之,使用包含时将使用对象名来调用方法,而使用私有继承时将使用类名和作用域解析运算符来调用方法。

通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。

各种继承方式

特征公有继承保护继承私有继承
公有成员变成派生类的公有成员派生类的保护成员派生类的私有成员
保护成员变成派生类的保护成员派生类的保护成员派生类的私有成员
私有成员变成只能通过基类接口访问只能通过基类接口访问只能通过基类接口访问
能否隐式向上转换是(但只能在派生类中)

隐式向上转换意味着无需进行显式类型转换,就可以将基类指针或引用指向派生类对象。

多重继承

  1. 虚基类
    虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。格式为在类声明中使用关键字virtual
    class Singer : virtual public Worker { … };
    C++在基类是虚的时候,禁止信息通过中间类自动传递给基类。如果不希望默认构造函数来构造虚基类,则需要显式地调用所需的基类构造函数。如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。
  2. 多次继承可能导致函数调用的二义性。可以使用作用域解析运算符来判别,更好的方法是在派生类中重新定义函数。
  3. 在祖先相同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则。
  4. C-风格字符串库函数strchr()
    while (strchr (“wstq”, choice)==NULL)
    该函数返回参数choice指定的字符在字符串 ”wstq” 中第一次出现的地址,如果没有这样的字符,则返回NULL指针。
  5. 当类通过多条虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象。
    若某个成员名优先于其他的,则不会导致二义性。即派生类中的名称优先于直接或间接祖先类中的相同名称。
  6. 小结 如果一个类从两个不同的类那里继承了两个同名的成员,则需要在派生类中使用类限定符来区分它们。如果一个类通过多种途径继承了一个非虚基类,则该类从每种途径分别继承非虚基类的一个实例。
  7. 当派生类使用关键字virtual来指示派生时,基类就成了虚基类:
    class marketing : public virtual reality { … };
    其主要变化(使用虚基类的原因)是,从虚基类的一个或多个实例派生而来的类将只继承了一个基类对象。为实现这种特性,必须满足:
  • 有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说是非法的。
  • 通过优先规则解决名称二义性。

类模板

模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数。

定义类模板

template <typename Type> 其中typename也可为class,可以使用自己的泛型名代替Type,其命名规则与其他标识符相同。当模板被调用时,Type将被具体的类型值取代。

由于模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。为此,需要声明一个类型为模板类的对象,方法是使用所需的具体类型替换泛型名。

指针栈的使用方法之一:让调用程序提供一个指针数组,其中每个指针都指向不同的字符串。注意:创建不同指针是调用程序的职责,而不是栈的职责。栈的任务是管理指针,而不是创建指针。

一个允许指定数组大小的简单数组模板:一种方法是在类中使用动态数组和构造函数参数来提供元素数目;另一种方法是使用模板参数来提供常规数组的大小,C++11新增的模板array就是这样做的。

template <class T, int n>
关键字class(或typename)指出T为类型参数,int指出n的类型为int。这种参数(指定特殊的类型而不是用作泛型名)称为非类型或表达式参数。

构造函数方法使用的是通过newdelete管理的堆内存,而表达式参数方法使用的是为自动变量维护的内存栈。

模板多功能性:模板类可用作基类,也可用作组件类,还可用作其他模板的类型参数。
递归使用模板,例:
ArrayTP<ArrayTP<int , 5>, 10> twodee; //这使得twodee是一个包含10个元素的数组,其中每个元素都是一个包含5个int元素的数组,与之等价的二维数组声明如下: int twodee[10][5];
默认类型模板参数:即可以为类型参数提供默认值,
template <class T1, class T2 = int> class Topo { … };
若省略T2的值,编译器将使用int:
Topo<double, double >m1; //T1 is double , T2 is double
Topo<double>m2;//T1 is double ,T2 is int
虽然可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。然而,可以为非类型参数提供默认值,这对于类模板和函数模板都是适用的。

模板的具体化

显式实例化

关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板定义所在的名称空间中。如:
template class ArrayTP<string, 100>;//generate ArrayTP<string, 100> class
其将ArrayTP<string, 100>声明为一个类,在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。

显式具体化是特定类型(用于替换模板中的范型)的定义。当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。具体化类模板定义的格式如下:

template <> class Classname<specialized-type-name> {}
部分具体化

即部分限制模板的通用性。例如,部分具体化可以给类型参数之一指定具体的类型:

// general template
template <class T1, class T2> class Pair {}
// specialization with T2 set to int
template <class T1> class Pair<T1, int> {}

关键字template后面的<>声明的是没有被具体化的类型参数。即第二个声明把T2具体化为int,但T1保持不变。

新的特性

模板可用作结构、类或模板类的成员,模板还可以包含本身就是模板的参数,这种参数是模板新增的特性。
可以混合使用模板参数和常规参数,例如,Crab类的声明可以这样:

template <template <typename T> class Thing, typename U, typename V>
Class Crab
{
private:
    Thing<U> s1;
    Thing<V> s2;
    ……
}

现在,成员s1和s2可存储的类型为泛型,而不是用硬编码指定的类型。模板参数T表示一种模板类型,而类型参数U和V表示非模板类型。

模板类和友元

模板类声明也可以有友元。模板的友元分3类

  • 非模板友元
  • 约束模板友元,即友元的类型取决于类被实例化时的类型
    使友元函数本身成为模板,即为约束模板友元做准备,要使类的每一个具体化都获得与友元匹配的具体化包含以下三步:
  1. 首先,在类定义的前面声明每个模板参数。
Template <typename T> void counts();
Template <typename T> void report( T &);
  1. 然后,在函数中再次将模板声明为友元。这些语句根据类模板参数的类型声明具体化:
template <typename TT>
class HasFriendT
{friend void vounts<TT>();
  friend void report<>(HasFriendT<TT> &);
};
  1. 声明中的<>指出这是模板具体化
  • 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元。通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:
template <typename T>
class ManyFriend
{template <typename C, typename D> friend void show2(C &, D &);
};

模板别名

可使用typedef为模板具体化指定别名:

typedef std::array<double, 12> arrd;
arrd gallons; // gallons is type std::array<double, 12>

//C++11新增的功能—使用模板提供一系列别名,如:
tmplate<typename T>
uing arrtype = std::array<T, 12>; //template to create multiple aliases
//这将arrtype定义为一个模板别名,可用它来指定类型:
arrype <std::string>months; //months is type std::array<std::string, 12>

总之,arrtype表示类型std::array<T, 12>
C++11允许将语法using =用于非模板,用于非模板时与常规typedef等价:

typedef const char* pc1; //typedef syntax
using pc2 = const char *; //using = syntax

C++11新增的另一项模板功能时可变参数模板,能够定义这样的模板类和模板函数,即可接受可变数量的参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhugenmi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值