C++ Primer Plus学习笔记-第十四章:C++中的代码重用

第十四章:C++中的代码重用

前言:本章将讨论包含对象成员的类,模板类和valarray,私有和保护继承,多重继承,虚基类,创建类模板,使用类模板和模板的具体化;另外,本章的所有内容全部都围绕一个核心——C++中的代码重用展开

1.包含对象成员的类

类中可以包含对象,比如前几章中就有在类对象中包含string类的例子;但现在我们将从实现的角度考虑这一现象;

valarray类是由头文件valarray支持的,这个类提供一个包含某种数据的序列,使用它创建一个对象时,需要在标识符valarray后面加上一对尖括号,包含序列的数据类型,然后再空格后给出序列的名称;就像这样:

valarray<int> q_values;
valarray<double> weights;

另外,还可以对这个类创建的对象进行初始化,以下是示例:

double gps[5] = {3.1, 3.5, 3.8, 2.9, 3.3};
valarray<double> v1;//不进行初始化
valarray<int> v2(8);//含有八个对象
valarray<int> v3(10,8);//含有八个对象,全部初始化为10
valarray<double> v4(gps, 4)
//创建了含有四个对象的序列,使用gps中的数据从前向后初始化

另外,创建valarray对象时可以使用列表初始化:

valarray<int> v5 = {20, 32, 17, 9};
//虽然我们没有指出序列长度,但会自动判断

最核心的是,valarray支持对其中的元素进行如下操作:

  • operator:使用下标访问其中的元素
  • size():返回包含的元素总数
  • sum():返回所有元素的总和
  • max():返回最大的元素
  • min():返回最小的元素

当类对象出现在某个类中时,后者不能自动获得前者的接口;

初始化这样一个包含了类的类时,需要首先使用初始化成员列表来初始化内部包含的类,初始化的顺序只和类在外围类中出现的顺序有关;另外如果省略初始化成员列表中初始化内部类的操作,实际上不会引起错误,因为编译器会自动调用默认构造函数;

2.私有继承

使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员;这意味着基类的方法将不会成为派生类对象公有接口的一部分,但可以在派生类的成员函数中使用它们,另外还可以通过创建一个调用私有方法的新公有方法来恢复几个可能特殊需要使用的接口;

使用私有继承,类将获得实现但不获得接口;而公有继承将同时让派生类获得接口和实现;

要使用私有继承,使用关键字private,实际上所有继承默认都是private的;另外,一个类可以同时继承多个类:

class student : private std::string, private std::valarray<double>
{
public:
	...
};

要获得基类的相关属性,可以在派生类中包含类对象,也可以使用继承的方法,两者导致初始化类对象时有一些差异;这是基于包含的初始化:

Student(const char * str,const double * pd, int n) : name(str), scores(pd, n) {}
//name和scores是类实例的名称

这是基于继承的初始化:

Student(const char * str, const double * pd, int n) : std::string(str), ArrayDb(pd, n) {}
//std::string和ArrayDb是两个类名
//ArrayDb是valarray<double>的别名

使用私有继承时,基类的方法只能在派生类内部使用,但我们有时希望基类工具是公有的,要实现这样的目的可以在派生类中创建一个新的公有方法,而这个公有方法是调用派生类内部私有的基类方法;

私有继承能使用类名和作用域解析运算符调用基类方法;但要使用基类本身怎么办?答案是使用强制类型转换,比如这样:

const string & Student::Name() const
{
	return (const string &) *this;
}
//将*this强制转换为string对象的引用并返回

用类名显式访问限定函数名不适合友元函数,因为友元函数不属于类成员;然而,可以通过显式强制类型转换来调用正确的友元函数;

成立的基础:友元函数往往用于类对象后置的运算符重载,识别的依据只是运算符两侧的数据类型,因此强制类型转换可以调用基于算符两侧特定数据类型的友元函数;

保护继承是私有继承的变体, 使用关键字protected;基类的公有成员和保护成员都成为派生类的保护成员,也即是说内部可访问,外界不可调用;与private不同的是,protected的继承不会导致接口在多层派生中,于派生类内部变得不可直接调用;

该表格阐述了三种继承对成员属性的影响:

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

3.多重继承

MI这一术语描述的是有多个直接基类的类;但当同一个类中同时通过继承得到两个相同的类时,要使用基类的方法会导致二义性问题,解决方法是创建一个指向某个基类的指针,然后把自身强制转换成那个指针对应的内容,缩小范围后将不存在二义性问题:

