C++学习-入门到精通【16】自定义模板的介绍

news2025/6/8 22:46:51

C++学习-入门到精通【16】自定义模板的介绍


目录)

  • C++学习-入门到精通【16】自定义模板的介绍
    • 前言
    • 一、类模板
      • 创建一个自定义类模板:Stack\<T\>
    • 二、使用函数模板来操作类模板特化的对象
    • 三、非类型形参
    • 四、模板类型形参的默认实参
    • 五、重载函数模板


前言

在前面的学习中我们使用了不少的标准库中预先封装好的模板化的容器和算法。函数模板类模板使程序员可以非常方便地表示若干不同的相关(重载)的函数或者类,这样的函数被称为函数模板特化,而类被称为类模板特化

这种产生函数和类的技术称为泛型程序设计。可以将函数模板和类模板比作一个棕子,而棕子内部可以有不同的馅料,虽然使用这个模板做出来的都是相同外形(功能相同),但是里面的口味却不同(操作对象不同)。


一、类模板

类模板也被称为参数化类型,因为它们需要一个或多个参数,来说明如何自定义一个用于产生类模板特化的通用类模板。只要定义了一个类模板,当需要一个特定的类模板特化时,我们可以非常简单得到该特化的类类型,编译器会写出类模板特化的源代码。举个例子,如果此时有一个Stack类模板,那么我们就可以使用该模板创建出许多不同的Stack类模板特化(例如,double类型的Stack、Date类型的Stack、Employee类型的Stack等等)。

注意,如果要使用用户的自定义类型来创建一个模板的特化,那么该自定义类型必须满足模板的要求。例如,模板中会使用 < 来比较两个对象的大小从而进行排序,如果自定义的类型中没有重载 < 运算符,那么此时将发生编译错误。

创建一个自定义类模板:Stack<T>

所有类模板的定义均以关键字template开始,后面是一对尖括号<>中的模板形参表。每个模板形参表示一种类型,它必须以可交换关键字typenameclass开头。类型形参的作用是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个,那么编译器就会认为本次调用具有二义性,将产生一个错误信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2404656.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

源码级拆解:如何搭建高并发「数字药店+医保购药」一体化平台?

在全民“掌上看病、线上购药”已成常态的今天&#xff0c;数字药店平台正在以惊人的速度扩张。而将数字药店与医保系统打通&#xff0c;实现线上医保购药&#xff0c;更是未来互联网医疗的关键拼图。 那么&#xff0c;如何从技术底层搭建一个 支持高并发、可扩展、安全合规的数…

Hadoop 3.x 伪分布式 8088端口无法访问问题处理

【Hadoop】YARN ResourceManager 启动后 8088 端口无法访问问题排查与解决(伪分布式启动Hadoop) 在配置和启动 Hadoop YARN 模块时&#xff0c;发现虽然 ResourceManager 正常启动&#xff0c;JPS 进程中也显示无误&#xff0c;但通过浏览器访问 http://主机IP:8088 时却无法打…

零基础在实践中学习网络安全-皮卡丘靶场(第十期-Over Permission 模块)

经过这么长时间的学习&#xff0c;我相信大家已经有了很大的信心&#xff0c;有可能会有看不起的意思&#xff0c;因为皮卡丘是基础靶场&#xff0c;但是俗话说"基础不牢&#xff0c;地动山摇"&#xff0c;所以还请大家静下心来进行学习 来翻译一下是什么意思&#…

毕设 基于机器视觉的驾驶疲劳检测系统(源码+论文)

文章目录 0 前言1 项目运行效果2 课题背景3 Dlib人脸检测与特征提取3.1 简介3.2 Dlib优点 4 疲劳检测算法4.1 眼睛检测算法4.2 打哈欠检测算法4.3 点头检测算法 5 PyQt55.1 简介5.2相关界面代码 6 最后 0 前言 &#x1f525;这两年开始毕业设计和毕业答辩的要求和难度不断提升…

学习STC51单片机30(芯片为STC89C52RCRC)

每日一言 当你感到疲惫时&#xff0c;正是成长的关键时刻&#xff0c;再坚持一下。 IIC协议 是的&#xff0c;IIC协议就是与我们之前的串口通信协议是同一个性质&#xff0c;就是为了满足模块的通信&#xff0c;其实之前的串口通信协议叫做UART协议&#xff0c;我们千万不要弄…

Python-进程

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

Paraformer分角色语音识别-中文-通用 FunASR demo测试与训练

文章目录 0 资料1 Paraformer分角色语音识别-中文-通用1 模型下载2 音频识别测试3 FunASR安装 &#xff08;训练用&#xff09;4 训练 0 资料 https://github.com/modelscope/FunASR/blob/main/README_zh.md https://github.com/modelscope/FunASR/blob/main/model_zoo/readm…

对抗反爬机制的分布式爬虫自适应策略:基于强化学习的攻防博弈建模

在大数据时代&#xff0c;数据的价值不言而喻。网络爬虫作为获取数据的重要工具&#xff0c;被广泛应用于各个领域。然而&#xff0c;随着爬虫技术的普及&#xff0c;网站为了保护自身数据安全和服务器性能&#xff0c;纷纷采取了各种反爬机制。这就使得爬虫与反爬虫之间形成了…

