C++学习-入门到精通【16】自定义模板的介绍
C++学习-入门到精通【16】自定义模板的介绍
目录)
- C++学习-入门到精通【16】自定义模板的介绍
- 前言
- 一、类模板
- 创建一个自定义类模板:Stack\<T\>
- 二、使用函数模板来操作类模板特化的对象
- 三、非类型形参
- 四、模板类型形参的默认实参
- 五、重载函数模板
前言
在前面的学习中我们使用了不少的标准库中预先封装好的模板化的容器和算法。函数模板
和类模板
使程序员可以非常方便地表示若干不同的相关(重载)的函数或者类,这样的函数被称为函数模板特化
,而类被称为类模板特化
。
这种产生函数和类的技术称为泛型程序设计
。可以将函数模板和类模板比作一个棕子,而棕子内部可以有不同的馅料,虽然使用这个模板做出来的都是相同外形(功能相同),但是里面的口味却不同(操作对象不同)。
一、类模板
类模板也被称为参数化类型
,因为它们需要一个或多个参数,来说明如何自定义一个用于产生类模板特化的通用类模板。只要定义了一个类模板,当需要一个特定的类模板特化时,我们可以非常简单得到该特化的类类型,编译器会写出类模板特化的源代码。举个例子,如果此时有一个Stack类模板,那么我们就可以使用该模板创建出许多不同的Stack类模板特化(例如,double类型的Stack、Date类型的Stack、Employee类型的Stack等等)。
注意,如果要使用用户的自定义类型来创建一个模板的特化,那么该自定义类型必须满足模板的要求。例如,模板中会使用 < 来比较两个对象的大小从而进行排序,如果自定义的类型中没有重载 < 运算符,那么此时将发生编译错误。
创建一个自定义类模板:Stack<T>
所有类模板的定义均以关键字template
开始,后面是一对尖括号<>
中的模板形参表。每个模板形参表示一种类型,它必须以可交换关键字typename
或class
开头。类型形参的作用是Stack元素类型的占位符。在模板定义中,类型形参的名字必须是唯一
的。并不一定要使用T
,任何标识符都可以。
在Stack<T>
开头的类模板中,整个定义中的元素的类型都被泛化成T
。在使用类模板创建一个对象时,类型形参会和一个特定的类型相关联。此时,编译器将生成一个类模板的副本,其中所有出现类型形参的地方都被这个特定的类型替换。
类模板另一个与普通类定义不同的地方在于,类模板的接口并没有与它的实现分离开来。(这是因为模板的替换工件是完全发生在编译阶段的,如果将定义和实现分离,那么使用该类模板以创建一个类模板特化的源文件是只能访问到这个类模板的定义的,但是这个模板是如何实现的该源文件是无法获取的,因为一个程序的源文件是在链接阶段才进行一系列的操作将它们链接成一个可执行程序的,在编译阶段时编译器就无法创建一个对应的类模板的副本)。
Stack.h
#pragma once
#include <deque>// 创建一个类模板
template <class T>
class Stack
{
public:T& top(){return stack.front();}void push(const T& pushValue){stack.push_front(pushValue);}void pop(){stack.pop_front();}bool isEmpty() const {return stack.empty();}void size() const{stack.size();}
private:std::deque<T> stack;
};
类模板中定义的成员函数就是函数模板,但是它们在类模板体内定义时,不用在前面加上template关键字和模板形参。
在类模板的定义之外声明模板的成员函数
成员函数的定义可以出现在类模板的定义之外。如果要这样做的话,每个成员函数必须以关键字template开始,后面跟着与类模板一样的模板形列表。除此之外,成员函数还必须用类名和作用域分辨运算符进行限定。
例如,在类模板之外定义成员函数top:这里的inline
不加也可以,加了也不会报错,
template <class T>
inline T& Stack<T>::top()
{return stack.front();
}
其中的Stack<T>::
表明该函数是在Stack<T>的作用域中。可以看到在类模板的定义之外声明模板的成员函数比在模板体内声明要复杂不少,所以我们更倾向于在模板内部声明它的成员函数。
对类模板Stack<T>进行测试
test.cpp
#include <iostream>
#include "Stack.h"
using namespace std;int main()
{Stack<double> doubleStack; // 创建一个double类型的类模板特化const size_t doubleStackSize = 5;double doubleValue = 1.1;cout << "Pushing elements onto doubleStack\n";// 往栈内压入5个double类型的值for (size_t i = 0; i < doubleStackSize; ++i){doubleStack.push(doubleValue);cout << doubleValue << ' ';doubleValue += 1.1;}cout << "\n\nPopping elements from doubleStack\n";// 将栈中元素全部弹出while (!doubleStack.isEmpty()){cout << doubleStack.top() << ' ';doubleStack.pop(); }cout << "\nStack is empty, connot pop.\n";Stack<int> intStack;const size_t intStackSize = 10;int intValue = 1;cout << "\n\nPushing elements onto intStack\n";for (size_t i = 0; i < intStackSize; ++i){intStack.push(intValue);cout << intValue++ << ' ';}cout << "\n\nPopping elements from intStack\n";while (!intStack.isEmpty()){cout << intStack.top() << ' ';intStack.pop();}cout << "\nStack is empty, cannot pop." << endl;
}
运行结果:
可以看到,在上面的程序中我们使用两条不同的语句就能创建两个功能相同但是操作对象不同的Stack对象,并且它们的行为也符合我们对栈的设计。
二、使用函数模板来操作类模板特化的对象
如果大家认真读过上面的示例代码一定会发现,关于doubleStack和IntStack的操作的代码几乎一模一样,所以这就又提供了一个使用函数模板的机会。
下面我们就定义了函数模板testStack
来完成和上面程序中的main函数相同的任务。
#include <iostream>
#include <string>
#include "Stack.h"
using namespace std;// 创建一个函数模板来测试特化的Stack类模板
template<class T>
void testStack(Stack<T>& theStack,const T& value,const T& increment,size_t size,const string& stackName
)
{cout << "Pushing elements onto " << stackName << "\n";T pushValue = value;for (size_t i = 0; i < size; ++i){theStack.push(pushValue);cout << pushValue << ' ';pushValue += increment;}cout << "\n\nPopping elements from " << stackName << "\n";// 将栈中元素全部弹出while (!theStack.isEmpty()){cout << theStack.top() << ' ';theStack.pop(); }cout << "\nStack is empty, connot pop.\n";
}int main()
{Stack<double> doubleStack;const size_t doubleStackSize = 5;testStack(doubleStack, 1.1, 1.1, doubleStackSize, "doubleStack");cout << "\n\n";Stack<int> intStack;const size_t intStackSize = 5;testStack(intStack, 1, 1, intStackSize, "intStack");
}
运行结果:
在上面程序中我们使用函数模板时,编译器会根据第一个实参所使用的类型来决定该特化的函数模板中的模板形参使用的是什么类型。
三、非类型形参
上面程序中的类模板Stack中只在它的模板声明中使用了一个类型形参。除此之外,还可以使用非类型模板形参(或非类型形参),它可以有默认的实参并作为常量处理。例如C++标准库的array类模板,它的模板声明的开始部分是:
template <class T, size_t N>
,
因此,如下的声明array <double, 100> salesFigures;
创建了一个有100个double类型元素的类模板特化,然后使用这个模板特化实例化了对象salesFigures。所以上面的这个声明就是指定该array对象内部的内置数组的大小。
四、模板类型形参的默认实参
类型形参也可以指定它的默认类型实参。例如,C++标准库中的stack容器适配器类模板的开始部分为template<class T, class Container = deque<T> >
它指定了默认情况下,stack对象使用一个deque对象来保存stack对象的T类型的元素。以下的声明
stack<int> values;
就是创建了一个int类型的stack类模板特化,并使用它实例化了一个名为values的对象。这个stack对象的int类型的元素存储在一个deque<int>
对象中。
默认类型形参与默认的函数参数一样,必须是模板参数列表中最靠右边的形参,有多个默认形参时,这些形参也必须是最靠右边的形参。
五、重载函数模板
函数模板与重载
之间的关系非常密切,当重载的函数对不同类型的数据执行相同的操作时,这些重载的函数可以用更加紧凑、方便的函数模板来表示。之后,我们就可以通过使用不同的形参,让编译器自动生成不同的函数模板特化来处理相应的函数调用。从一个给定的函数模板生成的这些函数模板特化都具有相同的名字,因此编译器采用重载的方式来调用正确的对应函数。
同样的函数模板也可以被重载。例如可以提供多个函数模板,它们具有相同的函数名,但是函数的形参不同。而且,一个函数模板也可以被非模板函数进行重载,这些非模板函数也一定要与该模板函数具有相同的函数名和不同的形参。
重载函数的匹配过程
编译器在函数被调用时就要决定到底调用哪一个函数的匹配过程。首先编译器会查看也有的函数和函数模板,找到函数名和参数类型与这个函数调用一致的一个函数,或者生成一个函数名和参数类型与这个函数调用一致的函数模板特化。如果无法匹配,编译器发出一条错误信息。如果对这个函数调用有多个匹配情况,那么编译器试着确定最佳的匹配。如果最佳匹配超过1个,那么编译器就会认为本次调用具有二义性,将产生一个错误信息。
相关文章:

