首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++基础篇】学习C++就看这篇--->泛型编程之模板

【C++基础篇】学习C++就看这篇--->泛型编程之模板

作者头像
HABuo
发布2025-07-15 14:24:28
发布2025-07-15 14:24:28
2070
举报

主页:HABUO🍁主页:HABUO

🍁C++入门到精通专栏🍁

🍁如果再也不能见到你,祝你早安,午安,晚安🍁

前言: 上篇博客我们学习了C++内存管理的方式,并和C语言的方式进行了对比,本篇博客我们依然是过渡篇,是为了学习后面的STL标准库做准备,本篇博客我们主要了解学习C++泛型编程的一种方式:模板。希望经过本篇博客的学习,大家有所收获!

📕一、泛型编程

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

什么意思呢?我们举个例子,假如我们想实现两个数的交换,但是我们又想交换不同类型的数,在C语言当中,我们只能通过定义不同的函数来实现,进入C++呢,以我们现在的知识,只能利用函数重载来实现同一函数名调用不同函数。

如下述一段代码:

代码语言:javascript
复制
void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}

void Swap(double& left, double& right)
{
    double temp = left;
    left = right;
    right = temp;
}

void Swap(char& left, char& right)
{
    char temp = left;
    left = right;
    right = temp;
}

观察上述代码就可以发现,虽然C++的函数重载实现了上述功能,但是代码复用率太低,有没有一种东西可以解决上述问题呢? 模板就来了

📕二、函数模板

✨2.1 函数模板的定义与使用方式

概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定 类型版本。

模板参数: 使用 template <typename T>template <class T> 声明。T 是一个占位符类型(称为类型参数),代表在编译时将被实际类型替换掉。可以使用多个类型参数(如 template <typename T, typename U>

通用函数定义: 在模板声明之后,定义一个函数。在这个函数的参数、返回值或函数体内,使用占位符类型 T 来代替具体的类型。

见下述使用:

代码语言:javascript
复制
template<class T>
template<typename T> //与上述两种方式均可, 模板参数->类型,可以写多个参数根据情况而定
                    //template<typename T1, typename T2,......,typename Tn>

void Swap(T& x1, T& x2)
{
    T x = x1;
    x1 = x2;
    x2 = x//如果此处; 我们不写是不会报错的,因为如果该函数模板 未被实例化,有些编译器是不会对模板内部进行检查的,,但是会对模板的声明做检查
          //即{}或者上面参数部分等描述错误会仍然报错  
}

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

✨2.2 函数模板的原理

如下述使用:

代码语言:javascript
复制
template<class T>
template<typename T> 
void Swap(T& x1, T& x2)
{
    T x = x1;
    x1 = x2;
    x2 = x;  
}
int main()
{
    int a = 10;
    int b = 20;
    Swap(a, b);
    double c = 1.11;
    double d = 2.21;
    Swap(c, d);
    return 0;
}

原理如下:

编译时生成: 函数模板本身不是一个可以直接调用的函数。它只是一个蓝图。

按需生成: 当编译器在代码中遇到使用该模板函数的语句时(例如 Swap(a, b);Swap(c, d);),它会:

  1. 根据调用时传递的实际参数类型(或显式指定的类型 <...>),推导出模板参数 T 的具体类型(这里是 intdouble)。
  2. 根据模板定义,生成(称为实例化)一个处理该具体类型的实际函数(例如 Swap(int a, int b)Swap(double c, double d))。

这个生成的实际函数称为模板函数函数模板的一个特化/实例

听了上面的介绍,问个问题上面我们调用的是否是同一个函数? 如果调试的时候我们可以看到,调用的是上述同一个模板,但是是同一个函数吗? 不是。 这里我们不能调用函数模板,调用的是函数模板实例化生成的对应类型的函数,可以调试的时候观察汇编代码。

所以总结一下函数模板让你只写一次函数逻辑,然后让编译器根据你调用时使用的具体数据类型,自动生成处理该类型数据的实际函数版本。 它是实现“一次编写,处处使用(不同类型)”的关键工具,也就是说事实上该有的函数一个都没有少,只不过这部分函数编译器背后帮我们去解决了,我们只写一个模板就可以了

✨2.3 函数模板的实例化

模板参数实例化分为:隐式实例化显式实例化

1️⃣隐式实例化:让编译器根据实参推演模板参数的实际类型

代码语言:javascript
复制
template<class T>
T Add(const T& left, const T& right)
{
    return left + right;
}
int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    Add(a1, a2);//这种就是模板隐式实例化,也就是说是编译器在背后自己推导出类型的
    Add(d1, d2);
    //但如果是这种形式
    //Add(a1, d1);//编译器就无法处理
    // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
    return 0;
}

2️⃣显式实例化:在函数名后的<>中指定模板参数的实际类型

