引言
在没有接触模板之前,如果我们要编写一个比较大小的函数,我们往往会这样写:
int compare(const string &v1, const string &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int compare(const double &v1, const double &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
这些函数几乎相同,它们之间唯一的区别是形参的类型,每个函数的函数体是相同的。
这种方法的缺点是:每个函数只适用于一种数据类型,当遇到新的数据类型时,就需要增加新的函数。
有没有一种技术可以让我们编写一遍,就能适用于所有数据类型呢? 技术的创新源于人的懒惰,恰好C++就给我们提供了这样的技术——函数模板。
函数模板的工作原理
函数模板是一个独立于类型的函数,编译器在编译期根据程序员提供的实际参数类型替换形参,使替换后的代码就如同直接用实际参数编写的代码一样。
定义函数模板
模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔,后接我们平时写的函数声明,唯一的区别就是形参类型只是一个没有实际意义的类型。需要注意的是模板形参表不能为空。
// implement strcmp-like generic compare function
// returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
使用函数模板
int main ()
{
// T is int;
// compiler instantiates int compare(const int&, const int&)
cout << compare(1, 0) << endl;
// T is string;
// compiler instantiates int compare(const string&, const string&)
string s1 = "hi", s2 = "world";
cout << compare(s1, s2) << endl;
return 0;
}
inline 函数模板
// ok: inline specifier follows template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of inline specifier
inline template <typename T> T min(const T&, const T&);
定义类模板
template <class Type>
class Queue {
public:
Queue (); // default constructor
Type &front (); // return element from head of Queue
const Type &front () const;
void push (const Type &); // add element to back of Queue
void pop(); // remove element from head of Queue
bool empty() const; // true if no elements in the Queue
private:
// ...
};
使用类模板
Queue<int> qi; // Queue that holds ints
Queue< vector<double> > qc; // Queue that holds vectors of doubles
Queue<string> qs; // Queue that holds strings
模板形参
1、模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。如果是类型形参,我们就知道该形参表示未知类型,如果是非类型形参,我们就知道它是一个未知值。
2、像函数形参一样,程序员为模板形参选择的名字没有本质含义。
3、如果希望使用模板形参所表示的类型或值,可以使用与对应模板形参相同的名字
4、模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用
5、模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字:
typedef double T;
template <class T> T calc(const T &a, const T &b)
{
// tmp has the type of the template parameter T
// not that of the global typedef
T tmp = a;
// ...
return tmp;
}
6、用作模板形参的名字不能在模板内部重用
template <class T> T calc(const T &a, const T &b)
{
typedef double T; // error: redeclares template parameter T
T tmp = a;
// ...
return tmp;
}
7、正如可以重用函数形参名字一样,模板形参的名字也能在不同模板中重用:
// ok: reuses parameter type name across different templates
template <class T> T calc (const T&, const T&) ;
template <class T> int compare(const T&, const T&) ;
8、像其他任意函数或类一样,对于模板可以只声明而不定义。声明必须指出函数或类是一个模板
// declares compare but does not define it template <class T> int compare(const T&, const T&) ; 9、每个模板类型形参前面必须带上关键字 class 或 typename,每个非类型形参前面必须带上
类型名字,省略关键字或类型说明符是错误的
// error: must precede U by either typename or class template <typename T, U> T calc (const T&, const U&) ;
在模板定义内部指定类型
如果希望编译器将 size_type 当作类型,则必须显式告诉编译器这样做:
template <class Parm, class U> Parm fcn(Parm* array, U value) { typename Parm::size_type * p; // ok: declares p to be a pointer }非类型模板形参在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。例如,下面的函数模板声明了array_init 是一个含有一个类型模板形参和一个非类型模板形参的函数模板。函数本身接受一个形参,该形参是数组的引用// initialize elements of an array to zero template <class T, size_t N> void array_init(T (&parm)[N]) { for (size_t i = 0; i != N; ++i) { parm[i] = 0; } }
编写泛型程序1、编写模板代码时,对实参类型的要求尽可能少是很有益的2、对运算符的使用尽量单一