6.模板初阶(函数模板、类模板、类模板声明与定义分离)

news2025/4/5 22:02:42

1. 泛型编程

如何实现一个通用的交换函数呢?

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

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

模板的格式

  • template<class T> ,< >中可以写typename,也可以写class;其中T为一个虚拟类型
  • template<class Tp> , 虚拟类型我们可以自己取名,并不会进行限制
  • template<class K>
// 函数模板 (根据我们所需要的类型,编译器会根据我们给出的模板进行实例化)
template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

int main()
{
	int a = 1, b = 2;
	Swap(a, b);

	int x = 1, y = 2;  // 类型都为int,因此只会实例化一个函数
	Swap(x, y);

	double c = 1.1, d = 2.2;
	Swap(c, d);

	char e = 'a', f = 'b';
	Swap(e, f);

	return 0;
}

2. 函数模板

2.1 函数模板概念

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

2.2 函数模板格式

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

template<typename T>
void Swap( T& left, T& right)
{
    T temp = left;
    left = right;
    right = temp; 
}

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

2.3 函数模板的原理

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类也是如此。

image-20221212221722962

2.4 函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

  1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
    
	// 自动推演实例化 (编译器根据实参自己推演)
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;

    // 错误演示
    // cout << Add(a1, d2) << endl;   
    // 此时,编译无法通过,因为推演只能使虚拟类型T实例化为int,或者double,并不可以兼顾两者
    // 因此有以下两种解决方案
    
    // 1.用户自己进行类型强转
	cout << Add((double)a1, d2) << endl;  
  // 将a1强转为double类型,会存放到一个临时变量(临时变量具有常性), 因此,被引用时,一定要用const修饰
	cout << Add(a1, (int)d2) << endl;

	// 显示实例化
	cout << Add<double>(a1, d2) << endl; 
    // <>中放置类型,表示我们想让编译器用这种类型来实例化(也就是说编译器不会自己去推演)
	cout << Add<int>(a1, d2) << endl;

	return 0;
}
// 定义两个虚拟类,来进行实例化,也可以很好的解决下面的问题
template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
	return left + right;
}


int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	// 自动推演实例化
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;

	cout << Add(a1, d2) << endl;
	cout << Add(d1, a2) << endl;

	return 0;
}

2.5 模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
// 专门处理int的加法函数(可以和通用加法函数同时存在)
int Add(int left, int right) // _Z3Addii
{
	return left + right;
}

// 通用加法函数
template<class T>
T Add(T left, T right) // _Z3TAddii
{
	return left + right;
}

