C++ 模版函数 深入浅出

本文深入浅出地介绍了C++中的模板函数,包括其声明、使用方法及模板函数特化等内容,并通过具体示例展示了如何利用模板函数实现通用排序算法。

假设我们是一个实验室的学生,实验室老板喊你写一个排序算法排序一个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)

差不多就讲到这里,关于模版类的详细讲解我会在下一个博客讲到

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值