手写muduo网络库(一):项目构建和时间戳、日志库

引言 本文作为手写 muduo 网络库系列开篇&#xff0c;聚焦项目基础框架搭建与核心基础工具模块设计。通过解析 CMake 工程结构设计、目录规划原则&#xff0c;结合时间戳与日志系统的架构&#xff0c;为后续网络库开发奠定工程化基础。文中附完整 CMake 配置示例及模块代码。 …

14-Oracle 23ai Vector Search 向量索引和混合索引-实操

一、Oracle 23ai支持的2种主要的向量索引类型&#xff1a; 1.1 内存中的邻居图向量索引 (In-Memory Neighbor Graph Vector Index) HNSW(Hierarchical Navigable Small World &#xff1a;分层可导航小世界)索引 是 Oracle AI Vector Search 中唯一支持的内存邻居图向量索引类…

Web前端基础:JavaScript

1.JS核心语法 1.1 JS引入方式 第一种方式&#xff1a;内部脚本&#xff0c;将JS代码定义在HTML页面中 JavaScript代码必须位于<script></script>标签之间在HTML文档中&#xff0c;可以在任意地方&#xff0c;放置任意数量的<script></script>一般会把…

基于AWS Serverless架构:零运维构建自动化SEO内容生成系统

作者&#xff1a;[Allen] 技术专栏 | 深度解析云原生SEO自动化 在流量为王的时代&#xff0c;持续产出高质量SEO内容成为技术运营的核心痛点。传统方案面临开发成本高、扩展性差、关键词响应滞后三大难题。本文将分享如何用AWS Serverless技术栈&#xff0c;构建一套零服务器运…

电镀机的阳极是什么材质?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测技术社区&#xff0c;点击加入&#xff09;里的学员问&#xff1a;电镀的阳极有什么讲究&#xff1f;什么是可溶性阳极和非可溶性阳极&#xff1f; 什么是可溶性阳极与非可溶性阳极&#xff1f; 可溶性阳极 阳极本身就是…

vscode调试deepspeed的方法之一(无需调整脚本)

现在deepspeed的脚本文件是&#xff1a; # 因为使用 RTX 4000 系列显卡时&#xff0c;不支持通过 P2P 或 IB 实现更快的通信宽带&#xff0c;需要设置以下两个环境变量 # 禁用 NCCL 的 P2P 通信&#xff0c;以避免可能出现的兼容性问题 export NCCL_P2P_DISABLE"1" …

Codeforces Round 509 (Div. 2) C. Coffee Break

题目大意&#xff1a; 给你n、m、d n为元素个数,m为数列长度,d为每个元素之间的最短间隔 问最少需要多少个数列可以使得元素都能装进数列&#xff0c;并且满足每个元素之间的间隔大于等于d 核心思想 使用贪心的思想&#xff0c;将元素的大小进行排序&#xff0c;问题出在必…

榕壹云健身预约系统:多门店管理的数字化解决方案(ThinkPHP+MySQL+UniApp实现)

随着全民健身热潮的兴起&#xff0c;传统健身房在会员管理、课程预约、多门店运营等方面面临诸多挑战。针对这一需求&#xff0c;我们开发了一款基于ThinkPHPMySQLUniApp的榕壹云健身预约系统&#xff0c;为中小型健身机构及连锁品牌提供高效、灵活的数字化管理工具。本文将详细…

QUIC——UDP实现可靠性传输

首先我们要知道TCP存在什么样的痛点问题 TCP的升级很困难TCP建立连接的延迟网络迁移需要重新建立连接TCP存在队头阻塞问题 QUIC就是为了解决以上的问题而诞生了, 下面我会介绍QUIC的一些特性和原理 QUIC对比TCP优势: 握手建连更快 QUIC内部包含了TLS, 它在自己的帧会携带TL…

快速上手shell脚本运行流程控制

一、条件运行流程控制 1.if单分支结构 #!/bin/bash if [ 条件 ] then动作1动作2... fi 2.if双分支结构 ​ #!/bin/bash if [ 条件 ] then动作1动作2... else动作1动作2... fi​ 3.if多分支结构 二、循环运行流程控制 1.无判定for循环 给网卡一键添加5个IP 2.判断循环 while…

10.Linux进程信号

1. 理解信号 信号VS信号量 老婆&#xff1a;老婆饼-》没有任何关系&#xff01;信号&#xff1a;闹钟&#xff0c;上课铃声&#xff0c;脸色...人-》进程&#xff1b;信号中断人正在做的事&#xff0c;是一种事件的异步通知机制&#xff1b; 我们自习一会&#xff0c;等张三回…

机器学习基础(四) 决策树

决策树简介 决策树结构&#xff1a; 决策树是一种树形结构&#xff0c;树中每个内部节点表示一个特征上的判断&#xff0c;每个分支代表一个判断结果的输出&#xff0c;每个叶子节点代表一种分类结果 决策树构建过程&#xff08;三要素&#xff09;&#xff1a; 特征选择 选…