C++学习-入门到精通【16】自定义模板的介绍
C学习-入门到精通【16】自定义模板的介绍 目录) C学习-入门到精通【16】自定义模板的介绍前言一、类模板创建一个自定义类模板:Stack\<T\> 二、使用函数模板来操作类模板特化的对象三、非类型形参四、模板类型形参的默认实参五、重载函数模板 前言…...
关于脏读,幻读,可重复读的学习
mysql 可以查询当前事务隔离级别 默认是RR repeatable-read 如果要测脏读 要配成未提交读 RU 读到了未提交的数据。 3.演示不可重复读 要改成提交读 RC 这个是指事务还未结束,其他事务修改了值。导致我两次读的不一样。 4.RR–可以解决不可重复读 小总结&…...

源码级拆解:如何搭建高并发「数字药店+医保购药」一体化平台?
在全民“掌上看病、线上购药”已成常态的今天,数字药店平台正在以惊人的速度扩张。而将数字药店与医保系统打通,实现线上医保购药,更是未来互联网医疗的关键拼图。 那么,如何从技术底层搭建一个 支持高并发、可扩展、安全合规的数…...
旅行商问题(TSP)的 C++ 动态规划解法教学攻略
一、问题描述 旅行商问题(TSP)是一个经典的组合优化问题。给定一个无向图,图中的顶点表示城市,边表示两个城市之间的路径,边的权重表示路径的距离。一个售货员需要从驻地出发,经过所有城市后回到驻地&…...
unix/linux,sudo,其内部结构机制
我们现在深入sudo的“引擎室”,探究其内部的结构和运作机制。这就像我们从观察行星运动,到深入研究万有引力定律的数学表达和物理内涵一样,是理解事物本质的关键一步。 sudo 的内部结构与机制详解 sudo 的执行流程可以看作是一系列精心设计的步骤,确保了授权的准确性和安…...

