- 实现代码重用的一些方法(这里并不是全部):
- 包含(组合、层次化):类包含另一个类的对象
- 使用私有继承或保护继承
- 以上两种方法都用于实现has-a关系,常用第一种方法
- 多重继承可以使多个基类派生出一个新类,将基类的功能组合到一起
包含对象成员的类
- 公有继承实现is-a模型,没有增加新接口,可增加新实现
- 包含(组合、层次化)、私有继承、保护继承实现has-a模型,没有增加新接口,可增加新实现;
- 接口:直接暴露在外的函数;实现:函数定义;注意:这两个概念不是互斥的
私有继承
要区分开 类成员的访问类型 和 类的继承类型 之间的关系 类成员的访问类型 决定 类成员的可访问性和可继承性:
访问类型 | 外部可访问性 | 可继承性 |
---|---|---|
public | 可访问 | 可继承 |
protect | 不可访问 | 可继承 |
private | 不可访问 | 不可继承 |
类的继承类型 决定 继承下来的成员 的访问类型会变成什么:
继承类型 | 变化 |
---|---|
公有继承 | 不改变继承下来内容的访问类型 |
保护继承 | 将继承下来的内容的访问类型都变为protect |
私有继承 | 将继承下来的内容的访问类型都变为private |
派生类的指针(引用)要赋值给私有继承下来的基类类型指针(引用),必要强转派生类的指针(引用)
包含将对象作为一个有名字的成员添加到类中,私有继承将一个没有名字的成员添加到类中;这些成员称为子对象(subobject)
私有继承是默认继承方式
访问基类成员的格式:类名::基类成员
ps:友元函数不是类成员
若要直接获得子对象,则只需要对this(*this)进行强制类型转换即可,转换成你想要的子对象类型的指针(引用)
- 包含 与 私有继承 实现has-a关系的对比:
- 包含易于理解,可包含多个同类子对象,不可访问子对象的保护成员、不可重新定义虚函数
- 私有继承比较抽象,不易包含多个同类子对象,容易出问题(多重继承);但是特性多:可访问子对象的保护成员、可重新定义虚函数
- 一般来说,能用包含就用包含,实在不行再用私有继承
- 如果想要改变私有继承(或保护继承(可以吗?))下来的基类成员的访问属性,可以使用using关键字,将那些成员强行引入到特性的访问区块:
class Student : private std::string, private std::valarray<double>
{
...
public:
//使用using将那些继承下来的成员强行引入到public中,min和max函数的访问属性由private改成了public
using std::valarray<double>::min;
using std::valarray<double>::max;
//注意:using声明只需要成员名
//实现这个功能的一种更老的方法,但即将停用
//std::valarray<double>::min;
...
};
多重继承
多重继承(MI):有多个直接基类的类,每一个基类都要写上自己的继承属性
以下面的继承关系图为例介绍MI:
如果上图中所有的继承关系都是公有继承,那么SingingWaiter实例就会有两个Worker基类对象:
多重继承引入的问题
- 派生类对象(SingingWaiter)指针(引用)无法自动上转为相同基类类型(Worker)的指针(引用)
- 解决方法:手动强转下派生类对象(SingingWaiter)指针(引用)到上一级基类(Singer或Waiter)就好了
- 从不同的基类继承同名的方法
- 解决方法:可在调用同名函数时,通过类名指定
从不同继承路线上继承相同的基类
为了删除多个相同基类,有点像融合,C++引入了新技术— 虚基类 ;
要指定哪条继承线的相同基类进行融合,则继承这个相同类(Worker)的直接派生类(Singer和Waiter)都要在继承时加上virtual关键字:
class Singer : public virtual Worker{};
class Waiter : virtual public Worker{};
然后派生类(SingingWaiter)的基类对象情况就会变成这样:
引入虚基类的多重继承需要解决的问题
构造函数的问题
只能在类(SingingWaiter)在构造函数 初始化列表 中调用虚基类(Worker)的构造函数,类(SingingWaiter)的所有基类(Singer和Waiter)都不能在构造函数 初始化列表 中调用虚基类(Worker)的构造函数;
只能由类(SingingWaiter)来 自动 调用虚基类(Worker)的构造函数,类(SingingWaiter)的所有基类(Singer和Waiter)都不能再 自动 调用虚基类(Worker)的构造函数;
总的来说就是:类(SingingWaiter)的所有基类(Singer和Waiter)已无权调用虚基类(Worker)的构造函数,只能由类(SingingWaiter)调用虚基类(Worker)的构造函数;
虚基类中虚函数的问题
如果一个类(SingingWaiter)继承了虚基类(Worker)但是没有实现虚基类(Worker)的虚函数vfun,会出现下面两种情况:
如果有两个以上 虚基类(Worker)的派生类(Singer和Waiter) 都实现了虚基类(Worker)的虚函数vfun;这个时候如果通过这个类(SingingWaiter)的对象指针去调用虚函数vfun,那么将出现二义性;因为虚函数从虚基类(Worker)开始往下走会有两个最新版本出现;因此这个类(SingingWaiter)最好重写(override)虚函数vfun,否则多态性就会异常;
如果只有一个 虚基类(Worker)的派生类(Singer或Waiter) 实现了虚基类(Worker)的虚函数vfun;这个时候如果通过这个类(SingingWaiter)的对象指针去调用虚函数vfun,那么不会有任何问题;
重写虚函数的问题
在重写(override)虚基类(Worker)的虚函数vfun时最好不要以 递增 的方式去写;所谓的 递增 指的是:在重写(override)的时候调用了基类的虚函数vfun;
后记
虚函数的调用规则不受访问类型的影响,该调用哪个就调用哪个,如果调到了私有函数或者保护函数,那运行时就报错;
基类普通函数的调用规则也不受访问类型的影响,如果基类出现两个同名普通函数函数,即使一个是私有的、一个是公有的,那么一样存在调用的二义性;
类模板
类模板的声明与定义
//T是类型参数
//typename和class是等价的,但是class是C++98之前的版本
template <typename T>
class A
{
public:
void fun1(){ //do something }
void fun2();
A<T> fun3(){ //do something }
A fun4();
//注意:fun3和fun4的返回类型其实是等价的,没有差别;在类的块中,所有的A<T>和A都是等价的
};
//如果函数定义与函数声明分开,那么定义格式如下:
template <typename T>
void A<T>::fun2()
{
//do something
}
template <typename T>
A<T> A<T>::fun4() //注意:返回值的写法与函数原型不同,因为在类的块外面
{
//do something
}
//跟函数模板一样,以上只是类模板,机器码中不会包含类模板的信息;编译器会根据类模板来生成相应类定义机器码
//注意:类模板函数声明与定义不能放在两个独立的文件中
类模板的使用
A<int> test1;
A<string> test2
//注意:类型参数必须要手动确定,编译器不会像函数模板一样自动识别
数组模板实例和非类型参数
非类型(non-type)参数or表达式(expression)参数:
//数组模板
template <class T,int n> //n就是非类型参数;看起来像是一个变量,但我觉得是一个可以指定类型的宏定义,编译生成具体模板实例的时候,类成员的声明和定义中的n标识符将全部被替换为n的具体数值
class ArrayTP
{
private:
T ar[n]; //由于是宏定义,所以编译时可以确定数组大小
public:
...
void set(){};
};
template <class T, int n>
void ArrayTP<T,n>::set()
{
for(int i=0;i<n;++i)
ar[i]=i;
}
int main()
{
...
ArrayTP<double,12> example1;
ArrayTP<double,20> example2;
//注意:上面将生产两个模板实例,因为非类型参数不同
example1.set();
example2.set();
...
}
- 非类型参数的限制:
- 类型只能是整形、枚举、引用、指针
- 模板中的代码不能修改非类型参数的值,也不能获取参数的地址(引用也不行吧?)(因为非类型参数没有内存空间?)
- 在实例化模板的时候,用作非类型参数的值必须是常量表达式
- 非类型参数数组模板的优点:
- 使用非类型参数来创建数组速度更快,因为数组是自动变量;而且不需要手动new和delete;
- 非类型参数数组模板的缺点:
- 不同的非类型参数数值都会生成自己的模板实例,占用内存增加;
- 无法将一种尺寸的数组模板实例赋给另一种尺寸的数组模板实例;
模板多功能性
类模板也可以用作基类、派生类、组件类、其它模板的类型参数:
template <typename T>
class Array
{
private:
T entry;
};
template <typename Type>
class GrowArray:public Array<type> //派生类模板 继承 基类模板
{ ... };
template <typename Tp>
class Stack
{
Array<TP> ar; //类模板 作为 组件
};
Array<Stack<int> > asi; //类模板 作为 其它类模板的 类型参数;C++98要求后面两个 > 分开,但是C++11不用
递归使用类模板:一个类模板的类型参数还是类模板,例如:vector<vector<int,6>,7>
模板可以包含多个类型参数:
template<typaname T1,typename T2>
class Pair
{
private:
T1 a;
T2 b;
public:
...
void set(const T1 &aa,const T2 &bb);
};
template<typename T1,typrname T2>
void Pair<T1,T2>::set(const T1 &aa,const T2 &bb)
{
a=aa;
b=bb;
}
int main()
{
...
Pair<int,double> example1;
Pair<float,string> example2;
example1.set(3,6.268);
example2.set(3.1415,"hello world");
...
}
默认模板参数:
template <typename T1,typename T2 = int> //一定要确保有默认值的类型参数右边的类型参数都是有默认值的
class Topo //还可以为非类型参数设置默认值
{};
int main()
{
...
Topo<double> m2; //由于没有给出第二个类型参数,所以默认第二个类型参数为int
...
}
模板的具体化
隐式实例化(implicit instantiation) 、 显式实例化(explicit instantiation) 、 显式具体化(explicit specialization) 统称为 具体化(specialization)
假设有这样一个类模板:
template <typename T1,typename T2>
class ArrayTP
{
...
};
隐式实例化
ArrayTP<int,100> stuff; //隐式实例化
...
ArrayTP<double,30> *pt; //不会实例化
pt = new ArrayTP<double,30>; //隐式实例化
这种实例化在函数模板中是显式实例化的一种形式
显式实例化
template class ArrayTP<string,100>; //显式实例化
显式具体化
template<>
class ArrayTP<string,100>
{
...
};
当同时存在具体化模板和通用模板时,编译器优先选择具体化模板;如果还存在普通函数,是不是普通函数最优先
部分具体化
template <typename T1> //没有具体化的参数就保留下来了;是否要确保已具体化参数右边的函数都是被具体化了???
class ArrayTP<T1,int>
{
...
};
部分具体化的更多例子:
//general template
template <class T1, class T2, class T3> class Trio{...};
//specialization tith T3 set to T2
template <class T1, class T2> class Trio<T1, T2, T2>{...};
//specialization with T3 and T2 set to T1*
template <class T1> class Trio<T1, T1*, T1*>{...};
//给定上述声明,编译器将作出如下选择:
Trio<int, short char*> t1; //use general template
Trio<int, short> t2; //use Trio<T1, T2, T2>
Trio<char, char*, char*> t3; //use Trio<T1, T1*, T1*>
函数调用时,模板匹配优先度:显式具体化>部分具体化>实例化
指针(引用)类型优先与T*(T&)匹配,非指针(引用)类型优先与T匹配
成员模板
老编译器不接受模板成员
可以在类模板中定义类模板或者函数模板:
template <typename T>
class beta
{
private:
template <typename V>
class hold
{
private:
V val;
};
hold<T> q;
hold<int> n;
public:
template <typename U>
U blad(U u,T t,hold<T> ht,hold<int> hi)
{ ... }
};
//以上的类模板和函数模板都是在类中定义的,接下来看看在类外要怎么定义
//注意:部分编译器不接受类外定义
template <typename T>
class beta
{
private:
template <typaneme V>
class hold; //声明
hold<T> q;
hold<int> n;
public:
template<typename U>
U blad(U u,T t,hold<T> ht,hold<int> hi);
};
template <typename T>
template <typename V>
class beta<T>::hold //注意作用域解析运算符
{
private:
V val;
};
template <typename T>
template <typename U>
U beta<T>::blad(U u,T t,hold<T> ht,hold<int> hi) //注意作用域解析运算符
{ ... }
由于还要搬砖,没有办法一一回复私信把学习资料发给大家。我直接整理出来放在下面,觉得有帮助的话可以下载下来用于学习
链接:https://pan.baidu.com/s/1C-9TE9ES9xrySqW7PfpjyQ 提取码:cqmd
小弟才浅,如果本篇文章有任何错误和建议,欢迎大家留言 感谢各位人才的点赞、收藏、关注