C++深入学习之模板

为什么需要模板

先来看下面一段程序:

int add(int x, int y)
{
	return x + y;
}

double add(double x, double y)
{
	return x + y;
}

long add(long x, long y)
{
	return x + y;
}

string add(string x, string y)
{
	return x + y;
}

//T1 = T2 = T3
T3 add(T1 x, T2 y)
{
	return x + y;
}

可以看到重复率很高,代码很冗余。如果此时可以有一种通用的符号可以代替上述各种类型的话,那这么多的函数我们不就可以用一个函数来代替了吗?

这就是模板出现的原因,它的出现可以简化代码,让程序员少写代码。

还可以解决严格性与灵活性的冲突:

这是因为C++是强类型语言,其声明变量时需要有严格的类型声明,如 int a = 10;
这很严格,却丧失了一种灵活性(如上面说的多个不同类型变量是否可以通过一套模板来完成的例子)。

模板语法形式

//例子:函数模板
template <模板参数列表>
函数的返回类型 函数名字(函数的参数列表)
{

}

//模板的第一种声明形式
template <typename T1, typename T2...>
//模板的第二种声明形式
template <class T1, class T2...>
//注意:模板参数列表中typename与class的含义是完全一样(除非用的编译器是2003年以前的,那么typename可能编译器会识别不了)

模板类型

模板就两种类型:函数模板与类模板;

在刚刚的 模板语法形式 一节中提到的例子就是函数模板的形式。

函数模板

template<typename T>
T add(T x,T y){

}

add函数是前文所提到的例子,使用该模板技术之后,我们的 T 类型就可以用来代替上述一大串add函数中的各种类型啦。

函数模板示例 以及 实例化、特化概念

#include <cstddef>
#include <iostream>
#include <string>
#include <string.h>
using namespace std;

//函数模板示例
//template<typename T>//模板参数列表
template<class T> //使用class的效果和typename是一样的
T add(T x, T y){
	cout << "T add(T , T) " << endl;
	return x+y;
}

//函数模板与函数模板之间也是可以进行重载的
template<class T> //使用class的效果和typename是一样的
T add(T x, T y, T z){
	cout << "T add(T , T, T) " << endl;
	return x+y+z;
}

//当我们发现有些时候我们的函数模板并不适用
//需要将函数特例化写出来的时候,就需要采用下面这种形式
//表示这个函数是我们函数模板的一个特化
//模板特化又分为模板的全特化与偏特化(部分特化)
//   全特化:将模板的参数列表中的参数全部以特殊版本的形式写出来(如下面的const char* add函数);
//   偏特化(部分特化):将模板参数列表中的参数类型,至少有一个没有特化出来
template<>
const char* add(const char* ps1,const char* ps2){
	cout << "const char* add(const char*,const char*)" << endl;
	size_t len1 = strlen(ps1);
	size_t len2 = strlen(ps2);
	size_t len = len1+len2+1;
	char* pstr = new char[len]();
	strcpy(pstr,ps1);
	strcat(pstr,ps2);
	return pstr;
}

//上面的函数模板在 经过模板参数列表的推导之后 成为下面的函数,被称为模板函数
//也可以说是从抽象到具象的一种 实例化
//实例化可被区分为 隐式实例化和显式实例化
int add(int x,int y){
	cout << "int ad(int , int) " << endl;
	return x+y;
}

/*不难发现上面两个函数(add(int x,int y) 与 add(T x, T y))之间发生了重载关系
 * 即普通函数与函数模板之间可以进行重载
 * 经测试可以发现 普通函数 是优先于 函数模板 被调用的
 * */

void test(){
	int ia = 3, ib = 4, ic = 5;
	double da = 3.3, db = 8.8;
	string s1 = "hello",s2 = "world";

	//add(ia,ib)这句代码我们并未显式声明ia和ib的类型,是靠编译器进行隐式推导出来的
	//因此这是一种隐式实例化
	cout << "add(ia,ib) = " << add(ia,ib) << endl;
	//而add<double>(da,db)这句代码我们显式声明了da和db的类型
	//因此这是一种显式实例化
	cout << "add(da,db) = " << add<double>(da,db) << endl;
	cout << "add(s1,s2) = " << add(s1,s2) << endl;
	cout << "add(ia,ib,ic) = " << add(ia,ib,ic) << endl;
	
	//模板的特化示例
	const char* str1 = "hebei";
	const char* str2 = "wuhan";
	//如果不进行模板的特化,下面这行代码将报错,因为两个const char*无法进行相加
	cout << "add(str1,str2) = "  <<add(str1,str2) << endl;
}