Worker * pw1 = (Waiter *) &ed;
Worker * pw2 = (Singer *) &ed;

另外,可以使用虚基类;实现方法是在继承时使用virtual关键字修饰基类;同时继承多个虚基类时,不会多次导入重复的基类的基类,而是只保留一个版本;而同时继承多个非虚基类时,重复的部分会机械的重复继承;

在构造函数的使用上,虚继承和非虚继承有不同点;前者的构造函数,需要在初始化成员参数列表中直接对单拷贝的基类初始化,而后者只需要对直接的基类在初始化成员参数列表中逐个调用构造函数;

如果使用了一个最近祖先类中具有的方法,单继承会自动调用最近祖先类的方法,但是多继承可能同一层的多个祖先类中都具有相同的方法,这将导致二义性问题,解决的办法是直接定义一个本层的方法,实现机制是调用某个祖先类的方法,或直接使用作用域解析运算符;

插播:C函数strchr(a,b)接收两个字符串,查找后者在前者第一次出现的位置并返回,如果没有出现则返回Null指针;

4.类模板

容器类是设计用来存储其它对象或数据类型的类;类模板可以帮助我们编写一个适配多种数据类型的类,在实例化时可以生成针对不同数据类型的实例;模板能够将参数名作为参数传递给接收方来建立类或函数;现在让我们深入探索这一提高类通用性的机制;

模板类的开头是:

template <class Type>
//Type是使用中可以指定的
//指定后类中所用用Type代替数据类型的地方都会使用指定的类型进行替换

下面看一个例子:

#ifndef STACKTP_H_
#define STACKTP_H_

template <class Type>
class Stack
{
private:
	enum {MAX = 10};
	Type items[MAX];
	int top;
public:
	Stack();
	bool isempty();
	bool isfull();
	bool push(const Type & item);
	bool pop(Type & item);
	
};

template <class Type>
Stack<Type>::Stacl()
{
	top = 0;
}

template <class Type>
bool Stack<Type>::isempty();
{
	return top == 0;
}

template <class Type>
bool Stack<Type>::isfull()
{
	return top == MAX;
}

template <class Type>
bool Stack<Type>::push(const Type & item)
{
	if (top < MAX)
	{
		items[top++] = item;
		return true;
	}
	else
		return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
	if (top > 0)
	{
		item = items[--pop];
		return true
	}
	else
		return false;
}

#endif

现在我们完成了一个模板类的开发,让我们真正动手创建两个模板类的实例:

Stack<int> kernels;
Stack<string> colonels;

但是并不是所有时候都非要显式的指出参数的数据类型,比如下面这个例子:

template <class T>
void simple(T t) {cout << t << '\n';}
...
simple(2);
simple("two");

我们在之前的章节中实现了一个名为Stack的栈,现在让我们用模板重新将这个类扩展为更通用的形式:

#ifndef STCKP1_H_
#define STCKP1_H_

template <class Type>
class Stack
{
private:
	enum {SIZE = 10};
	int Stacksize;
	Type * items;
	int top;
public:
	explicit Stack(int ss = SIZE);
	Stack(const Stack & st);
	~Stack() {delete [] items;}
	bool isempty() {return top == 0;}
	bool isfull() {return top == stacksize;}
	bool push(const Type & item);
	bool pop(Type & item);
	Stack & operator=(const Stack & st);
};
//下面的内容是对类方法的定义,可以在另一个文件中进行
template <class Type>
Stack<Type>::Stack(int ss)
{
	stacksize = ss;
	top = 0;
	items = new Type [stacksize];
}

template <class Type>
Stack<Type>::stack(const Stack & st)
{
	stacksize = st.stacksize;
	top = st.top;
	items = new Type [stacksize];
	for (int i = 0; i < top; i++)
		items[i] = st.items[i];
}

template <class Type>
bool Stack<Type>::push(const Type & item)
{
	if (top < stacksize)
	{
		items[top++] = item;
		return true;
	}
	else
		return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
	if (top > 0)
	{
		item = items[--top];
		return true;
	}
	else
		return false;
}

template <class Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{
	if (this == &st)
		return *this;
	delete [] items;
	stacksize = st.stacksize;
	top = st.top;
	items = new Type [stacksize];
	for (int i = 0;i < top; i++)
		items[i] = st.items[i];
	return *this;
}

#endif

另外模板支持的参数不止一个,下面这个例子就同时使用了两个参数:

#ifndef ARRAYTP_H_
#define ARRAYP_H_

#include <iostream>
#include <cstdlib>

