【C++深入浅出】模版初识

 


目录

一. 前言

二. 泛型编程

三. 函数模版 

3.1 函数模版的概念

3.2 函数模版的格式

3.3 函数模版的原理

3.4 函数模板的实例化

3.5 模板参数的匹配原则

四. 类模版

4.1 类模版的定义

4.2 类模版的实例化


一. 前言

        本期我们要介绍的是C++的又一大重要功能----模版。通过模版,我们可以很轻松的进行泛型编程,大大简化我们编程时的代码。

        本文的目标是让读者对模版有一定程度上的了解,以便后续STL的学习,对于模版更深层次的内容,我们放到以后再进行拓展。

        话不多说,开启我们今日的学习叭

二. 泛型编程

        假如现在有个需求,要求我们实现一个swap函数用于数据的交换,数据类型可能是整形浮点型字符型等等,通过我们之前学习的知识,你会如何进行实现呢?

        聪明的你可能会这样实现

//利用函数重载实现三种不同类型的swap函数
void Swap(int& x, int& y) //引用传参
{
	int tmp = x;
	x = y;
	y = tmp;
}

void Swap(double& x, double& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}

void Swap(char& x, char& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}

        是的,上面的代码确实实现了我们的需求,但还是存在一些不足,体现在如下两个方面:

  1. 几个重载函数仅仅是类型不同,代码逻辑都是一样的,代码的复用率比较低。每当有新类型出现时,用户就需要再自行添加一个函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错(想必你写的时候也是CV的叭)

        依照以上两点不足,那我们能不能只给编译器提供一个模板,到时候让编译器根据我们指定的类型自动生成所需要的函数呢?

        答案是有的。C++为我们提供了模版的概念,支持我们进行泛型编程。模版就好比一个模具,我们可以从倒入任何种类(类型)的东西到模具中,最终都会形成我们想要的对应图案(即生成相应的代码)

        泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模版是泛型编程的基础。模板分为函数模版类模版

三. 函数模版 

3.1 函数模版的概念

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

3.2 函数模版的格式

        函数模版的定义格式如下所示:

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

        例如,我们将上面的swap函数定义为函数模版如下:

template<typename T> //T是类型名
void swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

其中,typename是定义模版参数的关键字,也可以使用class关键字替代。

3.3 函数模版的原理

        我们可以把函数模板看做一个蓝图,它本身并不是一个函数,而是编译器根据调用函数时传入实参产生特定类型函数的一个模具。

        例如,当我们给swap函数传入double类型的参数,编译器就会自动根据模板生成一份参数为double类型的swap函数;而如果我们传入的是int类型的参数,编译器就会生成一份参数为int类型的swap函数。

        由此可见,模板就是将本来应该我们做的重复的事情交给了编译器去做,将手动敲出来的代码变为编译器自动生成。

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

3.4 函数模板的实例化

        当不同类型的参数使用函数模板时,编译器根据模版为这个类型生成一份函数代码,这个过程称为函数模板的实例化。模板参数实例化分为:隐式实例化显式实例化

        隐式实例化

        即编译器根据实参的类型自动推导模版参数的类型:

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

int main()
{
	double d1 = 2.0;
	double d2 = 5.0;
	Add(d1, d2); //d1和d2是double类型的,编译时编译器推导T为double类型,实例化double类型的Add函数

	int i1 = 0;
	Add(i1, d2); //此处编译会报错,因为Add只有一个模板参数T,而实参有两种类型,编译器推导时不知道将T推导为int还是double
	return 0;
}

         可以看到,编译器进行隐式实例化也不是胡乱实例化的,必须要有唯一的结果编译器才会进行实例化,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。

        要解决上面Add函数的问题,我们有两个解决方法:

        1、用户自行进行强制类型转换

Add(i1, (int)d2); //将两个实参都变为int类型

Add((double)i1, d2); //将两个实参都变为double类型

        2、使用下面的显式实例化

        显式实例化

        调用函数时我们可以在函数名后使用<>来指定模板参数的实际类型,如下:

int main()
{
	int a = 10;
	double b = 20.0;
	double c = 30.0;
	// 显式实例化
	Add<int>(a, b);  //显式指定T实例化为int
	Add<double>(b, c);  //显式指定T实例化为double
	return 0;
}

        当实例化的函数形参和实参的类型不匹配时,编译器就会尝试进行隐式类型转换,如果无法转换成功编译器将会报错

template<typename T>
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int i = 10;
	double d = 10.0;
	Swap<int>(i, d); //尽管我们显式进行实例化了,但这里编译器仍然会报错,原因是形参和实参无法进行隐式类型转换
	return 0;
}

3.5 模板参数的匹配原则

        1、 一个非模板函数可以和一个同名的函数模板同时存在,并且该函数模板还可以被实例化为这个非模板函数。换句话说,我们既可以使用非模板函数,也可以使用编译器特化的模板函数。

//Swap函数模板
template<class T>
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

//处理int类型的Swap函数
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 10, b = 20;
	Swap(a, b); //使用下面专门处理int的Swap函数
	Swap<int>(a, b); //使用上面编译器特化后的Swap函数
	return 0;
}

        2、对于非模板函数和同名函数模板,调用时会调用最匹配的那个函数。在其他条件相同的情况下,编译器会优先调用非模板函数。如果模板可以产生一个更匹配的函数,则将选择模板。
 

//Add函数模板
template<class T1,class T2>
T1 Add(T1 x, T2 y)
{
	return x + y;
}

//处理int类型的Add函数
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	Add(10, 20); //虽然可以通过模板生成<int,int>的模板函数,但编译器并不会去生成,而是去调用已经存在的非模板函数
	Add(10, 20.0); //通过函数模板生成的函数会更加匹配,故调用函数模版生成的Add函数
	return 0;
}

         3、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

//Add函数模板
template<class T>
T Add(T x, T y)
{
	return x + y;
}

int Swap(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 20;
	double d = 10.0;
	Add(d, a); //这里会报错,Add是模板函数,不支持自动类型转换
	Swap(d, a); //这里编译通过,d会发生隐式类型转换为int类型
	return 0;
}


四. 类模版

4.1 类模版的定义

        在C++中,除了普通函数支持模版,类也支持模版,我们把这样的类称为类模版。下面给出一个类模版的定义格式:

template<class T1, class T2, ..., class Tn> //模板参数
class 类模板名
{
	// 类内成员定义
};

        下面我们用vector类来演示一下类模板的定义方法以及注意事项(只是演示噢,并不是真正的vector模拟实现)

//vector类
template<class T>
class Vector
{
public:
    //构造时使用new动态申请空间
	Vector(int capacity = 10)
		:_p(new T[capacity])
		_capacity(capacity)
		,_size(0)
	{}

	void puch_back(const T& x); //尾插
	void pop_back(); //尾删

	~Vector(); //析构时需要调用delete释放空间
private:
	T* _p;
	int _capacity;
	int _size;
};

//在类模板外定义成员函数,需要加上模版参数列表
template<class T>
Vector<T>::~Vector()
{
	if (_p != nullptr)
	{
		delete[] _p;
		_capacity = _size = 0;
	}
}

值得注意的是:和模板函数一样,类模板并不是一个具体的类,它只是一个模具,只有进行实例化后才会变成一个具体的类。

4.2 类模版的实例化

        与函数模板实例化不同,类模板不支持隐式类型推导,类模板实例化需要在类模板名字后用<>显式指定模板参数的实际类型。注意:类模板的名字不是真正的类,只有指定类型实例化后才是真正的类。

//模板的实例化
int main()
{
    //Vector是类名,Vector<int>才是类型名,才能用来定义对象
	Vector<int> vi(20); //用参数为int的Vector类定义对象
	Vector<double> vd(10); //用参数为double的Vector类定义对象
	return 0;
}

        类模版经过类型实例化后得到的类我们称作模板类,通过模板类我们就可以定义许多相应的类对象,三者的关系可用下图表示: 


 以上,就是本期的全部内容啦🌸