int main(){
	
	test();

	return 0;
}

模板参数列表参数类型的剖析

在这里插入图片描述

函数模板被分为 头文件 与 实现文件 的情况分析

问题研究的就是把函数模板的声明写到头文件中,把其实现写到另外一个文件里去,然后在测试文件里面进行测试一下来分析这种情况。

头文件中声明函数模板:
在这里插入图片描述
在实现文件中实现该函数模板:
在这里插入图片描述
测试文件中进行测试:
在这里插入图片描述
编译运行:
在这里插入图片描述
可以发现在函数模板声明与实现分文件存放时编译会出现问题,编译器找不到经过模板参数列表推导后的模板函数的定义。

所以得出一条重要结论:

对于模板而言,不能将头文件与实现文件分开(不能将声明与实现分开,否则会报错)。

但是如果非要将函数模板的头文件与实现文件进行分开编写的话,可以在头文件中去include实现文件:
在这里插入图片描述
注意要删去实现文件中的头文件引入嗷,否则会报重定义的问题:
在这里插入图片描述
编译运行,此时就没有问题了:
在这里插入图片描述
这与inline内敛函数是类似的。

成员函数的函数模板

上面聊的都是非类中成员函数的普通函数模板,现在我们来聊聊类中成员函数的函数模板。
直接看代码示例以及注释解析:

#include <iostream>

using namespace std;

class Point{
	public:
		Point(double dx = 0.0,double dy = 0.0)
		:_dx(dx)
		 ,_dy(dy)
		{
			cout << "Point(double =0.0,double =0.0)" << endl;
		}
		
		//成员函数也是可以设置为模板形式的
		//给模板参数列表设置默认参数long
		template<typename T=long>
		T func(){
			return _dx;
		}

		~Point(){
			cout << "~Point()" << endl;
		}
	private:
		double _dx;
		double _dy;
};

void test(){
	Point pt(1,2);
	//调用时通过<>传递给func函数的模板参数列表告知其T类型为int
	//或者也开以给模板参数列表设置默认类型参数
	//这样才能正常调用
	cout << "pt.func() = " << pt.func<int>();
}

int main(){
	test();
	return 0;
}

可变模板参数 – C++11新特性

可变模板参数是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。

基本形式:

template<typename ...Args>//这里的Args被称为模板参数包,表示这里面有N个typename参数

void func(Args...args){ //这里的args被称为函数参数包,功能同上

}

代码示例:

#include <iostream>

using namespace std;

//可变模板参数,传0到任意个参数,参数类型与个数都不确定
//...后面不一定要写Args,只是习惯上那么写
template <typename ...T> //T是模板参数包
void print(T ...t){ //t是函数参数包
	//打印模板参数个数和函数参数个数
	cout << "sizeof...(T) = " << sizeof...(T) << endl;
	cout << "sizeof...(t) = " << sizeof...(t) << endl;
}

//上面的show方法使用到了递归却没有退出条件
//因此我们要手动给一个空的退出条件
void show(){
	cout << endl;
}

//打印参数包中的数据的打印方法
template <typename T,typename ...Args>
void show(T t,Args ...args){
	cout << t << " ";
	//相当于...在args前面时是一个打包参数的过程,即打包操作
	//...在args后面时则变成了一个拆解参数的过程,即解包操作
	show(args...);//递归遍历
}


void test(){
	//通过下面的调用可以发现
	//此时我们可以传递任意多的参数进去
	print();
	print(1,"hello");//int string
	print(1,true,3.3,"helloworld");//int bool double string
}