Hadoop 3.x 伪分布式 8088端口无法访问问题处理
【Hadoop】YARN ResourceManager 启动后 8088 端口无法访问问题排查与解决(伪分布式启动Hadoop) 在配置和启动 Hadoop YARN 模块时,发现虽然 ResourceManager 正常启动,JPS 进程中也显示无误,但通过浏览器访问 http://主机IP:8088 时却无法打…...
Redis线程安全深度解析:单线程模型的并发智慧
Redis线程安全深度解析:单线程模型的并发智慧 引言:Redis的线程模型迷思 “Redis是单线程的”——这个广为流传的说法既正确又不完全正确。Redis的线程安全机制实际上是一套精心设计的并发控制体系,它既保持了单线程的简单性,又…...

零基础在实践中学习网络安全-皮卡丘靶场(第十期-Over Permission 模块)
经过这么长时间的学习,我相信大家已经有了很大的信心,有可能会有看不起的意思,因为皮卡丘是基础靶场,但是俗话说"基础不牢,地动山摇",所以还请大家静下心来进行学习 来翻译一下是什么意思&#…...
北京大学肖臻老师《区块链技术与应用》公开课:12-BTC-比特币的匿名性
文章目录 1.比特币的匿名性不是真的匿名,相当于化名,现金是真的匿名, 2.如果银行用化名的话和比特币的匿名哪个匿名性更好? 银行匿名性比比特币好,因为比特币的区块链的账本是完全公开的,所有人都可以查&am…...
[Harmony]网络状态监听
权限 在module.json5中添加必要权限: // 声明应用需要请求的权限列表 "requestPermissions": [{"name": "ohos.permission.GET_NETWORK_INFO", // 网络信息权限"reason": "$string:network_info_reason","…...