制作不易,能否点个赞再走呢🙏

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

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

相关文章

单例模式详解【2023年最新】

一、单例模式概念 单例模式是一种创建型设计模式&#xff0c;它确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。它的目的是限制一个类只能创建一个对象&#xff0c;以确保在整个应用程序中只有一个共享的实例。 单例模式通常用于以下情况&#xff1a;…

git stash的使用方法

git stash的使用方法 应用场景 当我们在开发一个新功能的时候&#xff0c;或者开发到一半&#xff0c;然后就收到了线上master 出现了bug&#xff0c;当分支开发已经进行了或者进行到一半了&#xff0c;这时怎么办呢&#xff1f; 这时解决方案有两种&#xff1a;一种是先先将当…

数据分析和互联网医院小程序:提高医疗决策的准确性和效率

互联网医院小程序已经在医疗领域取得了显著的进展&#xff0c;为患者和医疗从业者提供了更便捷和高效的医疗服务。随着数据分析技术的快速发展&#xff0c;互联网医院小程序能够利用大数据来提高医疗决策的准确性和效率。本文将探讨数据分析在互联网医院小程序中的应用&#xf…

在HTML当中引入Vue控件,以element-ui为例

前情&#xff1a;需要实现一个同时满足按天、按周、按月选择的时间选择器&#xff0c;但是以HTML为基础写的都不太满足我的要求&#xff0c;要么只能按天选择&#xff0c;要么就是想选择久远的时间得点很久&#xff0c;除非自己写捷径&#xff0c;所以就看上了element-ui的这个…

泰州市旅游景点门票预订管理系统 vue+uniapp微信小程序

本文从管理员、用户的功能要求出发&#xff0c;泰州市旅游景点管理小程序中的功能模块主要是实现用户、景点类型、景区信息、门票预定。经过认真细致的研究&#xff0c;精心准备和规划&#xff0c;最后测试成功&#xff0c;系统可以正常使用。分析功能调整与泰州市旅游景点管理…

ubuntu 下的 使用anaconda 环境运行python 项目

pycharm部署django项目到云服务器的详细流程_编程网 anaconda 安装环境 Ubuntu安装Anaconda详细步骤&#xff08;Ubuntu22.04.1&#xff0c;Anaconda3-2023.03&#xff09;-CSDN博客 ubuntu下Anaconda安装与使用教程_ubuntu 运行anaconda_fakerth的博客-CSDN博客 Anaconda教…

吴恩达《机器学习》1-2:什么是机器学习?

一、什么是机器学习&#xff1f; Arthur Samuel&#xff08;1959&#xff09;&#xff1a; 他定义机器学习为&#xff0c;在进行特定编程的情况下&#xff0c;给予计算机学习能力的领域。 Tom Mitchell&#xff08;1998&#xff09;&#xff1a; 他定义的机器学习是&#xff0c…

微服务框架Consul--新手入门

Consul Consul 是由 HashiCorp 开发的一款软件工具&#xff0c;提供了一组功能&#xff0c;用于服务发现、配置管理和网络基础设施自动化。它旨在帮助组织管理现代分布式和微服务架构系统的复杂性。以下是Consul的一些关键方面和功能&#xff1a; 服务发现&#xff1a;Consul …

【ChatGPT 01】ChatGPT基础科普

1. 从图灵测试到ChatGPT 1950年&#xff0c;艾伦•图灵(Alan Turing)发表论文**《计算机器与智能》&#xff08; Computing Machinery and Intelligence&#xff09;&#xff0c;提出并尝试回答“机器能否思考”这一关键问题。在论文中&#xff0c;图灵提出了“模仿游戏”&…

Go语言用Resty库编写的音频爬虫代码

