第十四章.c++中的代码重用(P535-601)

包含对象成员的类

如果一个类要使用另一个类的方法,需要将该类作为基类。但有时,两个类并没有直接联系,即只想使用某一种方法,而其他方法都没用,如果继承,就会出现新的问题。c++可以将类对象作为类成员。
最常用的就是string类,存储字符串,以往的字符数组太麻烦。同理,如果是多个数字呢,c++库提供了这样的类---------------valarray
这个类的使用与之前的vector和array有一些相似之处:

#include<iostream>
#include<valarray>
using namespace std;
int main(){
	valarray<double> dou(5);
	for(int i = 0; i<5;i++){
		cout<<"please enter the "<<i+1<<" number of valarray dou:";
		cin>>dou[i];
	}
	cout<<"--------------------"<<endl;
	for(int i = 0; i<5;i++){
		cout<<"the "<<i+1<<"number of dou is "<<dou[i]<<endl;
	}
	return 0;
}

此时dou为一个类对象。声明类对象的方式有很多:

valarray<double> v1;   //数组长度为0
valarray<int> v2(8);   //数组长度为8
valarray<int> v3(10,8);  //数组长度为8,且每个值都为10
double s[5] = {3.1, 4.1, 5.1, 6.1, 5.5}
valarray<double> v4(s1,4) ;  //数组长度为4,取值为s1数组的前四个

并且该类已经有很多方法以及重载运算符:

  • operator:可以向数组一样访问各个元素
  • size():返回数组元素数
  • sum():返回所有元素之和
  • max():返回最大的元素
  • min():返回最小的元素

总的来说,类的成员可以是另一个类的实例化对象,并且可以使用该实例化对象的方法,但两者关系不是继承。也就是不用继承使用另一个类的方法。通常描述为-----类获得了其成员对象的实现,但没有继承接口
之前的继承中,因为继承接口,所以可以调用基类的方法,其关系属于is-a模型,即派生类“是”基类;只不过派生类是基类的一种特殊化,严格来说,依旧属于基类,因此是is-a模型;
但包括对象成员的类,没有继承接口,但能使用对象成员的方法,其关系称为has-a模型,即我有你的一部分,但我不属于你。我不是你的特殊化,只是通过你的对象使用了你的方法。

在类中重载运算符时,需要区分:

double & operator [] (int i);   //实现s[5] = 40.4;也就是赋值
double operator [] (int i) const ;  //实现 a = s[5];可以当作左值

私有继承

我们知道公有继承是is-a模型,而包括对象成员的类是has-a模型,而另一种has-a模型是 ------私有继承
使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员,也就是基类方法不再成为派生类对象共有接口的一部分,但派生类的成员函数可以使用它们。

假设,有一个类(Student)中有两个类对象,一个string,另一个valarray。这是上节讲到的包含对象成员的类。Student类中显式定义了这两个类的对象,然后再Student的成员函数中可以经过这两个对象调用其相应的类成员函数。

但,如果Student类私有继承String与valarray类,我们的派生类会产生一个基类的组件,也就是说,我们的派生类Student中会有两个从String与valarray中继承出来的对象,但没有未被命名。之前的包含对象成员的Student中是显式定义了这两个类,而现在如果是私有继承,我们不需要定义,会自动包括一个基类的对象,但没有名称。
因此,原来是通过显式使用String对象而调用其方法,而现在因为没有名称,因此需要使用类名加作用域运算符来调用String的方法,所以要将之前所有显式使用String类对象的地方都换为String::。
但如果是访问基类对象呢?以为没有名称,无法直接访问,但之前提到,派生类对象可以强制转换为基类对象,所以,可以将Student强制转换为String类,使用*this表示派生类的具体对象:

return (const string &) *this;

因为私有继承的派生类对象不能访问基类方法,所以也就无法直接通过调用基类方法修改基类数据。但可以再派生类的方法中调用基类方法,因此,如果需要私有派生类修改基类数据,需要再私有派生类的成员函数中调用基类方法,也就是架起基类数据与派生类方法之间的桥梁,这样派生类对象就可以调用其方法修改基类数据,其实现是在方法中调用了基类方法。

多重继承

有多个直接基类的类,这样看没什么特别的,但有个问题:
如果从AB类派生出A类和B类,然后利用从A和B共同派生出一个类C,此时便会出现一些问题。
最后派生的C类包含A类和B类组件,而A与B分别又包括了一个AB组件,所就是说,C类中将有两个AB组件,这在成员函数中使用将会产生严重的二义性,为了解决这个问题,c++引入了一个新技术----------虚基类
虚基类是的从多个类(基类相同)派生出的对象只继承一个基类对象,以上面的实例,最后的C类只会有一个AB类,不会出现二义性:

class A: virtual public AB{...};
class B: public virtual AB{...}; //virtual与public位置可互换

并且构造函数也需要新的方法,原本的继承,派生类构造函数调用基类构造函数,但上述示例中,会发现C类的构造函数在最终有两条路径调用基类AB的构造函数,这是显然不行的,在多重继承中两条路径的歧义这是非常常见的,因此多重继承中需要对成员函数进行很多的修改,使得赋值有唯一路径。比如,需要在A类增加两个新的赋值成员函数,第一个其中调用构造函数给基类AB数据赋值,第二个则单独给A类新增的数据赋值,这样把数据分开。B类也是如此,使得在最后C类中各个数据赋值有明确的单独基类函数。当然,这只是一个小问题,在多重继承中有很多问题需要注意的,只会在实际应用中才会发现。

类模板

之前使用过模板类valarray,vector和array,可以使用相同的类,只是类成员的类型不一样,本节介绍如何自己定义类模板。

template<typename type>

将类中所有标识符用type代替。
使用模板成员函数替换原有的类的类方法,每个函数头都将以相同的模板声明打头,还需要将类限定符改为类:

Student::show(...){...}
//替换为
template<class Type>
Student<Type>::show(...){...}

如果是内联定义,可以省略模板前缀。
要清楚,类模板不是类,也没有成员函数,只是c++编译器指令,说明了如何定义。模板的具体实现----实例化或者具体化。不能将模板成员函数放在独立的实现文件中,模板不是函数,不能单独编译,必须与相应的实例化请求在一起使用,所以最好将模板放在头文件中。
使用模板类:

Student<int> s1;

实例化模板类型可以是常规的类型,但有的类型会有问题,比如声明为指针:

Student<char *> s2;

本意是指针类型,因为指针比较特殊,模板类可能无法正确创建并实现其功能,所以需要在类模板代码中做修正。

如果要指定数组大小的简单数组模板,第一种方式是使用动态数组和构造函数参数提供元素数目。第二种就是使用模板参数来提供常规数组的大小。如array:

array<int,5> a1;

只需要修改模板声明加上该参数即可:

template<class T1, int n>

并且,模板具有多种功能:
1.递归使用模板

Arrayy<Array<int, 5>, 10> arr;

此时相当于创建了一个二维数组,使用递归的方法
2.使用多个类型参数:

template<class T1, class t2>
#include<iostream>
using namespace std;
template<class T1, class T2>
class Student{
	private:
		T1 a;
		T2 b;
	public:
		Student(const T1 &f, const T2 &s) : a(f), b(s){
		}
		T1 &first();
		T2 &second();
		T1 first()const{
			return a;
		}
		T2 second()const{
			return b;
		}
};

template<class T1, class T2>
T1 &Student<T1, T2>::first(){
	return a;
}
template<class T1, class T2>
T2 &Student<T1, T2>::second(){
	return b;
}

int main(){
	Student<string,int> s1[4]={
		Student<string,int>("kyle", 5),
		Student<string,int>("smith", 4),
		Student<string,int>("jack", 4),
		Student<string,int>("snack", 6)
	};
	for(int i=0; i<4; i++){
		cout<<s1[i].first()<<" : "<<s1[i].second()<<endl;
	}
	s1[3].first() = "acb";
	s1[3].second() = 16;
	cout<<"------------------------"<<endl;
		for(int i=0; i<4; i++){
		cout<<s1[i].first()<<" : "<<s1[i].second()<<endl;
	}
	return 0;
}

这样类可以保存两种值,并且可以为类型参数提供默认值:

template<class T1, class T2 = int>

这里与函数模板区分,函数模板不能为参数提供默认值。

模板的具体话

隐式实例化:声明一个或者多个对象,指出所需的类型,编译器通过模板提供的处方生成具体的类定义。
显式实例化:使用关键字template指出所需类型来声明类时,编译器将生成类声明的显式实例化:

template class ArrayTP<string,10>;

这样,虽然没有创建类对象,编译器也将生成类声明,类方法定义。
显式具体化:对于某些类型,不执行通用的模板,而执行我们声明的特殊模板。:

template<>
class Student<int, int>{   //表示两个都是int时,声明该类
private:

public:
	void show();
};


void Student<int, int>::show(){
	cout << "int!!!this is template faction!" << endl;
}

部分具体化:也可以只规定一个特殊的类型,注意位置,这里是设置的第二个,与顺序有关

