模板定义:1、函数模板:
模板定义以关键字 template 开始,后接尖括号括起的模板形参表。
inline 函数模板, inline 说明符放在模板形参表后面,返回类型之前。
2、定义类模板:
必须以关键字template开头, 后接模板形参类型表。
使用类模板时,必须为模板形参显式指定实参。
模板形参: 模板形参可以是表示类型的类型形参;也可以是表示常量表达式的非类型形参;
非类型形参跟在类型说明符之后声明; 类型形参跟在关键字class 或 typename 之后定义。
模板形参名字的限制:
注意:模板形参的名字不能在模板内部重用;模板形参的名字只能在同一模板形参表中使用一次。
模板声明:
同一模板的声明和定义中,模板形参的名字不必相同。
每个模板类型形参前面必须带上关键字
typename 或 class
,每个非类型形参前面必须带上类型名字。
在模板定义内部制定类型:
通过在成员名 前面加 关键字 typename, 告诉编译器将成员当做类型。
template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
Parm::size_type *p;
}
parm:: size_type 有两种情况: 1) Parm 类中定义的数据成员。 则上面的code 是一个表达式
2) Parm 类中定义的数据类型。 则上面是一个定义,定义一个指针。
如果在函数模板内部使用这样的类型,必须告诉编译器。 在前面加上 typename: 如: typename Parm::size_type *p;
非类型模板形参:
调用函数是非类型形参将用值代替,值的类型在模板形参表中指定。
模板非类型形参是模板定义内部的常量值。
EX: template < class T, size_t N> void array_init( T (&parm) [N]) {} // parm 是一个引用,引用的对象是一个T 的数组,数组长度为N
int x[42];
array_init(x); // 实例化 array_init ( int (&) [42]);
注意: 在函数模板内部完成的操作限制了可用于实例化该函数的类型。
编写泛型代码的重要原则:
1)函数的形参是const引用
2)函数体的测试只用< 比较
模板在使用时讲进行实例化,类模板在引用实际模板类类型时实例化, 函数模板在调用它或用它对函数指针进行初始化或赋值时实例化
类的实例化: 1、类模板的每次实例化都会产生一个独立的类类型。
2、类模板形参是必须的
类型形参的实参的受限转换:
编译器只执行两种转换:
1) const 转换: 接受const 引用或const 指针的函数可以分别用非const对象的引用或指针来调用。如果函数接受非引用类型,形参类型和实参都忽略const
2)数组或函数到指针的转换: 如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。 数组实参将当做指向第一个元素的指针,函数实参当作指向函数类型的指针。
EX: template <typename T> T fref(const T&, const T&);
int s1[1], s2[2]; fref(s1,s2); 将会出错,因为s1 和 s2 的类型不一样。这里相同的类型是指相同长度的数组。
模板实参推断 与 函数指针:
可以使用函数模板对函数指针进行初始化和赋值。这样做会 -> 编译器使用指针的类型实例化具有适当模板实参的模板版本。
注意: 获取函数模板实例化的地址的时候,上下文必须是这样的: 它允许为每个模板形参确定唯一的类型或值。
在返回类型中使用类型形参:
调用提供显式模板实参与定义类模板的实例 类似, 在以逗号分隔、尖括号括住的列表中指定显式模板实参。
显式模板类型的列表出现在函数名之后,实参表之前。
EX:
template <class T1, class T2, class T3> T1 sum(T2, T3) ;
long val3 = sum<long>(i, lng) //只有结尾(最右边)形参的显示模板实参可以省略。
template <class T1, class T2, class T3> T1 sum(T2, T3) ;
long val3 = sum<long,int,long>(i, lng) // 不能省略
显式实参与函数模板的指针:
通过显式模板实参能够消除二义性:
EX:
template<typename> int compare (const T&, const T&);
void func( int(*) (const string &, const string&);
void func( int(*) (const int &, const int &);
func(compare) ; // error 获取函数模板实例化的地址的时候,
上下文必须允许为模板形参指定确定唯一的类型或值
函数fun 的形参是以函数的指针, 该函数的形参是两个 int 类型的const 引用,返回类型是int
如果直接用func(compare) 则 compare 有二义性。 因为func 存在连个重载版本。
可以通过以下解决:
func( compare<int>) ; //
从而调用特定的func 版本。
模板类型编译:
一般来说, 调用函数的时候,编译器只需要看到函数的声明。
定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。 因此,应该将 类定义 和 函数声明放在头文件中, 而普通函数和类成员函数 的定义放在源文件中。
模板: 要进行实例化, 编译器必须能够访问定义模板的源代码。
注意: 所以如果把模板类的定义文件和实现文件分开写, 这源文件实例化模板类的时候必须 include 实现文件 .cpp ;
看到网上有人把头文件 和 实现文件写在同一个文件,然后后缀名取 .hpp ; 如果以这种形式写的话,则 hpp 文件里面不能出现全局变量,static 变量,否则,会出现变量重复定义的错误。
这部分不懂,做个笔记mark下 ???????
模板编译模型:
1、 包含编译模型: 通过在声明函数模板 或 类模板的头文件中 添加一条 #include 指示使定义可用; 该include 引入了包含该相关定义的源文件。
2、 分别编译模型: export 关键字。 export 能够指明给定的定义可能会需要在其他文件中产生实例化。 export 关键字不必在模板声明中出现。
注意: 头文件的类定义体不应该出现关键字export , 如果在头文件中出现了export , 则该头文件只能被程序中的一个源文件使用。 所以,应该类的实现文件中使用export。
类模板中的名字查找:
1)独立于模板形参的那些名字
2)依赖于模板形参的那些名字
类模板成员
模板作用域中模板类型的引用: 当使用类模板的名字是,必须指定类模板形参。
例外的情况:在类本身的作用域内部,可以使用类模板的非限定名。
注意:编译器不会为类中使用的其他模板的模板形参进行这样的推断;正因为如此,若在模板类里面用到其他的类模板,如果没有为其他的类模板指定形参列表,则编译器
会把他当本类模板推断,但是类名又不符合,所以报错了!!!
类模板成员函数:
类模板成员函数的定义形式:
1, 必须以关键字 template 开头,后接类的模板形参表。
2, 必须指出他是哪个类的成员。(即是要有类名+作用域符号)
3, 类名必须包含器模板形参
template <class T> ret-type Queue<T>::member-name
类模板成员函数的实例化: 类模板成员函数的模板形参由调用该函数的对象的类型确定。
何时实例化类和成员:类模板的成员函数只有为程序所用才进行实例化。如果某函数从未使用, 则不会实例化该成员函数。
定义模板类型的对象时, 该定义导致实例化类模板。定义对象也会实例化 用于初始化该对象的任意构造函数,以及该构造函数调用的任意成员。
类模板的指针定义不会对类进行初始化,只有用到这样的指针时才会对类进行实例化。
重温概念点:复制初始化:调用复制构造函数。 首先使用指定构造函数创建一个临时对象,然后用复制构造函数将临时对象复制到正在创建的对象。两个步骤~~~~~
非类型形参的模板形参: 非类型模板实参必须是编译时常量表达式。
类模板中的友元声明:
三种友元声明:
1)普通非模板类或函数的友元声明, 将友元关系授予明确指定的类或函数。
2)类模板或函数模板的友元声明, 授予友元所有实例的访问权
3)只授予对类模板或函数模板的特定实例的访问权的友元声明
声明依赖性:
当授予对给定模板的所有实例的访问权的时候, 在作用域中不需要存在该类模板或函数模板 的声明。 实质上,编译器将友元声明 也 当作 类或函数的 声明对待。
想要限制对 特定实例化 的友元关系时,
必须在可以用于友元声明之前声明 类 或函数。
如果没有事先告诉编译器该友元是一个模板,则编译器 将认为该友元是一个普通非模板类 或 非模板函数。
成员模板:
注意: 当在类模板作用域外部定义成员模板的时候,必须包含两个模板形参表: 首先是类模板形参表,后面接着
成员自己的模板形参表。
成员模板和实例化: 成员模板只有在程序中使用是才实例化,
类模板的static 成员:
1使用类模板的static成员: 可以通过类类型的对象访问类模板的static 成员,或者 通过使用作用域操作符直接访问成员。注意: 通过类使用static成员是,必须 引用实际的实例化。
2 定义 static 成员: 跟使用任意其他static数据成员一样,必须在类外部出现数据成员的定义。 在类模板含有static 成员的情况下,成员定义必须指出它是 类模板的成员:
EX: template <class T>
size_t Foo<T>::ctr=0;
泛型句柄类:
1、 定义句柄类:
handle类的分析:
复制: 复制handle对象将不会复制基础对象,复制之后,两个handle对象将引用同一个基础对象。
创建: 用户需要传递属于 由handle管理的类型 (或 从该类型派生的类型) 的动态分配的地址,从此刻起,handle 将“拥有”这个对象。 而且,一旦不再有 任意handle 对象与该对象关联, handle 类将负
责删除该对象。
Handle定义文件:
#ifndef HANDLE_H
#define HANDLE_H
template <class T> class Handle
{
public:
Handle(T *p=0): ptr(p), use(new size_t(1)){} //默认构造函数
// 重载 解引用和箭头符号, 获取所指向对象引用或地址
T& operator *();
T* operator ->();
const T& operator *()const ;
const T* operator ->()const ;
// 复制控制: 复制构造函数; 重载赋值运算符; 析构函数
Handle(const Handle &h):ptr(h.ptr), use(h.use)
{
++*use;
}
Handle & operator=(const Handle& );
~Handle (){rem_ref();}
private:
T* ptr;
size_t* use;
void rem_ref(){
if(--*use==0) {delete ptr, delete use;}
}
};
template <class T>
inline Handle<T>& Handle<T>::operator =(const Handle& rhs)
{
++*rhs.use;
rem_ref();
ptr=rhs.ptr;
use=rhs.use;
return *this;
}
template <class T> inline T& Handle<T>::operator *()
{
if(ptr) return *ptr;
throw std::runtime_error("dereference of unbound Handle");
}
template <class T> inline T* Handle<T>::operator ->()
{
if(ptr) return ptr;
throw std::runtime_error("access unbound Handle");
}
template <class T> inline const T& Handle<T>::operator *() const
{
if(ptr) return *ptr;
throw std::runtime_error("dereference of unbound Handle");
}
template <class T> inline const T* Handle<T>::operator ->() const
{
if(ptr) return ptr;
throw std::runtime_error("access unbound Handle");
}
#endif
模板特化:
在定义了被特化的模板之前,不能出现该模板的特化; 在使用任意实参特化的模板之前,必须先出现模板特化。
1函数模板的特化: 该定义中一个或多个 模板形参 的实际类型 或实际值是指定的。
特化的形式:
- 关键字 template 后面接一对 空的尖括号
- 再接 模板名 和 一对尖括号 ,尖括号 中指定这个特化定义的模板形参
- 函数形参表
- 函数体
EX: 下面的程序 定义了 当模板形参 绑定到const char* 是, compare 函数的特化:
template<>
int compare<const char*> (const char* const &v1, const char* const &v2) { return strcmp(v1,v2); }
函数形参是类型形参的const 引用!!!!
声明模板特化: 函数模板特化可以声明而无须特化
注意: 函数模板特化必须 总是包含 空模板形参说明符, 即 template<> , 而且, 还必须包含函数形参表。 如果可以从函数形参表推断出 模板实参,则不必显式指定模板形参。
函数重载 与 模板特化: 在特化中省略空的模板形参表 template<> ,则结果是声明该函数的重载非模板版本。
注意: 如果程序是由多个文件构成, 模板特化的声明必须在使用 该特化的每个文件中出现。 不能在一些文件中从泛型模板定义实例化一个函数模板, 而在其他文件中为同一模板实参集合特化该函数模板。
所以: 应在一个头文件中包含模板特化的声明,然后在使用该特化的每个源文件包含该头文件。
注意: 对具有同一模板实参集的同一模板, 程序不能既有 显式特化 又有实例化。
特化出现在对该模板实例的调用之后是错误的!!!!
类模板的特化:
注意: 特化可以定义与模板本身完全不同的成员。
注意: 类模板特化应该与它所特化的模板定义相同的接口,否则试图使用未定义的成员时很奇怪。
类模板的部分特化:
类模板的部分特化 本身也是模板。
当声明了部分特化的时候,编译器将为实例化选择最特化的模板定义。 当没有部分特化可以使用的时候, 就使用通用 模板定义。
重载与函数模板。
模板函数可以重载:1、可以定义有相同名字 但形参数目或类型不同的多个函数模板。
2、可以定义与函数模板 有相同名字的 普通非模板函数。