void test2(){
	show(1,3.3);//递归
	//调用过程:
	//1、第一次调用show时,1是第一个参数,所以被打印,然后后面一坨被打包进下一个show函数中当作参数
	//    cout << 1 << " ";
	//    show(3.3);
	//2、第二次调用时,3.3是第一个参数,所以被打印,后面已经没有参数了,所以调用了空参的show
	//    cout << 3.3 << " ";
	//    show();
	//3、第三次调用时,因为没有参数了,而函数模板的第一个参数是必须要有的,所以这里的show因为
	//   没有参数于是只能去调用无参的同名show函数
	//   cout << endl;
	//   最终打印换行结束递归
	
}

int main(){
	test2();
	return 0;
}

类模板

形式如下:

template<typename T>
class Stack{
	private:
		T* data;
};

类模板示例

其实基本和函数模板没有什么太大分别,只要前面的函数模板研究清楚基本上就没有问题,一点点小的区别直接看代码示例即可:

#include <iostream>

using namespace std;

//类模板
template<typename T,size_t kSize = 10>
class Example{

public:
	Example():_data(new T[kSize]()){
		cout << "Example()" << endl;
	}
	//析构函数我们选择在类外进行实现
	~Example();
private:
	T* _data;
};

//类模板和函数模板的用法基本没有区别,只要注意一下一个点即可
//就是在类外进行成员函数实现时,必须要声明模板嗷
//因为Example是一个类模板,属于一种抽象类型,T是不确定的
//所以我们在类外写其函数实现时也要带上模板参数列表
template<typename T,size_t kSize>
Example<T,kSize>::~Example(){
	if(_data){
		delete[] _data;
		_data = nullptr;
	}
}

void test(){
	Example<int,20> example;
}

int main(){
	test();
	return 0;
}

模板的嵌套

类模板与类模板之间可以嵌套,类模板与函数模板之间也可以嵌套:

template<typename T> //类模板
class A{
	template<typename K> //类模板
	class B{
		template<typename ...Args> //函数模板
		T func(Args ...args){
		
		}
	}
}

模板也可以作参数

就是将模板作为参数放入模板参数列表中:

template<template<class T1> class T2, class T3,int num>

模板注意事项

成员函数模板不能被设计为virtual

成员函数模板不能声明为virtual的原因主要是因为C++中的虚函数机制和模板机制在实现上有一些根本性的差异。

首先,理解一下虚函数的工作原理。在C++中,虚函数用于支持动态绑定,使得在基类中声明的虚函数在派生类中可以被重写。当通过基类指针或引用调用一个虚函数时,运行时系统会根据对象的实际类型确定要调用的函数版本。

然而,模板并不是在编译时实例化,而是在运行时根据实际参数类型进行实例化。这意味着模板实例化的代码通常在编译时就确定了,而不是在运行时。

由于虚函数和模板的这种根本性差异,将成员函数模板声明为virtual会导致一些问题。例如,当你在派生类中为成员函数模板提供一个新的实现时,由于虚函数的动态绑定特性,你可能会覆盖基类中的实现,而不是添加一个新的实现。这显然不是你想要的结果。

因此,为了避免这种混淆和潜在的错误,C++标准规定成员函数模板不能被声明为virtual。如果你需要为不同的类型提供不同的行为,你应该使用模板特化和条件编译来实现,而不是尝试将模板和虚函数结合使用。

小结

模板是C++引入的新特性,也是标准模板库STL的基础,模板有函数模板和类模板之分,两种应用有很多相似之处。
学习模板,最重要的是理解模板定义(函数模板定义、类模板定义)与具体定义(函数定义和类定义)的不同,模板不是定义,要通过实例化(通过模板)或者特化(避开模板)来生成具体的函数或者类定义,再调用函数或者创建类的对象。

模板支持嵌套,这就是说可以在一个模板里面定义另一个模板。以模板(类,或者函数)作为另一个模板(类,或者函数)的成员,也称为成员模板。同时,模板也可以作为另一个模板的参数,出现在类型参数表中。

模板的使用在日常工作当中使用较少,除非是专门做一些库组件开发的会用的比较多,掌握本文的内容应付日常的工作学习已经足够(若想把所有的模板内容全部学会的话那比C++语言本身都还要繁杂),因此能够在看一些开源代码时知道其代码用到模板的时候在干嘛即可,比如每个C++er都会学习的 STL 的源码就应用了大量的模板相关的知识。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apache ActiveMQ 远程代码执行漏洞分析

