对前面一些基础知识的深入和补充。
一些模板例子:
template <typename T>
class List {
public:
template <typename T2>
List(List<T2> const&);
};
template <typename T>
template <typename T2>
List<T>::List(List<T2> const& b)
{
//todo
}
template <typename T>
int length(List<T> const&);
class Collection {
template <typename T>
class Node {
//todo
};
template <typename T>
class Handle;
template <typename T>
T* allco{
}
};
template <typename T>
class Collection::Handle {
//todo
};
一:虚成员函数
成员函数模板不能声明为虚函数。
因为成员函数模板实例化数量不确定 导致无法确定虚表大小,这与虚函数表设计相冲突。
相反的,类模板的非模板成员函数可以是虚函数。
二:模板的链接
模板在所属作用域下名字必须是唯一的。(1.类模板不能和另外一个实体共享 2.函数模板重载除外)
模板名字具有链接但是不能有c链接。
三:模板参数
1.类型参数
2.非类型参数
包括:整型或者枚举类型,指针类型,引用类型
函数和数组类型也可以被指定为非模板参数,但是需要先隐式转换为指针类型
template<int buff[5]> class Lexer;//
template<int* buff> class Lexer;//等价
3.模板的模板参数
声明和类模板的声明类似,但不能使用关键字struct和union
4.缺省模板实参
缺省实参不能依赖于自身的参数,但可以依赖于前面的参数
template <typename T, typename Allocator = allocator<T> >
class List;//allocator<T>不能依赖于本身的参数Allocator 但是可以依赖于前面的参数T
template <typename T1, typename T2, typename T3, typename T4 = char, typename T5 = char>
class QQ;
template <typename T1, typename T2, typename T3 = char, typename T4, typename T5>//正确 因为上面一个的4,5已经有缺省值了
class QQ;
template <typename T1 = char, typename T2, typename T3, typename T4, typename T5>//错误 2还没有缺省
class QQ;
template <typename T = void>
class Value;
template <typename T = void>
class Value;//错误:重复出现的缺省值
5.模板实参
注:
包括:显式模板实参,注入式类名称,缺省模板实参,实参演绎。
注:对于函数模板实参演绎,可以通过调换模板参数的顺序将无法演绎的参数放在前面,让后面的参数依旧可以演绎。
注:替换失败并非错误是函数模板可以重载的重要因素。允许试图创建无效的类型,但是不允许试图计算无效的表达式
注:在函数定义内部声明的类型不能作为模板的类型实参,未命名的class类型或者未命名的枚举类型不能作为模板的类型实参,通过tepedef声明给出的未命名类和枚举事可以作为模板类型参数的。
例如:
template <typename T> class List {
//todo
};
typedef struct {
double x, y, z;
}Point;
typedef enum {red, green, blue} *ColorPtr;
int main()
{
struct Association
{
int *p;
int *q;
};
List<Association*> error1;//错误:模板实参中使用了局部类型
List<ColorPtr> error2;//错误: 模板实参中使用了未命名的类型因为typedef定义的是 *ColorPtr, 并非ColorPtr
List<Point> ok;//正确 通过使用typedef定义的未命名类型
}
当创建程序时,编译器或者链接器要能够确定实参的值,如果实参的值要等到程序运行时才能确定(局部变量的地址等) 就与之相悖了。
特别的:
空指针常量,浮点类型,字符串这些常值不能作为有效的非类型实参。
对于字符串类型 可以用一个额外变量存储 调用
一些错误例子:
template<typename T, T nontype_para>
class C;
class Base {
public:
int i;
} base;
class Derived : public Base{
}derived_obj;
C<Base*, &derived_obj>* err1; //这里不会产生派生类型向基类的转换
C<int&, base.i>* err2; //域运算符后面的变量不会被看成变量
int a[10];
C<int*, &a[0]>* eer3; //单一数组元素地址不可取
6.友元
1.友元类
友元类的声明可以命名一个特定的类模板实例为友元,但这时声明的地方类模板必须是可见的,与普通类不同
例如:
template <typename T>
class Tree {
friend class Factory;//Factory可以不可见
friend class Node<T>;//node 必须可见
};
2.友元函数
在普通类里面声明友元函数。
注:命名一个实例的友元声明是不能作为定义的。
void multiply(void*);
template <typename T>
void multiply(T);
class Comrades {
friend void multiply(int){}//定义一个新的函数 非受限函数不能引用模板函数
friend void ::multiply(void*); //引用上面普通函数 没有<>普通函数优先度高
friend void ::multiply(int); //引用模板实例 因为普通函数不匹配
friend void ::multiply<double*>(double*); // 引用模板 但模板在此必须可见
friend void ::error() {}//错误 受限的友元不能是定义
};
在模板类里面胜面友元,与上面类似,add:可以使用模板参数来标识友元函数
template <typename T>
class Node {
Node<T>* allocate();
};
template <typename T>
class List {
friend Node<T>* Node<T>::allocate();
};
注:在类模板内部定义一个友元函数的时候 如果该友元函数 在不同模板实例化后类型一样 那么会产生重定义的问题。
如:
template <typename T>
class Creator {
friend void appear() {
}
};
Creator<void> miracle;
Creator<double> oops;//::appear()被第二次生成
注:类内定义友元函数是普通函数 而不是 模板的实例
由于函数的实体在类定义内部,所以是内联函数,在两个不同的翻译单元中可以生成相同的函数。
class Manager {
template <typename T>
friend class Task;
template <typename T>
friend void Schedule<T>::dispatch(Task<T>*);
template <typename T>
friend int ticket()
{
return ++Manager::counter;
}
static int counter;
};
注 友元模板:让模板的所有实例都成为友元