当前位置: 首页 > news >正文

【C++】模板初阶

目录

  • 一、泛型编程
  • 二、函数模板
    • 1、函数模板概念
    • 2、函数模板格式
    • 3、函数模板原理
    • 4、函数模板的实例化
    • 5、模板参数的匹配原则
    • 6、类模板
      • 6.1 类模板的定义格式
      • 6.2 类模板的实例化

在这里插入图片描述

个人主页<—请点击
C++专栏<—请点击

一、泛型编程

如何实现一个通用的交换函数Swap呢?我们可以通过函数重载来完成。

void Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}
void Swap(double& a, double& b)
{double tmp = a;a = b;b = tmp;
}
void Swap(char& a, char& b)
{char tmp = a;a = b;b = tmp;
}

使用函数重载虽然可以实现,但是有一下几个不好的地方:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?就像浇筑一样:
在这里插入图片描述

C++中存在这样的方法,就是模板C++中的模板(Templates)泛型编程的核心实现方式,泛型编程(Generic Programming)是一种编程范式,旨在通过将数据类型参数化,使代码(如类、方法、算法等)能够在不针对特定数据类型的前提下实现复用,同时保证类型安全。其核心思想是将 类型作为参数传递给代码实体,从而让同一逻辑可以适用于多种不同的数据类型,避免为每种类型重复编写相似代码

二、函数模板

1、函数模板概念

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

2、函数模板格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}

此时我们就可以使用函数模板来实现Swap函数:

template<typename T>
void Swap(T& a, T& b)
{T tmp = a;a = b;b = tmp;
}

执行的代码段:

int i = 1, j = 2;
Swap(i, j);
double x = 1.1, y = 2.2;
Swap(x, y);

在这里插入图片描述
可以看到它们成功被交换了。

注意typename可以被替换成class,但不能替换成struct,其中的T不是固定格式,你可以替换成你想替换的字母或单词,一般习惯用大写。

3、函数模板原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器,相当于你现在是包工头而不是工人了。
在这里插入图片描述

4、函数模板的实例化

上面2中讲的这种是隐式实例化,也就是实参类型推演模板参数,当你像下面这样写时编译器就推演不出参数了:

int i = 0;
double x = 1.1;
Swap(i, x);

一个是int一个是double,编译器此时就懵了,会报出以下错误:
在这里插入图片描述
为了让讲的例子更合理,我们再写出一个模板Add来讲解:

template<typename T>
T Add(const T& a, const T& b)
{return a + b;
}

执行的代码段:

int i = 0;
double x = 1.1;
cout << Add(i, x) << endl;

报错:
在这里插入图片描述
处理方案1:强转
隐式实例化:让编译器根据实参推演模板参数的实际类型

// 隐式实例化(实参类型推演模板参数)
cout << Add(i, (int)x) << endl;
cout << Add((double)i, x) << endl;

在这里插入图片描述
注意:由于Add中的参数是引用,强转时产生的临时变量具有常性,所以必须使用const引用,或者你不用引用,那就不需要加const

处理方案2:显示实例化
显式实例化:在函数名后的<>中指定模板参数的实际类型

//显示实例化(显示指定模板参数)
cout << Add<int>(i, x) << endl;
cout << Add<double>(i, x) << endl;

在这里插入图片描述
处理方案3:多加一个typename

template<typename T,typename t>
T Add(T& a, t& b)
{return a + b;
}
int i = 0;
double x = 1.1;

在这里插入图片描述

5、模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
// 专门处理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版本Add<int>(1, 2);
}
  1. 对于非模板函数同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个更加匹配的函数, 那么将选择模板。
// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}
void Test()
{// 与非函数模板类型完全匹配,// 不需要函数模板实例化Add(1, 2); // 模板函数可以生成更加匹配的版本,// 编译器根据实参生成更加匹配的Add函数Add(1, 2.0); 
}
  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

模板函数的类型推导机制

模板函数在调用时需要通过实参自动推导模板参数编译器要求实参类型与模板参数类型严格匹配,不进行自动类型转换,除非:

  • 模板参数被显式指定(如 func<int>(3.14))。
  • 使用了const引用指针类型(如 T& 允许int到const int的转换)。

