模板与泛型编程1(函数模板)
定义、实例化函数模板:
对于函数体完全相同,唯一差异就是参数类型的情况,我们可以定义一个通用的函数模板,而非为每个类型都定义一个新函数:
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 }