目录 一、Go语言与Resty库简介 二、音频爬虫的实现 1、确定抓取目标 2、使用Resty发送HTTP请求 3、解析响应数据 4、下载音频文件 5、并发下载音频文件 三、注意事项 总结 随着互联网的飞速发展&#xff0c;网络爬虫逐渐成为数据获取和分析的重要工具。在音频领域&…

队列概念|循环队列的实现

前言 今天我们将学习循环队列实现&#xff0c;我们首先介绍队列的概念和结构&#xff0c;之后一步步讲解循环队列由来与实现。 一、队列的概念与结构 1、队列的概念 队列&#xff1a; 只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表。队列是…

Leetcode—274.H指数【中等】

2023每日刷题&#xff08;十三&#xff09; Leetcode—274.H指数 算法思想 参考自灵茶山艾府 实现代码 int minValue(int a, int b) {return a < b ? a : b; }int hIndex(int* citations, int citationsSize){int cnt[5001] {0};int i;for(i 0; i < citationsSize; …

android开发使用OkHttp自带的WebSocket实现IM功能

一、背景 android app开发经常会有IM需求&#xff0c;很多新手不晓得如何入手&#xff0c;难点在于通讯不中断。其实android发展到今天&#xff0c;很多技术都很完善&#xff0c;有很多类似框架可以实现。例如有&#xff1a;okhttp自带的websocket框架、easysocket等等。本文主…

14 结构性模式-适配器模式

1 适配器模式介绍 适配器模式(adapter pattern )的原始定义是&#xff1a;将类的接口转换为客户期望的另一个接口&#xff0c;适配器可以让不兼容的两个类一起协同工作。 2 适配器模式原理 3 适配器模式应用实例 /*** SD卡接口**/ public interface SDCard {//读取SD卡Strin…

JavaEE-cookie和session

本部分内容包括 cookie基本概念&#xff0c;sendcookies和getcookies代码&#xff1b; session基本概念&#xff0c;session实现登陆界面&#xff1b; 上述过程中涉及的代码如下&#xff1a; 1 import javax.servlet.ServletException; import javax.servlet.annotation.WebSe…

spring-代理模式

代理模式 一、概念1.静态代理2.动态代理 一、概念 ①介绍 二十三种设计模式中的一种&#xff0c;属于结构型模式。它的作用就是通过提供一个代理类&#xff0c;让我们在调用目标 方法的时候&#xff0c;不再是直接对目标方法进行调用&#xff0c;而是通过代理类间接调用。让不…

搞定蓝牙-第六篇(HID

搞定蓝牙-第六篇&#xff08;HID&#xff09; ble与HIDHOGPGAPP与HID ESP32程序分析 ble与HID HOGP 我们发现&#xff0c;电脑连接了蓝牙键盘就可以直接使用了&#xff0c;不需要配置任何东西&#xff0c;那么&#xff0c;这两者是怎么通讯的呢。我们使用的电脑windows系统内…

离线语音通断器开发-稳定之后顺应新需求

使用云知声的US516p6方案开发了一系列的离线语音通断器&#xff0c;目前已经取得了不小的收获&#xff0c;有1路的&#xff0c;3路的&#xff0c;4路的&#xff0c;唛头和扬声器包括唛头线材也在不断的更新打磨中找到了效果特别好的供应商。 离线语音通断器&#xff0c;家用控…

【Java】HashSet集合用法

目录 HashSet 集合特点 示例代码 手写HashSet集合 HashSet 没有Get() HashSet 集合特点 HashSet 基于HashMap 来实现的&#xff0c;是一个不允许有重复元素的集合HashSet 允许有 null 值HashSet 是无序的&#xff0c;即不会记录插入的顺序HashSet集合实现了Set接口HashSet …

Spring IOC 和 AOP

核心概念 咱们这节就讲完了&#xff0c;在这节中我们讲了两个大概念&#xff0c;一个叫做IOC&#xff0c;一个叫做DI IOC是什么&#xff1f;是用对象的时候不要自己用new而是由外部提供&#xff0c;而spring在进行实现的时候是谁提供&#xff0c;就是IOC容器给你提供。 DI是什…