包含对象成员的类
如果一个类要使用另一个类的方法,需要将该类作为基类。但有时,两个类并没有直接联系,即只想使用某一种方法,而其他方法都没用,如果继承,就会出现新的问题。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数组的前四个
并且该类已经有很多方法以及重载运算符:
总的来说,类的成员可以是另一个类的实例化对象,并且可以使用该实例化对象的方法,但两者关系不是继承。也就是不用继承使用另一个类的方法。通常描述为-----类获得了其成员对象的实现,但没有继承接口。
之前的继承中,因为继承接口,所以可以调用基类的方法,其关系属于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;