要区分开 类成员的访问类型 和 类的继承类型 之间的关系
类成员的访问类型 决定 类成员的可访问性和可继承性:
访问类型 | 外部可访问性 | 可继承性 |
---|---|---|
public | 可访问 | 可继承 |
protect | 不可访问 | 可继承 |
private | 不可访问 | 不可继承 |
类的继承类型 决定 继承下来的成员 的访问类型会变成什么:
继承类型 | 变化 |
---|---|
公有继承 | 不改变继承下来内容的访问类型 |
保护继承 | 将继承下来的内容的访问类型都变为protect |
私有继承 | 将继承下来的内容的访问类型都变为private |
派生类的指针(引用)要赋值给私有继承下来的基类类型指针(引用),必要强转派生类的指针(引用)
包含将对象作为一个有名字的成员添加到类中,私有继承将一个没有名字的成员添加到类中;这些成员称为子对象(subobject)
私有继承是默认继承方式
访问基类成员的格式:类名::基类成员
ps:友元函数不是类成员
若要直接获得子对象,则只需要对this(*this)进行强制类型转换即可,转换成你想要的子对象类型的指针(引用)
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基类对象:
为了删除多个相同基类,有点像融合,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(); ... }
类模板也可以用作基类、派生类、组件类、其它模板的类型参数:
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) //注意作用域解析运算符 { ... }