C++: 模板初阶
文章目录
- 一. 泛型编程
- 二. 函数模板
- 函数模板的原理
- 函数模板的实例化
- 隐式实例化: 让编译器根据实参推演模板参数的实际类型
- 显示实例化: 在函数名后的<>中制定模板参数的世纪类型
- 模板参数的匹配原则
- 三. 类模板
- 类模板的定义格式
- 类模板的实例化
一. 泛型编程
如何实现一个通用的交换函数呢? 参数是不同类型的数据.
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++提供了模板的语法, 给这个"模具"中填充不同的类型, 就可以生成具体类型的代码
泛型编程: 编写与类型无关的通用代码, 是代码复用的一种手段. 模板是泛型编程的基础.

二. 函数模板
针对上面 Swap 函数有多种类型参数的情况, 可以定义一个通用的函数模板(function template), 而不是为每个类型都定义一个新函数.
一个函数模板就是一个公式, 可用来生成针对特定类型的函数版本. Swap 的模板版本可能像下面这样:
template <typename T> // 模板定义格式: template<typename T1, typename T2, ... , typename Tn>
void Swap(T &left, T &right)
{T temp = left;left = right;right = temp;
}
模板定义以关键字 template 开始, 后面跟一个模板参数列表, 这是一个用逗号分割的一个或多个模板参数的列表, 用小于号 < 和 大于号 > 包围起来.
在模板定义中, 模板参数列表不能为空.
模板参数列表的作用很像函数参数列表.
函数参数列表定义了形参对象, 模板参数列表定义了类型.
模板参数表示类或函数定义中用到的类型或值. 当使用模板的时候, 显示或隐式地制定模板实参, 将其绑定到模板参数上.
比如上述的 Swap 函数声明了两个名为 T 的类型参数, T 表示一个类型, T 的实际类型则在编译时根据传入的参数确定.
对于不同的参数类型, 最终调用的函数参数类型也不同

函数模板的原理
函数模板是一个蓝图, 它本身并不是函数, 是编译器用使用方式产生特定具体类型函数的模具. 所以其实模板就是将本来应该我们做得重复的事情交给了编译器.

在编译器编译阶段, 对于函数模板的使用, 编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用.
例如: 当用 double 类型使用函数模板时, 编译器通过对实参类型的推演, 将 T 确定为 double 类型, 然后产生一份专门处理 double 类型的代码
注意 函数模板的模板参数类型可以写 typename, 也可以写 class (不可以写 struct).
函数模板的实例化
用不同类型的参数使用函数模板时, 称为函数模板的实例化.

隐式实例化: 让编译器根据实参推演模板参数的实际类型
template <typename T>
T Add(const T &left, const T &right)
{return left + right;
}int main()
{int a1 = 10, b1 = 20;Add(a1, b1); // 推演出模板参数类型为 intdouble a2 = 1.1, b2 = 2.2;Add(a2, b2); // 推演出模板参数类型为 doubleAdd(a1, b2); // 推演失败return 0;
}
前两个可以通过编译, 函数模板类型相同, 编译器可以推演出函数模板的类型.
第三个传入了一个 int 类型和一个 double 类型, 编译器推演模板参数类型失败.

因为定义模板函数的时候, 规定了模板函数参数只有一个类型 T, 传入两个类型, 编译器不能确定应该是将 T 演绎成哪个类型.
在模板中, 编译器不会进行类型转换操作, 一旦转换出问题, 编译器就会背黑锅, 所以不会隐式转换.
有两种处理方式:
- 用户自己进行强化类型转换
- 使用显示实例化
Add(a1, (int)b2); // 用户自己强制类型转换
显示实例化: 在函数名后的<>中制定模板参数的世纪类型
int main()
{int a1 = 10;double b = 20.0;Add<int> (a1, b2); // 显示实例化return 0;
}
如果类型不匹配, 编译器会进行隐式类型转换, 如果无法转换成功, 编译器会报错.
如果函数参数没有模板参数, 那么就无法使用隐式实例化, 只能进行显示实例化.
// 申请一个T类型十个元素的数组并返回
template <typename T>
T *f()
{T *p = new T[10];return p;
}int main()
{int *p1 = f<int>();double *p2 = f<double>();return 0;
}
模板参数的匹配原则
- 一个非模板函数和一个同名的函数模板同时存在, 而且该函数模板可以被实例化为这个非模板函数
// 专门处理 int 类型的加法函数
int Add(const int &left, const int &right)
{cout << "(int) Add" << endl;return left + right;
}// 通用加法函数
template <typename T>
T Add(const T &left, const T &right)
{cout << "(template) Add" << endl;return left + right;
}int main()
{Add(1, 2); // 有现成的优先用现成的Add<int>(1, 2); // 如果指定显示实例化, 用函数模板
}

