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

前言

模板作为 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,两者实现的功能基本相同,只是所接受的参数和返回值发生了对应的变化。因此有没有一种办法可以简化这两个函数,将其合并为一个呢?答案就是使用模板。

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

C++ 复制代码
#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 等类型,具体如下:

C++ 复制代码
#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 模板的匹配优先度

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

C++ 复制代码
#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 容器就是一个模板类:

C++ 复制代码
#include <iostream>
using namespace std;

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

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

四、总结

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

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

相关推荐
好大哥呀11 分钟前
C++ Web 编程
开发语言·前端·c++
Mr_Xuhhh1 小时前
LeetCode hot 100(C++版本)(上)
c++·leetcode·哈希算法
漫随流水1 小时前
c++编程:反转字符串(leetcode344)
数据结构·c++·算法
南境十里·墨染春水1 小时前
C++ 笔记 友元(面向对象)
开发语言·c++·笔记
C++ 老炮儿的技术栈2 小时前
分享一个安全的CString
c语言·c++·windows·git·安全·visual studio
桦02 小时前
[C++复习]:STL
开发语言·c++
苏宸啊3 小时前
rbtree封装map和set
c++
汉克老师4 小时前
GESP2025年6月认证C++三级( 第一部分选择题(1-8))
c++·二进制·原码·补码·gesp三级·gesp3级·八进制、
不想写代码的星星4 小时前
C++ 折叠表达式:“我写递归你写折叠,咱俩代码差十年”
c++
Titan20245 小时前
map和set的封装学习笔记
数据结构·c++