代码语言:javascript
复制
int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
    Add(a1, (int)d1);
    //或者显式实例化
    Add<int>(a1, d1);// 显式实例化:在函数名后的<>中指定模板参数的实际类型
    return 0;
}

注意:如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

✨2.4 模板参数的匹配原则

1️⃣情况1 :一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函 数

代码语言:javascript
复制
 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}

void Test()
{
	Add(1, 2);// 与非模板函数匹配,编译器不需要实例化,也就是说,编译器能调用已经存在的就不会再费劲的去模板实例化
	Add<int>(1, 2);// 调用编译器实例化的Add版本
}

2️⃣ 情况2:对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

代码语言:javascript
复制
void main()
{
	Add(1, 2);// 与非函数模板类型完全匹配,不需要函数模板实例化
	Add(1, 2.1);// 编译器会优先选择非模板函数int Add(int, int),而非函数模板。
				//也就是说这要非模板函数能匹配成功,但是这是在模板函数参数不匹配的情况下,我们退其次才选择丢失精度的非模板函数
	            //但如果只要template<class T1, class T2>,Add(T1 left, T2 right),模板能完全匹配的情况下就不会调用非模板函数
				//即重载决议优先级:完全匹配(模板) > 需要转换的匹配(非模板),编译器优先选择参数类型完全匹配的模板函数
				//编译器会自我判断哪种情况更好
	//模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
}

对于Add(1,2.1) 解释如下:

1. 函数重载解析规则

  • 当存在模板和非模板的重载函数时,编译器优先考虑非模板函数(如果匹配成功)。
  • 非模板函数int Add(int, int)要求两个int参数。调用时传入的1int类型)完全匹配第一个参数,而2.1double类型)可通过隐式类型转换doubleint)匹配第二个参数。因此非模板函数是可行的。

2. 模板函数的限制

  • 函数模板T Add(T left, T right)要求两个参数类型必须相同(均为T)。
  • 调用Add(1, 2.1)传入intdouble,编译器无法推导出唯一的TT不能同时满足intdouble),导致模板参数推导失败。因此模板函数不被考虑。

📕三、类模板

背景:如我们C语言当中定义一个栈

代码语言:javascript
复制
typedef int StackDataType;
typedef struct Stack
{
    StackDataType* _Array;
    int _top;
    int _capacity;
}Stack; 

这就有一个问题,我们如果有这也的应用场景,一个栈存int,一个栈存double,怎么办是不是必须要定义两个,那代码量是不是陡然提升

在C语言当中是没法解决这也的问题的

所以模板类就可以解决了

✨3.1 类模板的定义格式

代码语言:javascript
复制
template <class T>
class Stack_CPP
{
public:
    Stack_CPP()
    {}
    ~Stack_CPP()
    {}
    void Push(T x)
    {}
private:
    T* _a;
    int _size;
    int _capacity;
};
int main()
{
    Stack_CPP<int> st_cpp_int;//和类实例化的唯一区别就是在类型后面加<模板类型参数>
    st_cpp_int.Push(1);//注意:这里和类实例化没有任何区别,仍然是有两个参数,有个隐含的this指针
    st_cpp_int.Push(2);
    Stack_CPP<double> st_cpp_double;//模板类就可以轻松解决上述问题
    st_cpp_double.Push(1.1);
    st_cpp_double.Push(2.1);
}

细节注意

1️⃣类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类

2️⃣假设我们定义一个类外面的函数如下:

只要在类外面定义,每个函数前面都要写template<class T>

代码语言:javascript
复制
template<class T>//只要在类外面定义,每个函数前面都要写template<class T>
void Stack_CPP<T>::push(const T& x)//此时表示域不是Stack_CPP::了,而是Stack_CPP<T>::

至此我们可以总结一下:C++解决了哪些C语言当中的问题 1. 忘记初始化和销毁 ->构造函数+析构函数 2. 没有封装,谁都可以修改结构体的数据 ->类+访问限定符 3. 如果想同时定义两个栈 ->模板


📕四、总结

本篇博客我们了解学习了 C++模板是编写与类型无关的通用代码的泛型编程手段。函数模板能为不同数据类型生成函数实例,通过 template<typename T>template<class T> 声明,使用时编译器按需实例化。类模板用于定义通用类,如模板类实现不同数据类型的栈结构,实例化时需指定类型。模板提升了代码复用性和灵活性,但要注意模板参数匹配原则,如非模板函数与同名模板函数同时存在时,编译器会优先选择匹配度更高的函数。掌握模板使用能有效减少重复代码,提高开发效率。


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 📕一、泛型编程
  • 📕二、函数模板
    • ✨2.1 函数模板的定义与使用方式
    • ✨2.2 函数模板的原理
    • ✨2.3 函数模板的实例化
    • ✨2.4 模板参数的匹配原则
  • 📕三、类模板
    • ✨3.1 类模板的定义格式
  • 📕四、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档