学习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>对象的成员。

 

 

 

posted @ 2014-02-28 18:12  toffrey  阅读(424)  评论(0)    收藏  举报