标签:
要讲模板函数,我们先来看个例子,假如老板要让你写一个排序函数,假定最开始用户的要求可能只是 int .那么你花了大把功夫写了一段代码:
void Sort(int* begin, int* end, bool(*cmp)(int, int)) //第三个参数是优先级函数,在这里我就简单用的冒泡排序。如果不会排序的请不要深究排序实现过程。 { int* i, *j; for (i = begin; i != end; i++) { int* max = i; for (j = i + 1; j != end; j++) { if (cmp(*j, *max)) max = j; } int temp = *max; *max = *i; *i = temp; } } bool k(int a, int b) { return a > b; } int main() { int arr[6] = { 1, 5, 3, 2, 6, 4 }; Sort(arr, arr + 6, k); for (int i = 0; i < 6; i++) cout << arr[i] << " "; cout << endl; return 0; }
然后你又屁颠屁颠跑去敲:
void Sort(double* begin, double* end, bool(*cmp)(double, double)) { double* i, *j; for (i = begin; i != end; i++) { double* max = i; for (j = i + 1; j != end; j++) { if (cmp(*j, *max)) max = j; } double temp = *max; *max = *i; *i = temp; } }
这下你傻眼了:我靠,这要写多少个重载呀,这个时候你该怎么办呢? 好吧,我来告诉你:
用模板!!!
经过上面这个例子大家对模板这个语法糖的出现的必要性可能不存有怀疑了。(不然那么多重载谁来写啊。又麻烦,还容易出错)。
一.模板函数的定义
那么我们来看下下面这个简单的模板例子:
template<typename T> void Say(T a) { cout << "这就是; " << a << endl; } int main() { Say(1); Say(2.3); Say('a'); Say("爱情"); return 0; }
好,看到这里大家看到了模板函数的好处,那么我们怎么使用模板函数呢?
我们对于上面那个例子:我们先只是让这个Say 只能传入 int 类型,那么代码就是:
void Say(int a) { cout << "这就是; " << a << endl; }
模板出现就是源于这种思想,我们来看,在刚才的函数之前加上一句话,template<typename T> 。
这句话的意思是:
1.表示接下来这个函数是一个模板函数.
2.这个函数有一个类型参数T.
那么是不是我们函数的参数列表中的int a 的 int 就可以被 T 这个类型参数去替换了?
template<typename T> //这里的typename可以写为class,但是尽量使用typename void Say(T a) { cout << "这就是; " << a << endl; }
//注意:这个函数原型是由编译器在遇到Say('a')之后才自动生成的.所以Say('a')调用的其实是下面这个函数。 void Say(char a) { cout << "这就是; " << a << endl; }
从上面这句话我们可以看出来我们调用模板函数的时候可以不用像实参一样给出类型参数。编译器会自动根据实参类型来判断。
类型参数可以不仅仅为一个可以多个。
template<typename T,typename F> void Func(T a,F b) { }
而且在模板参数列表中除了可以定义类型参数,还可以定义普通的参数。不过如果是普通的参数那么在函数里这个变量为常量,并且调用函数的时候必须显式给出类型:
template<typename T,int num> void Func(T arr) { for (int i = 0; i < num; i++) cout << arr[i] << " "; cout << endl; } int main() { int arr[5] = { 1, 2, 3, 4, 5 }; Func<int*, 3>(arr); //这里必须在<>内显式给出类型 return 0; }
1.模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。
2.对于模板参数列表中如果定义的是普通的参数,那么传入实参的时候只能给常量表达式。并且该参数作为常量在函数中存在。
3.变量覆盖问题:全局变量 < 模板参数列表的变量 < 函数参数列表的变量(函数内部的局部变量) . 左部的变量可以被右部的同名变量所覆盖。
4.模板参数名在同一模板参数表中只能被使用一次,但是模板参数名可以在多个函数模板声明或定义之间被重复使用。
5.如同非模板函数一样函数模板也可以被声明为inline 或extern 应该把指示符放在模板参数表后面而不是在关键字template 前面。
好到了这里想必你已经能很好的解决老板留下给你的问题了吧。下面是代码:
template<typename T> void Sort(T begin, T end, bool(*cmp)(T, T)) { T i, j; for (i = begin; i != end; i++) { T max = i; for (j = i + 1; j != end; j++) { if (cmp(j, max)) max = j; } auto temp = *max; *max = *i; *i = temp; } } bool Cmp1(double* a, double* b) { return *a > *b; } bool Cmp2(vector<int>::iterator a, vector<int>::iterator b) { return *a > *b; } int main() { double arr[6] = { 1, 5, 3, 2, 6, 4 }; Sort(arr, arr + 6, Cmp1); //可以对数组排序 vector<int> varr = { 1, 5, 3, 2, 6, 4 }; Sort(varr.begin(), varr.end(), Cmp2); //也可以对容器排序 for (int i = 0; i < 6; i++) cout << arr[i] << " "; cout << endl; for (int i = 0; i < 6; i++) cout << varr[i] << " "; cout << endl; return 0; }
二.模板函数的特化(显式具体化)和显式实例化
话说上次你巧妙利用模板的特性快速的解决了用户的需求,老板对此对你大加赞赏。
这次,老板又找到你了,说有个用户想让你帮他们写一个加法函数(如果是字符串应当作为连接函数)。对于传进来的两个参数实现相加后作为返回值能够传出。
你想了想,这不简单吗?模板函数已经学会,这样的要求简直是小case。所以不一会儿你就写好了代码:
template<typename T> T void Add(T a, T b) { return a + b; }
当你准备交给老板的时候你想了想,不对啊,如果传入的是 char*(c风格的字符串)怎么办呢?无法将两个char*相加呀!
所以在这里你需要将char*这个特殊的类型来单独处理! 为了解决这样的问题,模板函数给出了对应解决问题的方案———— 显式具体化
显示具体化的语法是这样的:
//注意函数模板显式中是不存在类型参数的 template<> //这里template后面跟着的<>表示这是Add函数模板的一个实例化 char* Add<char*>(char* a, char* b) //这里的函数名后面跟了一个<> 里面填写的是特化的指定类型. { char* newStr = new char[strlen(a) + strlen(b) + 1]; for (int i = 0; i < strlen(a); i++) newStr[i] = a[i]; for (int i = 0; i < strlen(b); i++) newStr[i + strlen(a)] = b[i]; newStr[strlen(a) + strlen(b)] = '\0'; return newStr; }
// C++Test.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <typeinfo> #include <string.h> #include <vector> using namespace std; template<typename T> T Add(T a, T b) { return a + b; } //注意函数模板显式中是不存在类型参数的 template<> //这里template后面跟着的<>表示这是Add函数模板的一个实例化 char* Add<char*>(char* a, char* b) //这里的函数名后面跟了一个<> 里面填写的是特化的指定类型. { char* newStr = new char[strlen(a) + strlen(b) + 1]; for (int i = 0; i < strlen(a); i++) newStr[i] = a[i]; for (int i = 0; i < strlen(b); i++) newStr[i + strlen(a)] = b[i]; newStr[strlen(a) + strlen(b)] = '\0'; return newStr; } int main() { Add(1, 3); Add("123", "123"); return 0; }
我们还是来看下上面那个加法的函数模板:
template<typename T> T void Add(T a, T b) { return a + b; }
噢,有的人就可能会这样想:int 类型的可以隐式转化为 double 所以其实调用的是 Add<double>(2,2.3);
其实不是这样的,在函数模板的实参和形参匹配中是利用了实参推演的。意思就是说。调用Add(2,2.3)的时候,编译器会先到非模板函数中去找,发现找不到的时候就会到模板函数去找可以实例化为这样一个函数的模板 Add(int,double) .发现找不到,于是报错!!
是不是这样就没有解决办法呢? 其实是有的。我们可以显式去指定模板类型参数!!
Add<double>(2,2.3). 这样的话 编译器就不会进行实参推演,直接根据前面给出的double生成Add模板的double实例化。然后再将实参传入进去。这时候int就可以进行隐式转化了。
template<typename T> T Add(T a, T b) { return a + b; } int main() { Add<double>(1, 2.3); //这里叫做显式实例化。 return 0; }
对于下面这个加法模板我们可以这样调用 Add(‘a’,string(‘bc‘)) 或者 Add(string("ab"),‘c‘).
<pre name="code" class="cpp">template<typename T,typename F> ___ Add(T a, F b) { return a + b; }
比如:我这样调用的时候Add(‘a’,string(‘bc‘)) 对应的 T 为 char ,F 为 string .那么返回值类型就为 F(string).相反如果我这样调用Add(string("ab"),‘c‘). 返回值类型就为T.
所以我们更希望我们的模板能智能地识别到应该返回的类型:所以有了后置返回类型的语法
template<typename T,typename F> auto Add(T a, F b) -> decltype(a+b) { return a + b; }
一般函数模板都是放在头文件申明中,而如果有特殊的类型需要让函数有特别的操作即可在源文件中进行显式具体化。这样的话调用该函数,编译器就会先到显示具体化中去找匹配函数。
那么编译器如何知道去哪儿找定义或者说,有多个可以使用的定义的时候优先选择哪个呢?
详情请看 ———————— 重载解析
标签:
原文地址:http://blog.csdn.net/y1196645376/article/details/51295160