浅述C++模板——函数模板及类模板

前言

模板作为 C++ 的一大特色,对于泛型编程有着重要的作用。同时,对于大规模类似的函数或是类型不确定的类,模板都起了至关重要的作用。

一、模板

在开始学习模板之前,我们首先需要了解模板。先看下面一个例子:

#include <iostream>
using namespace std;

int func_one(int num){
    return 2 * num;
}

double func_two(double num){
    return 2 * num;
}

int main(){
    int num_int = 2;
    double num_double = 2;
    
    func_one(num_int);
    func_two(num_doule);
    
    return 0;
}

我们可以轻易发现,对于函数 func_one、func_two,两者实现的功能基本相同,只是所接受的参数和返回值发生了对应的变化。因此有没有一种办法可以简化这两个函数,将其合并为一个呢?答案就是使用模板。

下面的例子是对上面的两个函数使用模板的结果:

#include <iostream>
using namespace std;

template <class T>
T func(T num){
    return 2 * num;
}

int main(){
    int num_int = 2;
    double num_double = 2;
    
    func(num_int);
    func(num_doule);
    
    return 0;
}

在上面的案例中我们见到了新的关键字 template。它表示此处运用模板,后面 <> 中则表示,有一个模板类型 T,T 在此会根据我们函数的传参自动生成对应的类型。例如 num_int 传入后 T 则代表 int; num_double 传入后 T 则代表 double。

模板就像一个空的模具,大体上不会改变,但是你提供什么参数都可以按照模具的形状进行操作

二、函数模板

在上面介绍时我们使用的模板就是函数模板,除此之外还有类模板,但是我们先从函数模板开始讲起。

2.1 模板的格式

**函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。**函数模板的诞生是为了简化重复的函数,哪怕利用重载,也需要对一个函数进行多次重载,但是模板函数只需一个模板即可适应各个种类。

函数模板的格式如下:

template <class T1, typename T2, class T3, … , typename TN>

返回值类型 函数名(参数列表){

​ 函数主体;

}

声明一个模板类型的时候,可以使用 “class” 或 “typename”,两者无差别。但是需要注意的地方在于,一个模板类型只能代表一种类型。例如在上面的函数中,T1 实例化为 int 类型后就不能再代表 char 等类型,具体如下:

#include <iostream>
using namespace std;

template <class T1>
double func_one(T1 num_1, T1 num_2){
    return num_1 + num_2;
}

template <class T1, class T2>
double func_two(T1 num_1, T2 num_2){
    return num_1 + num_2;
}

int main(){
    int num_int = 2;
    double num_double = 2;
    
    func_one(num_int, num_double);
    func_two(num_doule, num_double);
    
    return 0;
}

在这个例子中,主函数中调用 func_one 是失败的,T1 在接收 num_1 时,已经实例化为 int, 但是 num_2 为 double 类型,因此发生了错误。为接收不同类型的两个参数,我们可以考虑使用 func_two。

2.2 模板的匹配优先度

先看下面的案例,猜测系统会调用哪个函数。

#include <iostream>
using namespace std;

int func(int num_1, int num_2){
    return num_1 + num_2;
}

template <class T1>
int func(T1 num_1, T1 num_2){
    return num_1 + num_2;
}

int main(){
    int num_1 = 1;
    int num_2 = 2;
    
    func(num_1, num_2);
    func<int>(num_1, num_2);
    return 0;
}

经过调试,我们可以得知:

第一次调用 func 函数,调用的是第一个非模板的 func 函数,这是由于在可以找到匹配的函数时,优先不调用模板函数,避免重复生成一个一样的函数;

第二次调用 func 函数,调用的模板生成函数,这是由于 func(), 表示我们手动要求生成一个 func 模板函数用 int 实例化之后的函数,此时会优先采用手动要求的模板函数。

2.3 模板的声明

比起其他的函数,模板函数要求必须声明和定义在一起,不可声明与定义分离。此过程涉及到编译链接的过程,不在此展开。

三、类模板

在了解了函数模板之后,类模板也非常好理解,例如我们之后会学到的 vector 容器就是一个模板类:

#include <iostream>
using namespace std;

template <class T>
class vector{
private:
    T* ptr;
public:
    vector(T num){
        // ......
    }
}

在这个类中,构造一个类时需要传入一个类型作为参数,然后 ptr 的类型就确定了,之后的函数就可以据此继续完成。之后将以 vector 为例,讲述一个模板类的实现。

四、总结

模板的优点显而易见,一方面节省了资源,便于代码的迭代更新,另一方面增加了代码的灵活性。那是不是我们要多多使用模板呢?答案是否定的。

首先模板相比于普通的函数需要的编译时间更长,且出现错误信息时不易定位错误位置。所以具体使用重载还是模板要根据实际情况来合理进行判断选取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值