假设我们是一个实验室的学生,实验室老板喊你写一个排序算法排序一个int数组
当然,以我们的聪明才智,很快的就写好了这个函数:
bool cmp(const int& a,const int& b)
{
//a>=b返回true,a<b返回false
return a >= b ;
}
void Sort(int * begin,int * end, bool(*cmp)(const int&, const int&))
{
int* i;
int* 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;
}
}
岂不美哉,马上去交差
但过了一两天,老板告诉你,我这边想要排序double类的数据了
好吧,改呗,然后你又花了几分钟改成了如下的代码:
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;
}
}
这当然很简单啦,甚至你还进行了函数重载,排序double就自动调用这个,厉害了,去交差。
几天之后,老板又告诉你,我不仅想排序基本数据类型了,还想排序string、char*和各种的类、结构体,你当时就有点懵逼了,这得写死我,然后你去网上查如何实现同种算法解决不同类型数据的C++特性,然后你发现了:
模版函数
通过以上这么苦逼的实验室例子,我们已经知道了,如果对所有数据类型都重载一遍函数,那么我们就真的会成为一个码农,而程序员要做的就是将能交给计算机做的统统交给计算机去做。
好,接下来我就讲一下模版函数的大部分用法和特性吧:
模版函数声明方法:
template<typename 数据类型泛化名称(可多个)>
返回类型 函数名 (参数表)
{
函数体
}
一个简单的例子:
template<typename T>
void Print(T par)
{
cout << "Print : " << par << endl;
}
int main()
{
Print("hello"); //调用Print<const char*>();
Print(1); //调用Print<int>();
Print(5.6); //调用Print<double>();
string str("eihei");
Print(str); //调用Print<std::string>();
return 0;
}
当我们用上template 关键字的时候,代表我们接下来写的东西都是泛型的,比如template<typename T>这句就是说现在我们要写一个内容,可能是类或者函数之类,它的一个类型参数为 T(可以为任何类型,可以为多个类型):template<typename T,typename S> 也是可以的
当我们调用这个函数名的时候,实际上做了什么事情呢?比如我们这样用:
Print(5.6);
它就会根据你传入的参数类型,生成对你这个参数特化的函数,比如上面例子的特化函数如下:
void Print(double par)
{
cout << "Print : " << par << endl;
}
模板函数类似于重载函数,但两者有很大区别:函数重载时,每个函数体内可以执行不同的动作,但同一个函数模板实例化后的模板函数都必须执行相同的动作。
我们应该要知道,模版函数并不是真正的生成了无数多的函数,而是在根据你传入的参数,自动代替类型泛化名称,然后产生对应的函数。
关键字typename也可以使用关键字class,这时数据类型参数标识符就可以使用所有的C++数据类型。
函数的参数列表和泛化类型列表中不仅仅可以有泛化类型,也可以用自己的类型:
如下:
template<class T,int C >
void func(T a,int b, string str)
{
}
参数列表的东西我们可以理解,但是这个泛化类型列表里面的基础类型有什么用呢?还不如直接放参数列表里面呢。卖个关子
如下代码:
template<class T,int size >
class tem {
private:
T *a[size];
};
int main()
{
tem<int, 5> test;
return 0;
}
我们用这种方式就可以在初始化类的时候传入自定参数,而不用通过构造函数。
当然,这一个博客只讲模版函数,有空讲类。
所以啦,这时候我们如果遇到老板要求我们再做刚才的函数,我们就可以这样写:
template<class T>
bool cmp(const T& a,const T& b) {//a>=b返回true,a<b返回false
return a >= b;
}
template<class T>
void Sort(T * begin,T* end, bool(*cmp)(const T&, const T&)) {
T* i;
T* j;
for ( i = begin; i != end; i++) {
T *max = i;
for (j = i + 1; j != end; j++) {
if (cmp(*j, *max))max = j;
}
T temp = *max;
*max = *i;
*i = temp;
}
}
但是你一想,这样也不行啊,只能对基础类型进行排序,结构体这些遇到了就没办法了呀。
这时候cmp函数的作用就体现出来了,又引出了一个概念,叫做:
模版函数特化
比如我们接到任务,要对以下这样的数据进行排序:
struct Data
{
char *s;
int a;
string n;
};
我们要根据结构体中 a 的大小进行一个排序,怎么办?这样写:
template<>
bool cmp<Data>(const Data& a, const Data& b)
{
return a.a >= b.a;
}
这样我们就对刚才的模版函数cmp进行了一个关于Data数据的特化版本,这个版本的意义何在呢,就是割掉原来的模版函数cmp中的Data数据的比较方法,而在调用到Data数据的时候,使用上面的方法。
其实也有点相当于重载了,但是这次我们只需要重载一个小小的cmp函数就够了。
特化版本格式如下:
template<>
返回类型 函数名<特化类型> (参数列表)
{
函数主体
}
这样,你就可以根据老板提出的需求,写一个特化版本就行,甚至对vector、list中的数据同样奏效,实际上,STL库中的sort也是用的模版函数
到这里已经讲的差不多了,我们用一个小小的例子再讲一下关于模版函数的特化吧
比如老板喊你写一个函数,能够将两个数据相加之后返回相加之后的数据,你一想,学了模版函数之后,这些也太简单了吧,花了一分钟,写好了代码:
template<class T>
T Add(T a,T b)
{
return a + b;
}
好嗷,该交差了吧。
但是你一想,不对,要是是两个char* 类型怎么办?
不行,得改,还好我们已经学了模版函数特化,花了几分钟:
template<>
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;
}
这样就可以了吧,拿去交差,老板看了都说好。
回到刚才的Add函数,考虑如下情况:
Add(5,4.8);
这样会调用哪一个函数?Add<double>还是Add<int> 进行隐式转换呢,答案是:都不是。
在调用的时候编译器先会去找符合这个条件非模版函数,发现没有,然后回去寻找模版函数,还有没有,于是报错。
我们可以这样使用:
Add<double>(15, 4.8);
我们还知道STL中的string是可以和char和char*相加的,并且相加之后得到string,我们该如何设计上面的代码呢?
template<typename T,typename F>
auto Add(T a, F b) -> decltype(a+b)
{
return a + b;
}
大家可以看到,这里的返回值不是T也不是F,而是T和F相加之后应该得到的值的类型。
比如如果是double加上int类型,返回的自然就是double类型。
如果为string和char*之类,返回的就是一个string类型。
具体的我就不写了,关于decltype 详细介绍的大家可以去看这个博客:c++11新特性–decltype,或者我自己也可能会写一个这样的博客。
这就叫做后置返回类型。很有意思吧。
当然我们也可以用如下的形式自行显式指定返回类型,比如一个简单的求和函数:
template <class T,class F,class S>
T sum(const F& a,const S& b)
{
return a + b;
}
//T是显式指定,F、S是从函数实参类型推导而来
auto result = sum<long>(5, 7l);//调用long sum(int,long)
差不多就讲到这里,关于模版类的详细讲解我会在下一个博客讲到
本文深入浅出地介绍了C++中的模板函数,包括其声明、使用方法及模板函数特化等内容,并通过具体示例展示了如何利用模板函数实现通用排序算法。
983

被折叠的 条评论
为什么被折叠?



