14.1包含对象成员的类
当我们在描述has-a问题时,我们可以使用类里包含一个类的方法来描述这种问题。
例如,student类中有学生的姓名和分数,
我们可以在student类创建一个string类的name对象,和valarray类(这个类类似于数组,但是功能比数组强大)的scores对象
这种方法比较简单,可以直接在student类的成员函数里使用string类和valarray的方法(valarray用法)来实现相应的操作,但是这种方法也有其缺点,无法使用虚函数
14.2私有继承
另一种方法是使用私有继承。
使用私有继承,基类的公有成员和保护成员将称为派生类的私有成员,这意味着基类方法不会称为派生类对象的公有接口的一部分,但是派生类的成员函数里可以使用它
下面给出使用私有继承来实现student类的代码
1.基本框架
class student:private string,private valarray<double>
{
private:
typedef valarray<double> arrayDb;//这里用typedef简化一下
public:
};
基本框架如上,使用private关键字,由于成绩是浮点数,所以继承的是valarray<double>类,<double>指的是存储double类型数据。新的student类不需要提供私有数据,因为私有继承已经提供了两个无名称的子对象成员,这是与第一个方法的第一个主要区别
2.初始化基类组件
如果我们是students里包含着一个 stirng类的name对象,和valarray的scores对象
我们完全可以这样来初始化
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):string(str),arrayDb(pd,n){}
3.访问基类的方法
私有继承只能在派生类的成员函数中使用基类的方法,由于没有名称,所以要用类名和作用域解析运算符来调用基类的方法
double student::Average()const
{
if(arrayDb::size()>0)
{
return arrayDb::sum()/arrayDb::size();
}
else
{
return 0;
}
}
4.访问基类的对象
使用私有继承时,string对象没有名称,那么student类的代码如何访问内部的string对象呢?
答案是使用强制类型转换。
const string& student::Name()const
{
return (const string &)*this;
}
可以使用这个返回的引用来访问基类的对象
5.访问基类的友元函数
通过显式地转换位基类来调用正确的函数
ostream &operator <<(ostream &os,const student &stu)
{
os<<(const string &)stu<<endl;
}
显式地将stu转换为string对象引用,进而调用函数operator<<(ostream&os,const stirng&)
引用stu不会隐式的转换为string引用,因为私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用或指针。
14.2.2使用包含还是私有继承
通常使用包含来建立has-a关系,如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承
14.2.3保护继承
使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员,保护成员在类外无法使用。
14.2.4使用using重新定义访问权限
我们希望在派生类的成员函数中像基类一样访问成员函数,我们可以使用using声明
class student:private string,private valarray<double>
{
private:
typedef valarray<double> arrayDb;
using valarray<double>::min;
using valarray<double>::max;
public:
student(const char *str,const double *pd,int n):string(str),arrayDb(pd,n) {}
};
这样可以使min和max像student的公有方法一样,可以直接调用。
注意事项:using声明只适用于继承,并不适用于包含
14.3多重继承
多重继承意味着我们可以继承多次,只需要将继承的类用逗号分隔开即可
14.3.1有多少父类
但是可能存在下面这种情况
这种情况子类C中就会有两部分父类中的成员,这将引发一个问题,如下
D x;
A *p = &x;
//这时候会发生二义性问题,因为有两部分A的成员,p不知道该指向哪一个了
我们可以通过显式类型转换解决这个问题
D x;
A *p = (B*)&x;
A *q = (C*)&x;
虚基类
虚基类可以使得从多个类中派生出的对象只继承一个基类对象,例如,如果A是虚基类,那么D中只会有一部分A中的成员。
基类是虚基类的时候,C++禁止信息通过中间类自动传递给基类,并且虚基类的构造函数先于派生类的构造函数执行。
所以我们在最终派生类的构造函数中,可以直接在初始化列表里给虚基类的构造函数传值,这一点对于非虚基类是非法的
#include <iostream>
using namespace std;
class A
{
int a;
public:
A();
A(int aa):a(aa){}
void display()
{
cout<<a<<endl;
}
};
class B:virtual public A
{
int b;
public:
B();
B(int aa = 0,int bb = 0):A(aa),b(bb){}
};
class C:virtual public A
{
int c;
public:
C();
C(int aa = 0,int cc = 0):A(aa),c(cc){}
};
class D:public B,public C
{
public:
D();
D(int aa = 0,int bb = 0,int cc = 0):A(aa),B(aa,bb),C(aa,cc){}
};
int main()
{
D x(1,2,3);
x.display();
return 0;
}
其他的注意事项
当B类被用作C和D的虚基类,同时被用做X和Y的非虚基类,此时M类是从C、D、X和Y那派生来的,那么M类中有3个B部分成员
14.4类模板
类模板与函数模板类似,对于处理不同数据类型的类,我们可以给它编写一个泛型的栈,然后将具体类型作为参数传递给这个类。
模板提供了参数化类型,可以将类型名作为参数传递给接收方建立类或者函数,例如我们将int传递给Queue模板,Queue模板将创建一个对int类型进行排队的Queue类。
14.4.1定义类模板
使用template<class Type>来定义一个类模板
具体语法如下
template<class T>
class Stack
{
private:
const static int MAX = 10;//或 enum{MAX = 10}
T items[MAX];
int top;
public:
Stack();
};
template<class T>
Stack<T>::Stack()
{
......
}
需要在类的声明前和类的成员函数前加上template<class T>,作用域解析运算符前需要使用Stack<T>的形式
14.4.2使用模板类
仅在程序包含模板并不能生成没模板类,必须请求实例化
例如: Stack<int> fun Stack<long long> happy
看到声明后,编译器将按照Stack<T>模板来生成独立的类声明和两组独立的类方法,届时将用int来替换类里所有的T
14.4.5模板的多功能性
模板类可以作为基类,也可以作为组件类,还可以作为其他模板的类型参数。
1.递归使用模板
Array<Array<int,5>10> a;
就相当于
int a[10][5];
2.使用多个类型参数
可以使用多个类型参数,例如
template<class T,class V>
3.默认类型模板参数
template <class T = int>
class Stack
则可以这样做: Stack<> a;//创建一个int类型的栈
14.4.6模板的具体化
1.隐式实例化
模板类默认就是进行的隐式实例化,即编译器碰到创建具体类型对象时,才进行创建相应类型的类声明
如
Stack<int> a;//会创建int类型的栈的类
Stack<double> *p;//不会创建
p = new Stack<double> //会创建int类型的栈的类
2.显式实例化
使用关键字指出所需类型来声明类的时候,编译器将生成类声明的显式实例化,这种情况下,虽然没有创建和提及类对象,但是编译器也将生成类声明
如
template<class T= int>
class Stack
{
private:
const static int MAX = 10;
T items[MAX];
int top;
public:
Stack();
};
template class Stack<double>;//显式实例化
3.显式具体化
对于特定类型,模板中的某些地方需要修改,我们可以使用显式具体化,例如,比较字符串不能通过简单的重载运算符比较,而是要使用strcmp函数,这时候需要用到显示具体化
template<>
class Stack<const char* >
{
......
}
当编译器遇到char类型时,将使用这个具体化的模板,而不是泛式的模板
4.部分具体化
部分具体化即部分限制模板的通用性
1.给类型参数指以指定具体的类型
//隐式实例化
template<class T1,class T2>
class car
{
}
//部分显示具体化
template<class T1>
class car<T1,int>
{
}
这里注意,<>里放的是没有被具体化的参数,如果<>里为空,部分显式具体化就变成显式具体化了。
如果有多个模板可供选择,编译器将选择具体化程度最高的模板。
2.部分具体化的其他操作
template<class T1,class T2> car<T1,T2,T2>
{
........
}
template<class T1> car<T1,T1*,T1*>
{
........
}
14.4.7成员模板
模板可用做结构 类或模板类的成员。
如
template<class T>
class fun
{
private:
template<class V>
class happy
{
private:
V a;
public:
}
public:
fun();
};
很老的编译器不接受模板成员,而另一些编译器接受模板成员,但是不接受类外面的定义,如果你的编译器接受类外面的定义,则可以使用下列语法来在类外使用模板成员
#include <iostream>
using namespace std;
template<class T>
class fun
{
private:
template<class V>
class happy;
public:
fun();
};
template<class T>
template<class V>
class fun<T>::happy
{
private:
V a;
public:
happy(V aa);
};
template<class T>
template<class V>
class fun<T>::happy(V aa)
{
a = aa;
};
14.4.8将模板作为参数
模板可以包含类型参数(typename T)和非类型参数(int n 用于传数组元素个数),还可以包含模板的参数
如
#include <iostream>
using namespace std;
template <class T>
class king//一个模板类
{
};
template<template<class T> class Ting>
class crab
{
private:
Ting<int> s1;
Ting<double> s2;
public:
carb();
};
int main()
{
crab<king> s;
//king会通过Ting传进类内,于是 Ting<int> s1 -->King<int> s1;
//Ting<double> s2 -->King<double> s2
crab<stack> s2;
return 0;
}
未完待续