template <class T,int n>
class ArrayTP
{
private:
	T at[n];
public:
	ArrayTP() {}
	explicit ArrayTP(const T & v);
	virtual T & operator[](int i);
	virtual T operator[](int i) const;
};

template<class T, int n>
ArrayTP<T,n>::ArrayTP(const T & v)
{
	for (int i = 0; 1 < n; i++)
		ar[i] = v;
}

template<class T, int n>
T & ArrayTP<T,n>::operator[](int i)
{
	if (i < 0 || i >= n)
	{
		std::cerr<<"Error in array limite:" << i << "is out of range\n";
		std::exit(	EXIT_FAILURE);
	}
	return ar[i];
}

template<class T, int n>
T ArrayTP<T, n>::operator[](int i)const
{
	if (i < 0 || i >= n)
	{
		std::cerr<<"Error in array limits:" << i << "is out of range\n";
		std::exit(EXIT_FAILURE);
	}
	return ar[i];
}

#endif

类模板和函数模板很相似,也可以有隐式实例化,显式实例化,显式具体化;

上述在生成类对象时才使用模板的操作是隐式实例化,而显式实例化则是主动生成类模板实例的:

template class ArrayTP<string,100>;
//声明必须位于模板定义所在的名称空间中
//但是这么做的意义不明,完全没有必要仅仅为了提高编译速度而引入新的特性

另外还可以对模板类进行显式具体化,具体操作是:

template <> class Classname<specialized-type-name> {...}

让我们看一个具体一点的例子,*比如你需要提供一个使用const char 的模板类:

template <> class SortedArray<const chat chat *>
{
	...//专门为这种数据类型写的代码
}

C++还允许进行部分具体化,也即是说类型列表中不是所有的参数类型都是特定的,具体而言是这样实现的:

template <class T1> class Pair<T1,int> {...}

也可以通过为指针提供特殊版本来部分具体化现有模板:

//普通版模板类
template<class T>
class Feeb {...};

//指针版模板类
template<class T>
class Feeb {...};

另外显式具体化还能用于设置各种限制,比如这样:

template <class T1, class T2, class T3> class Trio {...}
template <class T1, class T2> class Trio<T1, T2, T2> {...}
template <class T1> class Trio<T1, T1*, T1*> {...}
//第一个例子中template后的类型表已经提供了足够的信息,因此不需再补充
//后面两个例子template后的参数表没有提供足够的信息,因此再次进行参数表提供

模板可以用作结构,类或模板类的成员;下面是一个简单的类设计,从中我们可以看到这项特性的使用方法:

#include <iostream>
using std::cout;
using std::endl;

template <typename T>
{
private:
	template <typename V>
	class hold
	{
	private:
		V val;
	public:
		hold(V v = 0) : val(v) {}
		void show() const {cout << val <<endl;}
		V Value() const {return val;}
	};
	hold<T> q;
	hold<int> n;
public:
	beta(T t, int i) : q(t), n(i) {}
	template<typename U>
	U blab(U u, T t) {return (n.Value() + q.Value()) * u / t;}
	void Show() const {q.show;n.show;}
};

可以将模板用作参数,具体而言是这样的:

template <template <typename T> class Thing> 
class Crab
{...};

还可以混合使用模板参数和常规参数:

template <template <typename T> class Thing, typename U, typename V>
class crab
{...};

模板类声明 也可以有友元,模板的友元分三类:

  • 非模板友元
  • 约束模板友元,即友元的类型取决于类实例化时的类型
  • 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元

首先是非模板友元函数;在模板类中将一个常规函数声明为友元,其友元将成为所有实例的友元;另外可以修改使友元本身成为模板,实现的方法稍微复杂:

  1. 在类定义的前面声明每个模板函数
  2. 在类中的声明中将模板函数声明为友元

具体实现方式是:

template <typename T> void counts();
template <typename T> void report(T &);

template <typename TT>
class HasFriendT
{
...
	friend void counts<TT> {};
	friend void report<>(HasFriendT<TT> &);
};

在类的内部创建的是非约束友元函数,也就是说友元函数的实例化和类模板的实例化是可以独立开分别指定的:

template <typename T>
class ManyFriend
{
...
	template <typename C, typename D>friend void show2(C &, D &);
};

本章最后一个知识点是为类型指定别名,这个特性在C++11中首次得到支持;方法是:

using arraytype = std::array<T,12>;
//然后就可以这样使用自定义的名称:
arraytype<int> data;//实例化一个名为data的类实例
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值