学习C++.Primer.Plus 14 C++中的代码重用(继承、类模板)
1.继承
1.1私有继承
1 class Student : private std::string, private std::valarray<double> 2 { 3 public : 4 using std::valarray<double>::min; 5 ... 6 }; 7 8 ostream & operator<<(ostream & os, const Student & stu) 9 { 10 os << (const string &) stu; 11 std::valarray<double>::sum(); 12 return (const string&) *this; 13 }
私有继承提供了无名称的子对象成员。上面的代码提供的是string和valarray类型的对象。
访问上面的无名子对象成员时,应用强制类型转换。12行
私有继承时,原类型的公有成员和保护成员都成为了子类的私有成员,原类型的私有成员只能通过基类的接口访问。
调用方法时,使用基类名和作用域解析操作符来调用方法。11行
在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。(原因:1.有可能基类也有同名友元函数,若可转,不知道调用哪个基类的友元函数2.上面代码不强转会递归调用)10行
在派生类的公有部分加入using声明可以重新定义可见性。using声明只能带成员名,没有圆括号、函数特征标和返回类型。using声明之后使得该名的方法(const和非const)都可以用。4行
当初始化列表中存在多个参数时,这些参数的初始化顺序与参数在类中声明的顺序一致,而不受参数在初始化参数列表中出现的顺序影响。
1.2保护继承
私有继承时,原类型的公有成员和保护成员都成为了子类的保护成员,原类型的私有成员只能通过基类的接口访问。
指向派生类的引用或指针可以隐式转换成指向基类的引用或指针,但条件是只能在派生类内部可以这样做。因为在子类外时,父类的公有成员部分已经不可见,也就是不能直接在子类外调用父类的原公有方法了。
1.3公有继承
私有继承时,原类型的公有成员和保护成员仍旧是子类的公有和保护成员,原类型的私有成员只能通过基类的接口访问。
指向派生类的引用或指针可以隐式转换成指向基类的引用或指针。
1.4多重继承
多重继承声明时,每个基类都要加访问限制符,否则会将不加的默认为private继承。
1.4.1虚基类
在祖先相同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则。
如果基类名为Worker,从中派生出Singer和Waiter,又有新类SingerWaiter派生自Singer和Waiter,那么就要使Singer和Waiter声明时把Worker作为虚基类。
class Singer: virtual public Worker {...} class Waiter: virtual public Worker {...}
这么一来,SingerWaiter里就能保证继承的Singer和Waiter都共享一个Worker对象,而不是各自引入Worker的拷贝。
构造函数的初始化列表中,在基类是虚拟的时,禁通过中间类传递值给基类。而且编译器必须在构造派生对象之前构造基类对象组件。如下:
SingerWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Worker(wk), Waiter(wk, p), Singer(wk, v) {}
对于虚基类,必须显式地调用它(除非要调用该虚基类的默认构造函数),类似上面的Worker(wk)。但对于非虚基类,这样跨层调用则是非法的。
多重继承时,有时候调用哪个基类的方法会出现混淆,比如两个基类都有SHOW()方法。这里可以用作用域解析符指出:SingerWaiter.Singer::SHOW()。最好的方法是在派生类中重新定义该方法并指出调用哪个基类。
1.4.2方法的优先性:
当子类和基类有同名方法时,在子类中,子类的方法优先级高于基类。而任何基类的方法定义都不优先于其它基类。不管是private还是public,也就是说,虚拟二义性与访问规则无关,私有方法也都算在内。
1.5继承与包含
通常应该用包含来实现has-a关系。不过如果新类需要重新定义虚函数,或者需要访问原有类的保护成员时,就需要用到继承而不是包含。
2.类模板
template <typename Type, template<typname TT> class Thing, int n, typename TTT = int> class Stack { int stacksize; public: Stack & operator= (const Stack & st); } template<typename Type> Stack<Type> & Stack<Type>::operator= (const Stack<Type> & st) {...}
类模板的声明为template<typename Type> class classname{},或者是<class Type>。
除非将模板声明加了export前缀export template<typename Type> class Stack...,否则模板成员函数放在一个独立的实现文件中将无法运行。因为模板不是函数,它们不能单独编译。
对于模板类内部的函数,编译器将根据函数的参数类型来确定生成哪种函数,所以必须显式地提供所需的类型。
可以在模板声明或模板函数定义内部使用Stack,但在类的外面,必须指定返回类型,或使用作用域解析操作符时,使用完整的Stack<Type> 行
模板可以有类型参数(用typename或class限定的参数),如上面的typename Type,还可以有非类型(或称为表达式)参数,如上面的int n。表达式参数指定了特殊类型,而不是通用类型。
表达式参数可以是整型、枚举、引用或指针类型。因此不能用double m等。
模板代码中不能修改模板参数的值,也不能使用模板参数的地址。如n++或者&n都是不对的。
在实例化模板时,用作表达式参数的值必须是常量表达式。
表达式参数的优缺点:(针对:模板类中有数组等需要动态分配内存的时候)与在其中使用构造函数方法来动态分配内存,使用new和delete来管理堆内存相比,表达式参数方法使用的是为自动变量维护的内存栈,执行速度更快。而缺点是每种长度的数组大小都会生成自己的模板,不同长度的不够通用。如Stack<double ,12> eqq和Stack<double, 13> eqq2就将生成两个独立的类声明。
模板类声明时,还可以为类型参数提供默认值。typename TTT = int。这一点与函数模板不同,函数模板不能为其模板参数提供默认值。
模板的具体化有三种:隐式实例化、显式实例化和显式具体化。
编译器在需要对象之前,不会生成类的隐式实例化:
Stack<double ,30>* pt;//只是创建了一个指向该类型的指针,不需要对象,所以不会隐式实例化。 pt = new Stack<double ,30>;//现在隐式实例化
而显式实例化如下:
template class Stack<string, 100>;//生成模板类的显式实例化
显式具体化:当要生成的类型采用通用模板不行时,比如用在int和double等类上的>和<号对char *类型不适用,这时就要为特定的类型重写相关的比较方法,也就是为特定的类定义模板,而不是为通用类型。这就是显式具体化。当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。具体化格式如下:templte后加空<>
template <> class Stack<char *> { ... }
部分具体化:当有多个类型参数时,只具体化一部分参数,叫部分具体化。如下:template后<>内是没有具体化的参数,类名后面的加的是已经实例化后的参数:
template <class T1> class Stack<T1, int, int> {...}
如果在实例化请求时有多个模板可以选择,刚选择具体化程序最高的模板。
模板类内可以内嵌模板类成员:
template <typename T> class beta { private: template <typename T2> class hold//类模板中加类模板成员定义 {...} template <typename T3> class hhhd;//类模板中加类模板成员声明 public: template<typename U> U blab (U u, T t);//类模板中加模板函数 } //在模板类外定义成员模板类 template<typename T>//外围模板类 template<typename T3>//成员模板类 class beta<T>::hhhd { ... }
由于模板类是嵌套的,所以在模板类外定义其方法时,必须使用嵌套的限定方法,如:template <typename T> template <typename U>,定义还必须指出hold和hhhd和blab是beta<T>的成员,所以要用作用域解析符::。
模板还可以作为模板类的类型参数。如template <template<typename T> class Thing>,这里的Thing是参数template<typename T> class是类型,指定这是一个模板。注意是类模板,而非模板的实例化。
模板参数还可以混合常规参数使用。
模板类的友元函数有三种:非模板友元、约束模板友元(友元的类型取决于类被实例化时的类型)、非约束模板友元(友元的所有具体化都是类的第一个具体化的友元)
模板类的友元函数声明不可以这样写:friend voi report(Stack &);友元函数并不属于模板类内部。外部并不存在Stack类,而只存在该模板类的具体化,如Stack<int>。
每个类的实例化都有一套自己的静态成员部分。
有的非模板友元与类模板被实例化的类型无关,那么,所有实例化类型都共用这一套友元函数。如果与被实例化类型有关,那么,只是该类型的友元函数。很好理解,用作其它的也用不了。
模板类型参数示例:
//第一步:声明模板友元函数 template <typename T> void counts(); template <typename T> void report (T &); //第二步:在类模板中声明为友元 template <typename TT> class HasFriend { friend void counts<TT>();//该声明中,template后的<>表明这是函数模板的具体化。因为不能从参数里获取具体化类型,所以必须加TT来指明具体化类型 friend void report<>(HasFriend<TT> &);//因为这里可以从参数类型推断出函数模板的具体化类型,所以report后的<>可以为空,当然也可以为<HasFriend<TT>>。这个就是具体化的原理。 ... } HasFriend<int> skkk;//这时友元将TT替换成int //第三步:提供友元的定义 template <typename T> void counts() {...} template <typename T> void report (T & hf) { }
非约束模板友元函数:友元函数是函数模板,并且,友元模板函数的声明完全在类模板内。这么一来,友元模板函数的每一个具体化 都是类模板所有具体化的友元。也就是说,(友元函数模板的具体化是谁的友元) 不受 (类模板具体化的类型)约束,所以叫非约束模板友元函数。示例如下:
template <typename T> class ManyFriend { template <typename C, typename D> friend void show2(C & c, D & d);//声明非约束模板友元函数 } ... ManyFriend<double> d1; ManyFriend<int> i1; ManyFriend<int> i2; show2(i1, i2); show2(d1, i1);//与这个具体化配置:void show2<ManyFriend<double> &, ManyFriend<int> &>(ManyFriend<double> & d, ManyFriend<int> & i)。同时,这个具体化也是所有ManyFriend具体化的友元,并访问了ManyFriend<int>对象的和ManyFriend<double>对象的成员。