漏洞简介 Apache ActiveMQ官方发布新版本&#xff0c;修复了一个远程代码执行漏洞&#xff0c;攻击者可构造恶意请求通过Apache ActiveMQ的61616端口发送恶意数据导致远程代码执行&#xff0c;从而完全控制Apache ActiveMQ服务器。 影响版本 Apache ActiveMQ 5.18.0 before …

docker部署firefox浏览器,实现远程访问

拉取firefox镜像&#xff0c;部署代码 docker run -d --name firefox -e TZAsia/Hong_Kong -e DISPLAY_WIDTH1920 -e DISPLAY_HEIGHT1080 -e KEEP_APP_RUNNING1 -e ENABLE_CJK_FONT1 -e VNC_PASSWORD12345678ABCabc -p 5800:5800 -p 5900:5900 -v /docker/firefox/config:/…

阿里云计算平台大数据基础工程技术团队直聘!!!

大数据基础工程技术团队&#xff0c;隶属于阿里云智能集团计算平台事业部&#xff0c;是一支负责阿里集团、公共云和混合云场景计算平台大数据&AI产品的稳定性建设、架构&成本优化、运维产品ABM&#xff08;Apsara Big data Manager&#xff09;研发和售后技术专家支持…

05、Kafka ------ 各个功能的作用解释(主题和分区 详解,用命令行和图形界面创建主题和查看主题)

目录 CMAK 各个功能的作用解释&#xff08;主题&#xff09;★ 主题★ 分区★ 创建主题&#xff1a;★ 列出和查看主题 CMAK 各个功能的作用解释&#xff08;主题&#xff09; ★ 主题 Kafka 主题虽然也叫 topic&#xff0c;但它和 Pub-Sub 消息模型中 topic 主题及 AMQP 的 t…

好用的AI写作软件,这6款助你轻松写作

这几年&#xff0c;AI在线写作平台在国内市场上呈现出蓬勃发展的态势&#xff0c;这些写作软件能够帮助用户快速生成高质量的文章。下面我将介绍国内的6款AI在线写作平台&#xff0c;一起来看看吧&#xff01; 第一个爱制作AI 爱制作AI是拥有智能创作的AI在线写作平台之一&…

深兰科技AI医疗健康产品获3000台采购订单

12月6日&#xff0c;武汉某企业与深兰科技签署协议&#xff0c;一次性采购3000台深兰科技AI生理健康检测仪——扁鹊。 深兰科技AI生理健康检测仪——扁鹊是深兰科技推出的人体生理指标检测产品。基于AI生物技术、融合互联网医疗及AIoT技术&#xff0c;深兰科技AI生理健康检测仪…

限制选中指定个数CheckBox控件(2/2)

实例需求&#xff1a;工作表中有8个CheckBox控件&#xff08;下文中简称为控件&#xff09;&#xff0c;现在需要实现限制用户最多只能勾选4个控件。 在上一篇博客中已经实现了这个需求&#xff0c;其基本思路是用户选中第5个控件时&#xff0c;事件代码将取消勾选最后一个选中…

弱光图像增强算法(6大算法附程序),一站式解决论文实验比较部分

过往几年大量从事弱光图像增强的炒菜工作。 为了方便科研比较&#xff0c;也就是主观视觉比较和定量比较&#xff0c;提供一个集成程序给各位参考 非常简单&#xff0c;只需要点击Main.PY和修改输出的路径即可 本次收集的6类算法(EnlightenGAN, RUAS, SCI, ZeroDCE, ZeroDCE…

python封装接口自动化测试套件 !

在Python中&#xff0c;我们可以使用requests库来实现接口自动化测试&#xff0c;并使用unittest或pytest等测试框架来组织和运行测试套件。以下是一个基本的接口自动化测试套件封装示例&#xff1a; 首先&#xff0c;我们需要安装所需的库&#xff1a; pip install requests …

运动耳机怎么选?2024年运动耳机推荐,运动蓝牙耳机排行榜10强

​在现代生活中&#xff0c;音乐和运动已经成为很多人生活不可分割的一部分。运动耳机在这样的背景下变得越来越受欢迎&#xff0c;它们不仅可以在运动时提供音乐的陪伴&#xff0c;还能增加运动时的乐趣和动力。但是&#xff0c;面对市面上众多不同类型的运动耳机&#xff0c;…