毕设 基于机器视觉的驾驶疲劳检测系统(源码+论文)
文章目录 0 前言1 项目运行效果2 课题背景3 Dlib人脸检测与特征提取3.1 简介3.2 Dlib优点 4 疲劳检测算法4.1 眼睛检测算法4.2 打哈欠检测算法4.3 点头检测算法 5 PyQt55.1 简介5.2相关界面代码 6 最后 0 前言 🔥这两年开始毕业设计和毕业答辩的要求和难度不断提升…...
Ubuntu18.6 学习QT问题记录以及虚拟机安装Ubuntu后的设置
Ubuntu安装 1、VM 安装 Ubuntu后窗口界面太小 Vmware Tools 工具安装的有问题 处理办法: 1、重新挂载E:\VMwareWorkstation\linux.iso文件,该文件在VMware安装目录下 2、Ubuntu桌面出现vmtools共享文件夹,将gz文件拷贝至本地,解…...
Vue3中computed和watch的区别
文章目录 前言🔍 一、computed vs watch✅ 示例对比1. computed 示例(适合模板绑定、衍生数据)2. watch 示例(副作用,如调用接口) 🧠 二、源码实现原理(简化理解)1. comp…...
发版前后的调试对照实践:用 WebDebugX 与多工具构建上线验证闭环
每次产品发版都是一次“高压时刻”。版本升级带来的不仅是新功能上线,更常伴随隐藏 bug、兼容性差异与环境同步问题。 为了降低上线风险,我们逐步构建了一套以 WebDebugX 为核心、辅以 Charles、Postman、ADB、Sentry 的发版调试与验证流程,…...
瀚文(HelloWord)智能键盘项目深度剖析:从0到1的全流程解读
瀚文(HelloWord)智能键盘项目深度剖析:从0到1的全流程解读 一、项目整体概述 瀚文(HelloWord)智能键盘是一款多功能、模块化的智能机械键盘,由三大部分组成:键盘输入模块、可替换的多功能交互…...
Shell编程核心符号与格式化操作详解
Shell编程作为Linux系统管理和自动化运维的核心技能,掌握其常用符号和格式化操作是提升脚本开发效率的关键。本文将深入解析Shell中重定向、管道符、EOF、输入输出格式化等核心概念,并通过丰富的实践案例帮助读者掌握这些重要技能。 一、信息传递与重定…...
针对“仅某个地区出现Bug”的原因分析与解决方案
一、核心排查方向(按优先级排序) 地区相关配置差异 检查点: 该地区是否有独立的配置文件或数据库分片?是否启用了地区特定的功能开关(Feature Flag)或AB测试?本地化内容(如语言、时…...

学习STC51单片机30(芯片为STC89C52RCRC)
每日一言 当你感到疲惫时,正是成长的关键时刻,再坚持一下。 IIC协议 是的,IIC协议就是与我们之前的串口通信协议是同一个性质,就是为了满足模块的通信,其实之前的串口通信协议叫做UART协议,我们千万不要弄…...
sql中group by使用场景
GROUP BY语句在SQL中用于将多个记录分组为较小的记录集合,以便对每个组执行聚合函数,如COUNT(), MAX(), MIN(), SUM(), AVG()等。GROUP BY的使用场景非常广泛,以下是一些典型的应用场景: 统计数量 当你想要计算某个字段的唯一值数…...
将HTML内容转换为Canvas图像,主流方法有效防止文本复制
HTML to Canvas 使用说明 项目概述 此项目实现了将HTML内容转换为Canvas图像的功能,可有效防止文本被复制。适用于需要保护内容的场景,如试题系统、付费内容等。 主要功能 防止复制: 将文本内容转换为Canvas图像,使用户无法选择和复制Mat…...

Python-进程
进程 简介 操作系统分配资源的基本单位 创建 依赖 依赖模块 multiprocessing 中的 Process 语法 Process(group[,target[,name[,args[,kwargs]]]]) target:如果传递了函数的引用,这个子进程就执行这里的代码args:元组的方式传递&#x…...

Paraformer分角色语音识别-中文-通用 FunASR demo测试与训练
文章目录 0 资料1 Paraformer分角色语音识别-中文-通用1 模型下载2 音频识别测试3 FunASR安装 (训练用)4 训练 0 资料 https://github.com/modelscope/FunASR/blob/main/README_zh.md https://github.com/modelscope/FunASR/blob/main/model_zoo/readm…...
【从0-1的CSS】第1篇:CSS简介,选择器以及常用样式
文章目录 CSS简介CSS的语法规则选择器id选择器元素选择器类选择器选择器优先级 CSS注释 CSS常用设置样式颜色颜色名称(常用)RGB(常用)RGBA(常用)HEX(常用)HSLHSLA 背景background-colorbackground-imagebackground-size 字体text-aligntext-decorationtext-indentline-height 边…...

对抗反爬机制的分布式爬虫自适应策略:基于强化学习的攻防博弈建模
在大数据时代,数据的价值不言而喻。网络爬虫作为获取数据的重要工具,被广泛应用于各个领域。然而,随着爬虫技术的普及,网站为了保护自身数据安全和服务器性能,纷纷采取了各种反爬机制。这就使得爬虫与反爬虫之间形成了…...
JDK21深度解密 Day 15:JDK21实战最佳实践总结
【JDK21深度解密 Day 15】JDK21实战最佳实践总结 文章简述 本篇文章是《JDK21深度解密:从新特性到生产实践的全栈指南》系列的第15篇,聚焦于JDK21实战最佳实践总结。作为Java历史上最重要的LTS版本之一,JDK21带来了虚拟线程、结构化并发、模式匹配、ZGC优化等革命性特性,…...

手写muduo网络库(一):项目构建和时间戳、日志库
引言 本文作为手写 muduo 网络库系列开篇,聚焦项目基础框架搭建与核心基础工具模块设计。通过解析 CMake 工程结构设计、目录规划原则,结合时间戳与日志系统的架构,为后续网络库开发奠定工程化基础。文中附完整 CMake 配置示例及模块代码。 …...
每日算法刷题Day25 6.7:leetcode二分答案3道题,用时1h40min(遇到两道动态规划和贪心时间较长)
3. 1631.最小体力消耗路径(中等,dfs不熟练) 1631. 最小体力消耗路径 - 力扣(LeetCode) 思想 1.你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左…...

14-Oracle 23ai Vector Search 向量索引和混合索引-实操
一、Oracle 23ai支持的2种主要的向量索引类型: 1.1 内存中的邻居图向量索引 (In-Memory Neighbor Graph Vector Index) HNSW(Hierarchical Navigable Small World :分层可导航小世界)索引 是 Oracle AI Vector Search 中唯一支持的内存邻居图向量索引类…...
kubeadm安装k8s
1、环境准备 1.1、升级系统内核 参考另一篇文章:https://blog.csdn.net/u012533920/article/details/148457715?spm1011.2415.3001.5331 1.2、设置Hostname cat <<EOF > /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhos…...
服务器新建用户无法使用conda
服务器新建用户无法使用conda 1.将.bashrc文件复制到新用户家目录下 sudo cp .bashrc /home/newuser/.bashrc2.source命令激活该文件 source ~/.bashrc3.将.condarc文件复制到新用户家目录下 sudo cp .condarc/home/newuser/.condarc...