template<class T1>
class Student<T1,int> {    //第二是int时,第一个无论
private:

public:
	void show();
};

template<class T1>
void Student<T1, int>::show() {
	cout << "!!!this is template faction!" << endl;
}
成员模板

模板可以用作结构,类或模板类的成员。

template<class T>
class Student{
	private:
		template<class V>
		class hold{
			private:
				V val;
			public:
				hold(V v):val(v){
				}
		};
		hold<T> q;
		hold<int> n;
	public:
		Student(T t, int i):q(t), n(i){
		}	
};

此时,模板类hold就是模板类Student的私有成员,同时,也可以将模板函数作为成员函数。
担忧的老编译器不支持这种成员模板,有的编译器对模板成员在类外还是类中定义有要求,所以根据编译器不同,格式不同。

将模板用作参数

前面我们知道,类成员可以是一个类对象,而类可以声明类模板来适合不同类型数据,所以,如果一个类中有两个类对象数据,且两个类对象数据是使用类模板定义的,所以我们就要传入类模板参数,也就是将模板用作参数:

template < template<typename T> class Thing>
class Student{
private:
	Thing<int> s1;
	Thing<double> s2;
...
}

其中Thing是参数,而前面的是类型,类型是一个类模板,所以声明时需要传入类模板:

Student<ARRy> S1;

此时ARRy是一个模板类,然后我们的模板类Student中两个私有成员将对应的类型int,double传入该ARRy模板中,定义相应类对象,然后作为Student的成员数据。等于模板类的嵌套。

模板类和友元

模板类声明友元,有三类:
1.非模板友元:

#include<iostream>
using namespace std;
template<class T> 
class Student{
	private:
		T item;
		static int num;
	public:
		Student(T i):item(i){
			num++;
		}
		~Student(){
			num--;
		}
		friend void display();
		friend void show(Student<T> &);
};
template<class T>
int Student<T>::num = 0;

void display(){
	cout<<"int items: "<<Student<int>::num<<", ";
	cout<<"double items: "<<Student<double>::num<<"."<<endl;
}
void show(Student<int> &s){
	cout<<"Student<int>: "<<s.item<<endl;
}
void show(Student<double> &s){
	cout<<"Student<double>: "<<s.item<<endl;
}

int main(){
 display();
 cout<<"---------------------"<<endl;
 Student<int> i1(10);
 display();
 show(i1);
 cout<<"----------------------"<<endl;
 Student<int> i2(5);
 display();
 show(i2);
 cout<<"----------------------"<<endl;
Student<double> d1(5.5);
 display();
 show(d1);
 cout<<"----------------------"<<endl;
 
 return 0;
 }
  • 上述代码模板类中有两个非模板友元
  • 模板类私有数据num为静态全局,即int类型公用一个num,double类型公用一个数据
  • 因为使用的是非模板友元,所以友元函数show要针对不同类型类对象,而定义不同类型函数,即函数重载,而编译时会有警告,警告此时模板类中的友元函数并非模板友元。
  • 此时,所有具体化的类公用一个友元函数

2.模板类的约束模板友元函数
使友元函数本身成为模板,为约束模板友元做准备,要使类的每一个具体化都获得与友元匹配的具体化:

#include<iostream>
using namespace std;

template<typename V>
void display();
template<typename D>
void show(D &);

template<class T> 
class Student{
	private:
		T item;
		static int num;
	public:
		Student(T i):item(i){
			num++;
		}
		~Student(){
			num--;
		}
		friend void display<T>();
		friend void show<>(Student<T> &);
};
template<class T>
int Student<T>::num = 0;

template<typename V>
void display(){
	cout<<" items: "<<Student<V>::num<<". ";
}
template<typename D>
void show(D &s){
	cout<<s.item<<endl;
}


int main(){
	Student<int> i1(10);
	Student<int> i2(20);
	display<int>();
	cout<<endl;
	show(i1);
	show(i2);
	cout<<"-----------------------"<<endl;
	Student<double> d1(5.5);
	display<double>();
	cout<<endl;
	show(d1);
 
 return 0;
 }
  • 在类外声明模板类,然后再类中具体化,每个类的具体化都有其对应的友元函数

3.模板类的非约束模板友元函数

就是将模板类放在类中声明,在类外定义

模板别名

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

typedef std::array<int,12> arrd;
typedef std::array<double,12> arri;
typedef std::array<int,5> arrf;

arrd ar;  //等于std::array<int, 12> ar;
arri ai;
arrf af;

c++11新增------使用模板提供一系列别名:

template<typename T>
	using arr = std::array<T,12>;

arr<double> ai;  //等于std::array<double, 12> ai;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值