int main()
{
	int a = 1, b = 2;
	Add(a, b);    	// 如果有专门处理int的加法函数,会优先调用

	Add<int>(a, b); // 会调用通用加法函数

	return 0;
}

  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(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

3. 类模板

// 如果使用typedef就只可以定义一个类型
// typedef int STDataType;     
// typedef double STDataType;

// 使用类模板
template<typename T>
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = )" <<capacity<<endl;

		_a = (T*)malloc(sizeof(T)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
	
	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(const T& x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

private:
	T* _a;
	int _top;
	int _capacity;
};


int main()
{
	// 类模板一般没有推演时机,函数模板实参传递形参,推演模板参数
	// 因此,我们直接显示实例化
	// 他们是同一个类模板实例化出来的
	// 但是模板参数不同,他们就是不同类型
	Stack<double> st1; // double
	st1.Push(1.1);

	Stack<int> st2; // int
	st2.Push(1);

	return 0;
}
#define N  10

namespace qwy
{
	template<class T>
	class array
	{
	public:
		T& operator[](size_t i)
		{
			assert(i < N);
			return _a[i];
		}
        
	private:
		T _a[N];
        
	};
}

int main()
{
	//int a2[10];
	//a2[20] = 0;  //只可以检查写越界
	//a2[10];      // 读的话,检查不出来越界

	qwy::array<int> a1;
	for (size_t i = 0; i < N; ++i)
	{
		// 编译时会转化为a1.operator[](i)= i;   
		a1[i] = i;
	}

	for (size_t i = 0; i < N; ++i)
	{
		// 编译时会转化为a1.operator[](i)= i;   
		cout << a1[i] << " ";
	}
	cout << endl;

	for (size_t i = 0; i < N; ++i)
	{
		a1[i]++;
	}

	for (size_t i = 0; i < N; ++i)
	{
		cout << a1[i] << " ";
	}
	cout << endl;

	//a1[20];   // 两种都可以检查出来越界
	//a1[10];


	return 0;
}

4.模板的声明与定义分离

// 方法1
// stack.h
#include<iostream>
using namespace std;

template<typename T>
class Stack
{
public:
	Stack(int capacity = 4);
	~Stack();
	void Push(const T& x);

private:
	T* _a;
	int _top;
	int _capacity;
};
// stack.cpp  (将声明与定义分离)
#include "Stack.h"

//此处一定要声明T是虚拟类型,每个函数前声明一次
template<class T>          
Stack<T>::Stack(int capacity = 4)
{
	cout << "Stack(int capacity = )" << capacity << endl;

	_a = (T*)malloc(sizeof(T)*capacity);
	if (_a == nullptr)
	{
		perror("malloc fail");
		exit(-1);
	}

	_top = 0;
	_capacity = capacity;
}

//此处一定要声明T是虚拟类型,每个函数前声明一次
template<class T>
Stack<T>::~Stack()
{
	cout << "~Stack()" << endl;

	free(_a);
	_a = nullptr;
	_top = _capacity = 0;
}

//此处一定要声明T是虚拟类型,每个函数前声明一次
template<class T>
void Stack<T>::Push(const T& x)
{
	// ....
	// 扩容
	_a[_top++] = x;
}


// 显示实例化  
// 此处一定要显示实例化,如果没有显示实例化,那么在汇编的时候,就找不到对应的函数放到符号表里面
template 
class Stack<int>;
// test.cpp
#include "Stack.hpp"

int main()
{
    // 这种显示实例化,只能够处理一种类型
	Stack<int> st;
	st.Push(1);
	st.Push(2);

    // 如果想要运行double类型,则需要在stack.cpp中添加double类型的显示实例化
	// Stack<double> stt;
	// stt.Push(1.1);
	// stt.Push(2.2);

	return 0;
}

思考:

如果不添加显示实例化为什么程序会报错?

image-20221213014645227

// 因此,对于上述情况,我们提出了两种解决方案
/*
1.显示实例化
    template
    class Stack<int>
2.不分离(不将声明和定义分别放到.h和.cpp文件中)
    我们可以将声明和定义放到.hpp文件中
    具体如下:
*/

#include<iostream>
using namespace std;

template<typename T>
class Stack
{
public:
	Stack(int capacity = 4);
	~Stack();
	void Push(const T& x);

private:
	T* _a;
	int _top;
	int _capacity;
};


template<class T>
Stack<T>::Stack(int capacity = 4)
{
	cout << "Stack(int capacity = )" << capacity << endl;

	_a = (T*)malloc(sizeof(T)*capacity);
	if (_a == nullptr)
	{
		perror("malloc fail");
		exit(-1);
	}

	_top = 0;
	_capacity = capacity;
}

template<class T>
Stack<T>::~Stack()
{
	cout << "~Stack()" << endl;

	free(_a);
	_a = nullptr;
	_top = _capacity = 0;
}

template<class T>
void Stack<T>::Push(const T& x)
{
	// ....
	// 扩容
	_a[_top++] = x;
}

// 思考:为什么不直接将声明和定义都放到类里面
// 如果们开放,更方便我们查看声明,了解类的功能

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

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

相关文章

线性、逻辑回归算法学习

1、什么是一元线性回归 线性&#xff1a;两个变量之间的关系是一次函数&#xff0c;也是数据与数据之间的关系。 回归&#xff1a;人们在测试事物的时候因为客观条件所限&#xff0c;求的都是测试值&#xff0c;而不是真实值&#xff0c;为了无限接近真实值&#xff0c;无限次的…

HarmonyOS开发实例:【状态管理】

状态管理 ArkUI开发框架提供了多维度的状态管理机制&#xff0c;和UI相关联的数据&#xff0c;不仅可以在组件内使用&#xff0c;还可以在不同组件层级间传递&#xff0c;比如父子组件之间&#xff0c;爷孙组件之间等&#xff0c;也可以是全局范围内的传递&#xff0c;还可以是…

【考研数学】1800还是660还是880?

关于这几本习题册如何选择&#xff0c;肯定是根据他们的不同特点以及我们的需求结合选择&#xff0c;给大家的建议如下&#xff1a; 1800适合初期&#xff0c;可以帮助你熟悉数学公式和基础定义&#xff0c;迅速上手用。刚开始觉得难很正常&#xff0c;存在一个上手的过程&…

VRRP虚拟路由实验(思科)

一&#xff0c;技术简介 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff09;是一种网络协议&#xff0c;用于实现路由器冗余&#xff0c;提高网络可靠性和容错能力。VRRP允许多台路由器共享一个虚拟IP地址&#xff0c;其中一台路由器被选为Master&#xff0c;负…

【Erlang】【RabbitMQ】Linux(CentOS7)安装Erlang和RabbitMQ

一、系统环境 查版本对应&#xff0c;CentOS-7&#xff0c;选择Erlang 23.3.4&#xff0c;RabbitMQ 3.9.16 二、操作步骤 安装 Erlang repository curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash安装 Erlang package s…

扫描IP开放端口该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开

扫描IP开放端口该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开 #/bin/bash #该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开放来哪些端口 #用telnet方式 IP$1 #IP119.254.3.28 #获得IP的前…

【STL】顺序容器与容器适配器

文章目录 1顺序容器概述1.1array1.2forward_list1.3deque 2.如何确定使用哪种顺序容器呢&#xff1f;3.容器适配器的概念4.如何定义适配器呢&#xff1f; 1顺序容器概述 给出以下顺序容器表&#xff1a; 顺序容器类型作用vector可变大小的数组&#xff0c;支持快速访问&#…

UML学习

UML(Unified Modeling Language)&#xff1a;统一建模语言&#xff0c;提供了一套符号和规则来帮助分析师和设计师表达系统的架构、行为和交互 类图&#xff1a;描绘类、接口之间的关系(继承、实现、关联、依赖等)以及类的内部结构(属性和方法)&#xff0c;直观展现系统的静态…

2024年3月电子学会青少年软件编程 中小学生Python编程等级考试一级真题解析(判断题)

2024年3月Python编程等级考试一级真题解析 判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 26、turtle 画布的坐标系原点是在画布的左上角 答案&#xff1a;错 考点分析&#xff1a;考查turtle相关知识&#xff0c;turtle画布坐标系是在画布的…

c# wpf LiveCharts 饼图 简单试验

1.概要 c# wpf LiveCharts 饼图 简单试验 2.代码 <Window x:Class"WpfApp3.Window5"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schem…

openharmony launcher 调研笔记(03)UI 数据装配

最近在看launcher&#xff0c;把自己调研的点做个笔记&#xff0c;持续修改更新中&#xff0c;个人笔记酌情参考。 桌面上半部分包含父子逻辑&#xff1a; Column() { PageDesktopLayout(); } PageDesktopLayout->GridSwiper->Swiper->SwiperPage 1.PageDe…

即插即用篇 | RTDETR引入Haar小波下采样 | 一种简单而有效的语义分割下采样模块

本改进已集成到 RT-DETR-Magic 框架。 下采样操作如最大池化或步幅卷积在卷积神经网络(CNNs)中被广泛应用,用于聚合局部特征、扩大感受野并减少计算负担。然而,对于语义分割任务,对局部邻域的特征进行池化可能导致重要的空间信息丢失,这有助于逐像素预测。为了解决这个问…

Collection与数据结构 二叉树(一):二叉树的性质与基本操作

1. 树形结构 1.1 概念1 (了解) 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#…

已解决:windows 下无法加载文件 xxx.ps1,因为在此系统上禁止运行脚本

目录 1&#xff0c;问题描述2&#xff0c;问题解决 1&#xff0c;问题描述 当通过 npm 全局安装依赖后&#xff08;比如 ts 对应的 tsc 命令&#xff0c;还有 pnpm&#xff09;&#xff0c;想直接使用安装的命令&#xff0c;就会报错&#xff1a; 2&#xff0c;问题解决 以管…

《QT实用小工具·二十二》多种样式导航按钮控件

1、概述 源码放在文章末尾 该项目实现了多种样式的导航按钮控件 可设置文字的左侧、右侧、顶部、底部间隔。 可设置文字对齐方式。 可设置显示倒三角、倒三角边长、倒三角位置、倒三角颜色。 可设置显示图标、图标间隔、图标尺寸、正常状态图标、悬停状态图标、选中状态图标…

P5356 [Ynoi2017] 由乃打扑克

我手把手教她打扑克 qwq 综合分析一下2个操作&#xff0c;查找区间第k小的值&#xff0c;感觉可以用主席树&#xff0c;区间修改那没事了 考虑分块做法,块长B 分析第一个操作 只需要维护数列的单调性&#xff0c;然后二分答案上二分就ok了 分析第二个操作 维护一个加法懒…

比较好玩的车子 高尔夫6

https://www.sohu.com/a/484063087_221273 四万多如愿收获手动挡高尔夫6&#xff0c;可靠性、经济性、操控性兼顾_搜狐汽车_搜狐网 2.基本上其他人也不知道到底是什么相关的车子信息

HarmonyOS 开发-自定义视图实现Tab效果

介绍 本示例介绍使用Text、List等组件&#xff0c;添加点击事件onclick,动画&#xff0c;animationTo实现自定义Tab效果。 效果预览图 使用说明 点击页签进行切换&#xff0c;选中态页签字体放大加粗&#xff0c;颜色由灰变黑&#xff0c;起到强调作用&#xff0c;同时&…

鸿蒙HarmonyOS开发实例:【简单时钟】

简单时钟 介绍 本示例通过使用[ohos.display]接口以及Canvas组件来实现一个简单的时钟应用。 效果预览 主页 使用说明 1.界面通过setInterval实现周期性实时刷新时间&#xff0c;使用Canvas绘制时钟&#xff0c;指针旋转角度通过计算得出。 例如&#xff1a;"2 * M…

第十课 Excel

最上方标题栏&#xff1a; 显示共工作薄名称&#xff0c;如果显示兼容模式是没有办法使用高级功能的。分辨高版本和低版本可以通过后缀名进行分辨&#xff1b;显示xlsx就是高版本工作薄&#xff0c;如果显示xls的话就是低版本工作薄了。如果同事老板都使用的是低版本的话我们发…