定义、实例化函数模板:
对于函数体完全相同,唯一差异就是参数类型的情况,我们可以定义一个通用的函数模板,而非为每个类型都定义一个新函数:
1 #include <iostream>
2 #include <vector>
3 using namespace std;
4
5 template <typename T>//模板参数列表
6 int compare(const T &v1, const T &v2) {
7 if(v1 < v2) return -1;
8 if(v2 < v1) return 1;
9 return 0;
10 }
11
12 int main(void) {
13 cout << compare(1, 0) << endl;//T为int
14 cout << compare(vector<int>{1, 2, 3}, vector<int>{2, 3, 4}) << endl;//T为vector<int>
15
16 return 0;
17 }
注意:模板定义以关键字 template 开始,后跟一个模板参数列表,里面有一个或多个模板参数。在模板定义中,模板参数列表不能为空
类似于函数参数,模板参数表示在类或函数定义中用到的类型或值。当使用模板时,我们 (隐式的或显式地) 指定模板实参,将其绑定道模板参数上。
当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参,并用推断出的模板参数来为我们实例化一个特定版本的函数。
模板参数可以是模板类型参数或非类型模板参数(表示值而非类型)
模板类型参数:
前面的 compare 中 T 就是一个模板类型参数。一般来说我们可以将类型参数看作类型说明符,就像内置类型或类类型说明符一样使用:
1 template <typename T>//类型参数
2 //函数模板返回类型与参数类型相同
3 T foo(T *p) {//类型参数可硬用来指定返回类型或函数的参数类型
4 T tmp = *p;//类型参数可以用于函数体内部声明变量或类型转换
5 //...
6 return tmp;
7 }
注意:类型参数可用来指定返回类型、函数的参数类型、声明变量或类型转换
类型参数前必须使用关键字 class 或 typename:
// 错误,U 之前必须加上 class 或 typename
template <typename T, U> T calc(const T&, const U&);
//正确
template <typename T, class U> T calc(const T&, const U&);
注意:在模板参数列表中,class 和 typename 含义完全相同,可以互换使用
非类型模板参数:
一个非类型模板参数表示一个值而非一个类型。我们通过一个特定的类型名而非关键字 class 或 typename 来指定非类型参数。当一个模板呗实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替,这些值必须是常量表达式,从而允许编译器在编译时实时实例化模板:
1 #include <iostream>
2 #include <string.h>
3 using namespace std;
4
5 template<unsigned N, unsigned M>
6 int compare(const char (&p1)[N], const char (&p2)[M]) {//数组不能拷贝,可以引用
7 return strcmp(p1, p2);
8 }
9
10 int main(void) {
11 compare("hi", "mom");
12
13 return 0;
14 }
编译器会实例化出如下模板:
int compare(const char (&p1)[3], const char (&p2)[4])
注意:一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或(左值)引用
绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期。指针参数也可以用 nullptr 或一个值为 0 的常量表达式来实例化
inline 和 constexpr 的函数模板:
template <typename T> inline T min(const T&, const T&);
注意:inline 或 constexpr 说明符放在模板参数列表之后,返回类型之前
模板编译:
当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化(使用)模板的特定版本时,编译器才会生成代码。
通常,当我们调用一个函数时,编译器只需要掌握函数的声明。类似的,当我们使用一个类型的对象时,类定义必须是可用的。但成员函数的定义不必已经出现。因此我们可以将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中。
模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包括声明也包括定义。
大多数编译错误发生在实例化期间报告:
如,前面的 compare 函数,如果我们用一个没有定义 < 运算符的类型实例化该函数模板,则会发生编译错误
定义 find 模板:
1 #include <iostream>
2 #include <vector>
3 #include <list>
4 using namespace std;
5
6 template<typename T, typename U>
7 T find(const T &bg, const T &ed, const U &val) {
8 auto cnt = bg;
9 while(cnt != ed) {
10 if(*cnt == val) break;
11 ++cnt;
12 }
13 return cnt;
14 }
15
16 int main(void) {
17 vector<int> vt = {1, 2, 3, 4, 5};
18 list<string> lt = {"jhhg", "fjsl", "zzz", "jfl"};
19
20 auto cnt = find(vt.begin(), vt.end(), 1);
21 cout << *cnt << endl;
22
23 auto gg = find(lt.begin(), lt.end(), "zzz");
24 cout << *gg << endl;
25
26 gg = find(lt.begin(), lt.end(), "aa");
27 if(gg == lt.end()) cout << "//" << endl;
28
29 return 0;
30 }
编写接受一个数组引用参数的 print 模板:
1 #include <iostream>
2 using namespace std;
3
4 template<typename T, unsigned N>
5 void print(const T (&t)[N]) {
6 for(int i = 0; i < N; i++) {
7 cout << t[i] << " ";
8 }
9 cout << endl;
10 }
11
12 int main(void) {
13 int a[10] = {2, 2, 3};
14 print(a);
15
16 string b[3] = {"fsl", "fj", "z"};
17 print(b);
18
19 // char *ch = "jfk";//注意,不能传指针,只能传数组
20 print("fjdl");
21
22 return 0;
23 }
定义接受一个数组实参的 begin、end 模板:
1 #include <iostream>
2 #include <algorithm>
3 using namespace std;
4
5 template<typename T, unsigned N>
6 T* begin_(const T (&a)[N]) {
7 return const_cast<T*>(a);//底层const只能显式的去除
8 }
9
10 template<typename T, unsigned N>
11 T* end_(const T (&a)[N]) {
12 return const_cast<T*>(a) + N;
13 }
14
15 template<typename T, unsigned N>
16 void print(const T (&t)[N]) {
17 for(int i = 0; i < N; i++) {
18 cout << t[i] << " ";
19 }
20 cout << endl;
21 }
22
23 int main(void) {
24 int a[10] = {7, 1, 3, 3};
25 sort(begin_(a), end_(a));
26 print(a);
27
28 return 0;
29 }
编写一个 constexpr 模板,返回给定数组的大小:
1 #include <iostream>
2 using namespace std;
3
4 template<typename T, unsigned N>
5 constexpr unsigned size(const T (&t)[N]) {
6 return N;
7 }
8
9 class gel{
10 int x;
11 };
12
13 int main(void) {
14 gel g[10];
15 cout << size(g) << endl;
16
17 return 0;
18 }