原因:模板的设计目标是实现完全泛型的代码,类型推导必须精确。如果允许自动转换,可能导致意外行为(如int与double相加时类型不一致)

普通函数的重载解析与类型转换

普通函数通过重载决议选择最佳匹配函数,并允许隐式类型转换:

  • 编译器会根据实参与形参的匹配程度排序候选函数。
  • 若存在多个可行函数,会选择转换代价最小的函数

标准转换的优先级
在同一等级的标准转换中(如int→double和double→int)C++进一步区分:

  • 类型提升(如int→long)优先于类型转换(如int→double)
  • 值保留转换(如int→double)优先于可能损失值的转换(如double→int)

原因:普通函数的重载机制允许为不同类型提供专门实现,而隐式转换是C++语言的基础特性(如int到double的转换)。

6、类模板

6.1 类模板的定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};

我们以Stack为例写类模板:

template<class T>
class Stack
{
public:Stack(int n = 4):_a(new T[n]),_top(0),_capacity(n){ }void push(T x);~Stack(){delete[] _a;_a = nullptr;_top = 0;_capacity = 0;}
private:T* _a;int _top = 0;int _capacity = 0;
};

这就是类模板,假设我们要在类外面实现里面声明的函数push时,我们需要这样实现:

template<class T>
void Stack<T>::push(T x)
{//..._a[_top++] = x;
}

也需要在函数上面加上template<class T>,并且要声明在Stack<T>类域中。

6.2 类模板的实例化

类模板实例化函数模板实例化不同,类模板实例化必须显示实例化,需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

Stack<int> s1;
s1.push(1);
s1.push(2);
s1.push(3);
Stack<double> s2;
s2.push(1.1);
s2.push(2.2);
s2.push(3.3);

Stack类名Stack<int>才是类型
在这里插入图片描述

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

http://www.xdnf.cn/news/385777.html

相关文章:

  • 从零开始开发纯血鸿蒙应用之XML解析
  • 《AI大模型应知应会100篇》第58篇:Semantic Kernel:微软的大模型应用框架
  • 计算机网络|| 常用网络命令的作用及工作原理
  • 张量并行优质博客
  • 【东枫科技】使用LabVIEW进行深度学习开发
  • 面试中常问的设计模式及其简洁定义
  • 【React】Craco 简介
  • JavaScript 循环语句全解析:选择最适合的遍历方式
  • 客服系统重构详细计划
  • 如何选择 RabbitMQ、Redis 队列等消息中间件?—— 深度解析与实战评估
  • 御网杯2025 Web,Msic,密码 WP
  • Docker、ECS 与 K8s 网段冲突:解决跨服务通信中的路由问题
  • [思维模式-30]:《本质思考力》-10-产品研发的两种模式:①自顶向下的规划、分解、牵引;②自底向上的堆叠、聚合。
  • Win全兼容!五五 Excel Word 转 PDF 工具解决多场景转换难题
  • MyBatis快速入门——实操
  • spark运行架构及核心组件介绍
  • spark-Schema 定义字段强类型和弱类型
  • 06.three官方示例+编辑器+AI快速学习webgl_animation_skinning_additive_blending
  • openharmony系统移植之gpu mesa3d适配
  • [Java][Leetcode middle] 80. 删除有序数组中的重复项 II
  • 【MySQL】页结构详解:页的大小、分类、头尾信息、数据行、查询、记录及数据页的完整结构
  • MySQL InnoDB 表空间详解
  • numpy模块综合使用
  • 罗技无线鼠标的配对方法
  • 什么是具身智能
  • 关于物联网的基础知识(二)——物联网体系结构分层
  • 在python中,为什么要引入事件循环这个概念?
  • 图形化编程革命:iVX携手AI 原生开发范式
  • 电池单元和电极性能
  • AI大模型学习十八、利用Dify+deepseekR1 +本地部署Stable Diffusion搭建 AI 图片生成应用