写在前面:
0. 所谓泛型编程就是独立于任何特定类型的方式编写代码,使用泛型程序时,需要提供具体陈旭实例所操作的类型或者值。我们经常用到STL容器、迭代器、和算法都是泛型编程的例子;
普通定义形式
templateT>
int func(const T &a1, const T &a2)
{
...
}
template T>
inline int func(const T &a1, const T&a2)
{
...
}
tempalte
T1 func(const T2 &t2, const T3 &t3)
{
...
}
//调用方法
func(i, log);
如果类型的定义顺序与调用顺序不一样的话, 则需要在申明的时候制定类型顺序;
tempalte
T3 func(const T1 &t1, T2 &t2)
{
...
}
//调用方法
func(12, 34);
//定义方式
template
class Queue
{
...
...
}
//使用方法
Queue qi;
template <class T, size_t N>
void func(T (&parm)[N])
{
//此时,N将直接用值代替
}
发现错误一般分为三个阶段:
1. 编译模板定义本身,可以检测模板本身的语法问题,例如漏掉分号,拼写错误等;
2. 编译器见到模板的使用时,检测参数个数、类型是否合法;
3. 模板实例化期间,检测类型相关的错误;
类模板在引用实例类模板类类型时实例化,函数模板在调用它或者用它对函数指针进程初始化或者赋值时实例化,在使用函数模板时,编译器通常会为我们推断模板实参;
当编译器看到模板定义时,不立即产生代码, 只有在看到模板调用时,编译器才会产生对应的实例,类型相关的错误才会被检查出来。
通常情况下,实例化一个对象或者调用一个函数时,编译器不需要看到函数或者类的定义,只有在连接的时候才会去关心类或者函数的定义。但是模板不一样, 编译器在实例化模板时,必须看到模板的定义才会编译通过。
//header file
#ifndef xx_H_
#define XX_H_
template T>
int func(T &t1, T&t2);
#include "oo.cpp" //模板定义文件
#endif
//oo.cpp
templateT>
int func(T &t1, T&t2)
{
...
}
template <class T> ret-type Queue<T>::member_func_name
{
//define
}
template <int hi, int wid>
class Screen
{
public:
Screen():{}
private:
std::string screen;
std::string::size_type cursor;
std::string::size_type height, width;
}
//实例化方法,参数必须是编译时常量表达式
Screen<24, 80> hp2621;
template
class Bar
{
friend class FooBar;
...
}
FooBar 的成员可以访问Bar类任意实例的private &protected 成员。
template
class Bar
{
template T> friend class Fool;
template T> friend void templ_fcnt(const T&);
...
}
表示Fool和templ_fcnt的任意实例都可以访问Bar的任意实例的private和protected成员。
模板类只授权对特定友元实例的访问权
template <class T> class Foo2;
template <class void templ_fcnt(const T&);
template <class Type>
class Bar
{
friend class Foo2<char*>;
friend void templ_fcnt<char*>(char *const &);
}
更通用的形式
template <class T> class Foo2;
template <class T> void templ_fcnt(const T&);
template <class Type>
class Bar
{
friend class Foo2;
friend void templ_fcnt(const Type &);
...
}
这样每个类型的类模板实例与对应的类型友元建立了一一映射关系。
- 声明依赖性
如果模板类授权给所有友元实例访问private和protected成员时, 编译器将友元声明当做类或者函数的声明对待;但是如果指定到特定类型时,必须在前面声明类或者函数。参考上面特定模板友元关系 和 一般友元关系 的声明。
同时,如果没有提前告诉编译器该友元是一个模板,编译器则认为友元是一个普通非模板函数或者非模板类。
这个名字确实有点绕, 其本质意思就是模板类的成员函数也希望有自己的参数类型,看如下例子:
template <class Type>
class Queue
{
public:
template <class It>
Queue(It begin, It end):
head(0), tail(0)
{
copy_elems(beg, end);
}
template <class Iter>
void assign(Iter , Iter);
private:
template <class Iter>
void copy_elems(Iter, Iter);
}
在类模板的外部定义模板成员,必须包含类模板的形参和模板成员的模板形参:
template //类模板的形参
tmeplate //成员模板形参
void Queue::assign(Iter begin, Iter end)
{
...
}
与其他成员一样,成员模板也只有在被使用的时候才会实例化。
template <class Type>
class Bar
{
public:
static std::size_t count(){return ctr};
private:
static std::size_t ctr;
}
实例化原则是:相同类型的实例共享一个static成员,例如Bar 类型的实例共享一个static 成员ctr,Bar 类型的实例共享一个static成员ctr;
- 使用方法
Bar<int> bar1, bar2;
size_t ct = Bar<int>::count();
template <class Type>
size_t Foo<Type>::ctr = 0;
由于模板的定义中,其操作都是依赖实例化的类型是否支持该操作或者操作的结果与预期是否相匹配,例如:
template <class Type>
int compare(const Type& t1, const Type &t2)
{
if(t1 > t2) return 1;
if(t1 < t2> return -1;
return 0;
}
在上面的例子中,如果用char* 去实例化模板时,函数将比较两个指针,很明显与预期的记过不相吻合。此时可以通过模板特例话来解决。
函数模板特例化形式如下:
- 关键字template 后面接一对空的尖括号(<>);
- 在接末班吗和一堆尖括号,尖括号中制定这个特化定义的模板形参;
- 函数形参表;
- 函数体。
例如:
template <>
int compare<const char*>(const char *t1, const char *t2)
{
return strcmp(t1, t2);
}
如果有多个模板形参,则依次排列即可。
template <>
class Queue
{
...
}
需要在类特化的外部定义普通成员函数时,成员之前不能加 template<>标记:
void Queue::push(const char* val)
{
...
}
template <>
void Queue::push(const char* const &val)
{
...
}
template<>
void Queue::pop()
{
...
}
现在,类类型Queue
template <class T1, class T2>
class tem
{
...
};
//partial specialization :fixes T2 as int and allows T2 to vary.
template <class T1>
class tem<T1, int>
{
}
使用方法:
tem<int , string> foo; //调用普通的类模板
tem<string , int> bar; //调用偏例化版本
函数模板可以重载:可以定义有相同名字但形参数据或类型不同的多个函数模板, 也可以定义与函数模板有相同名字的普通非模板函数。
不过从实践来看,设计既包含函数模板又包含非模板函数的重载函数集合是困难的,因为坑你会使函数的用户感觉到奇怪,定义函数模板特化几乎总是比使用非模板版本更好。