- 对于非模板函数和同名函数, 如果其他条件都相同, 在调用时会优先调用非模板函数而不会从该模板产生一个实例. 如果模板可以产生一个具有更好匹配的函数, 那么将选择模板.
// 专门处理 int 类型的加法函数
int Add(const int &left, const int &right)
{cout << "(int) Add" << endl;return left + right;
}// 通用加法函数
template <typename T1, typename T2>
T1 Add(const T1 &left, const T2 &right)
{cout << "(template) Add" << endl;return left + right;
}int main()
{Add(1, 2); // 与非函数模板完全匹配, 不需要函数模板实例化Add(1, 2.0); // 有更合适的不会进行隐式类型转换调用非函数模板, 而会直接调用函数模板实例化的实例
}

- 函数模板不允许自动类型转换, 而普通函数可以进行自动类型转换
// 专门处理 int 类型的加法函数
int Add(const int &left, const int &right)
{cout << "(int) Add" << endl;return left + right;
}// 通用加法函数
template <typename T>
T Add(const T &left, const T &right)
{cout << "(template) Add" << endl;return left + right;
}int main()
{Add(1, 2.0); // 函数模板不能进行自动类型转换, 只能调用普通函数, 传参的时候进行自动类型转化
}

总结
- 优先调用现有的普通函数
- 没有函数模板, 普通函数参数可以自动类型转换的, 使用普通函数.
- 可以通过函数模板实例化更合适的函数, 哪怕普通函数可以自动类型转换, 也用函数模板.
三. 类模板
以前写数据结构的时候, 通常会用 typedef 重命名数据结构内数据的类型.
例如 Stack 中用 typedef int STDataType, 制定 Stack 中的数据类型是 int 类型的.
但是, 这样只能保证一次只能用 int, 如果需要用到 double 的, 需要再重新写一份.
有了类模板就能很好的解决这个问题.
类模板的定义格式
template <class T1, class T2,...,class T3>
class 类模板名
{// 类内成员定义
};
例如: Stack
template <class T>
class Stack
{
public:Stack(size_t capacity = 10): _array(new T[capacity]), _capacity(capacity), _top(0){}~Stack();void push(const T &data);//...
private:T *_array;int _capacity;int _top;
};// 类外定义成员函数必须要加模板参数列表
template <class T>
Stack<T>::~Stack()
{delete[](_array);_capacity = _top = 0;
}
类外定义成员函数, 必须要加上模板参数列表声明, 并且指定类域时必须加上模板参数列表.
同时, 类模板成员函数的声明与定义必须放在同一个文件中, 否则编译器会链接失败.
类模板的实例化
类模板实例化与函数模板实例化不同, 类模板都是显示实例化, 需要在类模板名字后面跟<>, 然后将实例化的类型放在<>中即可, 类模板名字不是真正的类, 而实例化的结果才是真正的类
Stack<int> s1;
Stack<double> s2;
普通类的类名即是类型, 而类模板的类名不是类型, 类名<数据类型> 才是整个类的类型.
Stack<int> 和 Stack<double> 不是同一个类, 他们只是同一类模板显示实例化生成的不同类.
本章完.
相关文章:
C++: 模板初阶
文章目录 一. 泛型编程二. 函数模板函数模板的原理函数模板的实例化隐式实例化: 让编译器根据实参推演模板参数的实际类型显示实例化: 在函数名后的<>中制定模板参数的世纪类型 模板参数的匹配原则 三. 类模板类模板的定义格式类模板的实例化 一. 泛型编程 如何实现一个…...
人工智能基础_机器学习036_多项式回归升维实战3_使用线性回归模型_对天猫双十一销量数据进行预测_拟合---人工智能工作笔记0076
首先我们拿到双十一从2009年到2018年的数据 可以看到上面是代码,我们自己去写一下 首先导包,和准备数据 from sklearn.linear_model import SGDRegressor import numpy as np import matplotlib.pyplot as plt X=np.arange(2009.2020)#左闭右开,2009到2019 获取从2009到202…...
【算法挨揍日记】day29——139. 单词拆分、467. 环绕字符串中唯一的子字符串
139. 单词拆分 139. 单词拆分 题目描述: 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。 解题思路&am…...
YOLOv8-Seg改进:轻量级Backbone改进 | VanillaNet极简神经网络模型 | 华为诺亚2023
🚀🚀🚀本文改进:一种极简的神经网络模型 VanillaNet,支持vanillanet_5, vanillanet_6, vanillanet_7, vanillanet_8, vanillanet_9, vanillanet_10, vanillanet_11等版本,相比较yolov8-seg各个版本如下: layersparametersgradientsGFLOPsvanillanet_521230017523...
解决Requests中使用httpbin服务器问题:自定义URL的实现与验证
问题背景 在使用Python的Requests模块进行单元测试时,可能会遇到无法使用本地运行的httpbin服务器进行测试的问题。这是因为测试脚本允许通过环境变量HTTPBIN_URL指定用于测试的本地httpbin实例,但在某些测试用例中,URL是硬编码为httpbin.or…...
软考-高级-系统架构设计师教程(清华第2版)【第17章 通信系统架构设计理论与实践(P614~646)-思维导图】
软考-高级-系统架构设计师教程(清华第2版)【第17章 通信系统架构设计理论与实践(P614~646)-思维导图】 课本里章节里所有蓝色字体的思维导图...
【MATLAB源码-第82期】基于matlab的OFDM系统载波频移偏差(CFO)估计,对比三种不同的方法。
操作环境: MATLAB 2013b 1、算法描述 正交频分复用(OFDM)系统中的载波频率偏移(CFO)估计是一项关键技术,用于确保数据传输的准确性和效率。CFO通常由于振荡器频率不匹配和多普勒频移引起。不同的CFO估计…...
Docker Swarm: 容器编排的力量和优势深度解析
文章目录 Docker Swarm的核心概念1. 节点(Node)2. 服务(Service)3. 栈(Stack) 使用Docker Swarm1. 初始化Swarm2. 加入节点3. 创建服务4. 扩展和缩减服务5. 管理栈6. 管理服务更新 Docker Swarm的优势深度解…...
调整Windows键盘上只能看到拼音而无法看到实际的文本以及关闭输入法悬浮窗方法
一、输入法设置 如果您在键盘上只能看到拼音而无法看到实际的文本,这可能是因为您的输入法设置为中文拼音输入法或其他仅显示拼音的输入法。 要解决这个问题,您可以尝试以下方法: 1. 切换输入法:按下 Shift Alt 组合键或 Wind…...
【微软技术栈】C#.NET 中的管道操作
C#.NET 管道为进程间通信提供了平台。 管道分为两种类型: 匿名管道。 匿名管道在本地计算机上提供进程间通信。 与命名管道相比,虽然匿名管道需要的开销更少,但提供的服务有限。 匿名管道是单向的,不能通过网络使用。 仅支持一个服…...
Python学习笔记--进程
进程 Python 中的多线程其实并不是真正的多线程,如果想要充分地使用多核 CPU 的资源,在 Python 中大部分情况需要使用多进程。 Python 提供了非常好用的多进程包 multiprocessing,只需要定义一个函数,Python 会完成其他所有事情。 借助这个包,可以轻松完成从单进程到并…...
比亚迪刀片电池与特斯拉4680电池比较
1 电池材料 比亚迪刀片电池采用的磷酸铁锂LFP(LiFePO4),特斯拉的4680电池采用的三元锂。 磷酸铁锂:循环寿命长,安全性能好,价格低廉,但是能量密度低,导电性能差,低温表现…...
在写windows C++代码的时候,从代码安全角度考虑,我们应该注意什么?
在写windows C代码的时候,从代码安全角度考虑,我们应该注意什么?分别是:输入验证、内存管理、错误处理、并发和线程安全、使用安全的API、避免使用不安全的函数、最小权限原则。 一、输入验证 1. 用户输入验证 #include <io…...
【草料】uni-app ts vue 小程序 如何如何通过草料生成对应的模块化二维码
一、查看uni-app项目 1、找到路径 可以看到项目从 src-race-pages-group 这个使我们目标的查询页面 下面我们将这个路径copy到草料内 2、找到进入页面入参 一般我们都会选择 onload() 函数下的入参 这里我们参数的是 id 二、草料 建议看完这里的教程文档 十分清晰!…...
CMS与FullGC
JVM中的CMS(Concurrent Mark Sweep)GC和Full GC(Full Garbage Collection)是两种不同的垃圾回收算法。 CMS GC:CMS GC是一种并发的垃圾回收算法,它在运行期间与应用程序线程并发工作,尽可能减少…...
一款.NET开源的小巧、智能、免费的Windows内存清理工具 - WinMemoryCleaner
前言 我们在使用Windows系统的时候经常会遇到一些程序不会释放已分配的内存,从而导致电脑变得缓慢。今天给大家推荐一款.NET开源的小巧、智能、免费的Windows内存清理工具:WinMemoryCleaner。 使用Windows内存清理工具来优化内存,这样不必浪…...
iptables详解:链、表、表链关系、规则的基本使用
目录 防火墙基本概念 什么是防火墙? Netfilter与iptables的关系 链的概念 表的概念 表链关系 规则的概念 查询规则 添加规则 删除iptables中的记录 修改规则 更详细的命令(5链4表) 防火墙基本概念 什么是防火墙? 在…...
安全管理中心(设备和技术注解)
网络安全等级保护相关标准参考《GB/T 22239-2019 网络安全等级保护基本要求》和《GB/T 28448-2019 网络安全等级保护测评要求》 密码应用安全性相关标准参考《GB/T 39786-2021 信息系统密码应用基本要求》和《GM/T 0115-2021 信息系统密码应用测评要求》 1系统管理 1.1对系统管…...
Failed to execute org.scala-tools:maven-scala-plugin:2.15.2解决
原因也不是很清楚,查看一个博主文章(net.alchim31.maven:scala-maven-plugin:maven依赖无法下载或无法编译)得到的解决方案: 在idea的terminal执行以下语句即可实现maven对scala代码的编译: mvn clean scala:compile compile pac…...
C#中委托和事件的使用总结
委托(delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。事件是一种特殊的多播委托,仅可以从声明事件的类或结构中对其进行调用。类或对象可以通过事件向其他类或对象通知发生的…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