Linux进程通信之管道

目录 1、无名管道 1.无名管道的特点 2.pipe函数创建管道 3.图例 2、命名管道&#xff08;FIFO&#xff09; 1.命名管道的特点 2.mkfifo 函数-创建命名管道 3.示例 1.循环读取数据 2.循环写入数据 1、无名管道 管道通常指的就是无名管道&#xff0c; 1.无名管道的特点…

校招行测,认知能力测验,④破解数量关系测试题

数量关系&#xff0c;值得是数量计算、对比和分析&#xff0c;每种题型都有一定的规律性&#xff0c;如果善于终结也是容易掌握的&#xff0c;当然&#xff0c;只有见多&#xff0c;才能识广&#xff0c;最好的方式就是&#xff0c;锻炼&#xff0c;刷题&#xff0c;就算是临时…

3D Web可视化开发工具包HOOPS Communicator:提供Web端浏览大型模型新方案!

前言&#xff1a;HOOPS Communicator是Tech Soft 3D旗下的主流产品之一&#xff0c;具有强大的、专用的高性能图形内核&#xff0c;专注于基于Web的高级3D工程应用程序。其由HOOPS Server和HOOPS Web Viewer两大部分组成&#xff0c;提供了HOOPS Convertrer、Data Authoring的模…

类和对象的定义以及使用

文章目录 1. 类和对象的基本概念1.1 JAVA是面向对象语言1.2 类和对象的描述 2. 类与对象的定义与使用2.1 类的定义格式2.2 类的实例化(对象的创建)2.3 举个例子 3. 对象的构造及初始化3.1构造方法3.1.1构造方法的定义3.1.2 构造方法的特性 4.2 默认初始化5.4 就地初始化 4.this…

九州金榜如何让孩子在家庭教育中更优秀

​ 每个人在出生时就有上天恩赐的两份礼物&#xff0c;一份是血脉相连的亲情&#xff0c;一份是家庭的关爱与教育。 最早接触的人就是父母&#xff0c;最早接触的教育就是家庭教育&#xff0c;这对孩子的影响极为深远。 这种家庭教育相比较学校教育&#xff0c;不仅有言传教…

认知能力测验,⑤破解图形推理测试题,校招社招网申在线测评必用

认知能力测试&#xff0c;如今是每个求职者必须要面对的&#xff0c;有的人可以顺顺利利通过&#xff0c;而有的人只能够遗憾止步。想要通过认知能力测验&#xff0c;并不是一件易事&#xff0c;而今天要说的图形推理&#xff0c;仅仅是其中的一个部分&#xff0c;抛砖引玉&…

2024.01.09.Apple_UI_BUG

我是软件行业的&#xff0c;虽然不是手机设计的&#xff0c;但是这个设计真的导致经常看信息不完整&#xff0c;要下拉的。 特别读取文本或者其他文件的时候&#xff0c;上面有个抬头就是看不到&#xff0c;烦&#xff0c;体验感很差

Requests库的接口测试实现

Requests库是在接口测试中被广泛运用的库&#xff0c;包括模拟请求的下发&#xff0c;请求相关配置和响应结果的获取&#xff0c;核心主体都是通过request库完成。在接口测试中使用非常频繁。 一、Requests库环境搭建 接口测试的核心从模拟请求开始。在Python中&#xff0c;通…

机器学习:数据处理与特征工程

机器学习中的数据处理和特征工程是非常关键的步骤&#xff0c;它们直接影响模型的性能和泛化能力。以下是一些常见的数据处理和特征工程技术&#xff1a; 数据处理&#xff1a; 缺失值处理&#xff1a; 处理数据中的缺失值&#xff0c;可以选择删除缺失值、填充均值/中位数/众…

HarmonyOS应用开发学习笔记 ArkTS 布局概述

一、布局概述 布局指用特定的组件或者属性来管理用户页面所放置UI组件的大小和位置。在实际的开发过程中&#xff0c;需要遵守以下流程保证整体的布局效果 确定页面的布局结构。分析页面中的元素构成。选用适合的布局容器组件或属性控制页面中各个元素的位置和大小约束。 二…