编写一个函数比较两个值,并指出第一个值是小于丶等会还是大于第二个值,我们可能想要定义多个函数,每个函数比较一种给定类型的值:
//如果两个值相等,返回0,如果v1小返回-1,如果v2小返回1 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; }
这两个函数几乎是相同的,唯一的差异就是参数的类型,函数体则完全一样。
函数模板
一个函数模板就是一个公式,可用来生成针对特定类型的函数版本,compare的函数模板如下:
template<typename T> int compare(const T &v1, const T &v2) { if (v1 < v2)return -1; if (v2 < v1)return 1; return 0; }
模板定义以关键字template开始,后跟一个模板参数列表,这是一个逗号分隔的一个或多个模板参数的列表,用<>包围起来。
tip:在模板的定义中,模板参数列表不能为空
compare函数声明了一个名为T的类型参数,在compare中,我们用名字T表示一个类型,而T表示的实际类型则在编译时根据compare的使用情况来确定。
实例化函数模板
当我们调用一个函数模板时,编译器用函数实参来推断模板实参。当调用compare时,编译器使用实参的类型来确定绑定到模板参数T的类型:
cout << compare(1, 2) << endl;//T为int
实参类型是int,编译器会推断出模板实参为int,并将它绑定到模板参数T。
编译器用推断出的参数模板来实例化一个特定版本的函数。当编译器实例化一个模板时,它使用实际的模板实参代替对应的模板参数来创建出模板的一个新“实例”:
//实例化出int compare(const int&,const int&) cout << compare(1, 0) << endl; //实例化出int compare(const vector<int>&,const vector<int>&) vector<int> vec1{ 1,2,3 }, vec2{ 4,5,6 }; cout << compare(vec1, vec2) << endl;//T为vector<int>
模板类型参数
类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换:
//正确:返回类型和参数类型相同 template<typename T> T foo(T* p) { T tmp = *p;//tmp的类型是指针p指向的类型 //... return tmp; }
类型参数前必须使用关键词class或typename
//错误:U之前必须加上class或typename template <typename T, U>T calc(const T&, const U&);
在模板参数列表中,这两个关键词的含义相同,可以互换使用,一个模板参数列表中可以同时使用这两个关键词:
//正确:在模板参数列表中,typename和class没有什么不同 template<typename T, class U> calc(const T&, const U&);
用关键词typename来指定模板类型参数比用class更为直观,可以用内置类型作为模板类型实参。
非类型模板参数
一个非类型参数表示一个值而非一个类型。
当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替,这些值必须时常量表达式,从而允许编辑器在编译时实例化模板。
编写一个compare版本处理字符串字面常量。这种字面常量时const char 的数组。由于不能拷贝一个数组,所以我们将自己的参数定义为数组的引用。由于我们希望比较不同长度的字符串字面常量,因此为模板定义了两个非类型的参数。第一个模板表示第一个数组的长度,第二个模板表示第二个数组的长度。
template<unsigned N, unsigned M> int comapre(const char (&p1)[N],const char (&p2)[M]) { return strcmp(p1, p2); }
调用这个版本的compare:
comapre("hi", "mom");
编译器会使用字面常量的大小来代替N和M,从而实例化模板。编译器会在一个字符串常量的末尾插入一个空字符来作为终结符,因此编译器会实例化出:
int comapre(const char(&p1)[3], const char(&p2)[4])
- 一个非类型参数可以时一个整型,或者是一个指向对象或函数类型的指针或引用。
- 绑定到非类型整型参数的实参必须是一个常量表达式,绑定到指针或引用非类型参数的实参必须具有静态的生存期。
- 不能用一个普通局部变量或动态对象为指针或引用非类型模板参数的实参
- 指针参数也可以用nullptr或一个值为0的常量表达式来实例化