【C++初阶】模版入门看这一篇就够了

news2024/11/28 10:50:45

文章目录

  • 1. 泛型编程
  • 2. 函数模板
    • 2. 1 函数模板概念
    • 2. 2 函数模板格式
    • 2. 3 函数模板的原理
    • 2. 4 函数模板的实例化
    • 2. 5 模板参数的匹配原则
    • 2. 6 补充:使用调试功能观察函数调用
  • 3. 类模板
    • 3 .1 类模板的定义格式
    • 3. 2 类模板的实例化


1. 泛型编程

在C语言中,如果我们要实现交换函数swap,为了让这个函数能兼容所有的类型,我们需要swap_intswap_double等等,为每个类型单独实现一个对应的交换函数,还要起不同的名称防止冲突。
而在C++中,我们可以使用函数重载,不再需要使用其他函数名,但是似乎仍然需要为每个类型单独实现一个函数:

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;
}
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}
//....

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

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

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

工业上生产不同颜色的同一种金属制品,会先制作一个模具,然后通过往模具中加入实现准备好的不同颜色的液态金属就可以制作出不同的成品。

如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多不必要的代码。
巧的是前人早已将树栽好,我们只需在此乘凉,这便是泛型编程模板

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

模板有两种:
模板
我们来一一介绍。

2. 函数模板

2. 1 函数模板概念

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

2. 2 函数模板格式

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

swap函数为例:

template<typename T>
void swap(T& a, T& b)
{
	T temp = a;
	a = b;
	a = temp;
}

注意:typename是用来定义模板参数的关键字,也可以使用class(不能使用struct)。

2. 3 函数模板的原理

函数模板是一个蓝图它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。
所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

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

2. 4 函数模板的实例化

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

  1. 隐式实例化:让编译器根据实参推演模板参数的实际类型。
#include<iostream>
using namespace std;
template<class T>
T Add(T A, T B)
{
	return A + B;
}


int main()
{
	int a = 10;
	char b = 'a';
	cout << Add(a, b) << endl;
	return 0;
}

该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参aT推演为int,通过实参bT推演为char类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 还是char类型而报错。
注意在模板中,编译器一般不会进行类型转换操作。
这时候有两种解决办法:

  1. 手动进行强制类型转换
Add(a, (int)b);

这样就可以了,形参接收到的是强制类型转换后的数值。

  1. 显式实例化:在函数名后的<>中指定模板参数的实际类型。
int main()
{
	int a = 10;
	char b = 'a';
	cout << Add<int>(a, b) << endl;
	return 0;
}

这个写法可以先给Add的模板的Tint,使其生成对应的函数后再接收参数,那么如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

但是要注意,对于swap函数来说,上面的两种做法都不能使swap的两个参数不同,第一个做法中,强制类型转换产生的是一个临时对象,具有常性,不可修改;第二个中隐式类型转换产生的也是一个临时对象,不可修改。除非写一个两个实参类型不同的函数模板,比如:

#include<iostream>
using namespace std;

template<class T,class Y>
void swap(T& a, Y& b)
{
	//交换函数的实现
}

因为这样的函数模板没有什么实际意义,所以就不实现了。
不过顺带一提,对于这样的函数,如果我们想在调用时指定其两个参数的类型,可以这么写:

#include<iostream>
//using namespace std;	//这里注意:std命名空间中有一个swap函数模版

template<class T,class Y>
void swap(T& a, Y& b)
{
	//交换函数的实现
}

int main()
{
	int a = 10;
	char b = 'a';
	swap<int, char>(a, b);	//<>中不同的类型名用 , 隔开
	std::cout << a << ' ' << b << std::endl;
	return 0;
}

代码中也提到了全局的swap函数模板,所以在实践中我们不需要手动去实现swap函数模板,直接使用库里的就行了(库函数的swap不支持不同类型变量的交换)。

2. 5 模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,即使该函数模板可以被实例化为这个非模板函数。
#include<iostream>
using namespace std;

// 显式写出 int 的Add函数
int Add(const int& a, const int& b)
{
	return a + b;
}

// 这个函数模板也可以实例化出 int 的Add函数
template<class T>
T Add(const T& a, const T& b)
{
	return a + b;
}

int main()
{
	cout << Add(1, 2) << " " << Add(10.2, 15.0) << endl;
	return 0;
}

对于这种情况,编译器会优先使用显式写出来的那个int类型的Add函数,而不会使用函数模板去重新生成一个。

  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
#include<iostream>
using namespace std;

// 显式写出 int 的 Add 函数
int Add(const int& a, const int& b)
{
	return a + b;
}

//这个函数模板也能实例化出 int 的 Add 函数
template<class T,class Y>
T Add(const T& a, const Y& b)
{
	return a + b;
}

int main()
{
	cout << Add(1, 2.0) << endl;
	return 0;
}

这种情况下,编译器就不会使用上面显式写出来的函数,而是使用函数模板实例化出来一个新的函数。

  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
    这一条实际上在2.4中已经介绍过了,不再赘述。

2. 6 补充:使用调试功能观察函数调用

我们怎么能知道函数调用的是哪个模板,或者说哪个显式写出来的函数呢?
虽然可以通过在函数内部写一条输出语句输出不同的内容实现,但这在实际开发中显得有些麻烦,要给所有的可能的模板都加上输出,所以一般不采用。
我们可以使用调试功能来观察,这里以VS2022的调试功能为例:
图例

当程序运行到函数调用的这一行时,按F11(逐语句),就可以进入调用的函数内部:
图例
可以发现,这个函数调用的就是这个函数模板(实例化出来的函数),如果是调用的其他模板或函数,程序就会执行到相应的位置。

3. 类模板

3 .1 类模板的定义格式

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

举例:

#include<iostream>
using namespace std;
template<typename T>
class Stack
{
public:
	Stack(size_t capacity = 4)
		:_size(0)
		,_capacity(capacity)
		//,_data(new T[capacity])	//注意不能这么写,详情请看类和对象(下)
	{
		_data = new T[_capacity];
	}
	// 析构函数略
	void push_back(const T& temp)
	{
		if (_size == _capacity)
		{
			T* newdata = new int[2 * _capacity];
			for (int i = 0; i < _size; i++)
			{
				newdata[i] = _data[i];
			}
			delete[] _data;
			_data = newdata;
		}
		_data[_size++] = temp;
	}

	void Print()
	{
		for (int i = 0; i < _size; i++)
		{
			cout << _data[i] << " ";
		}
		cout << endl;
	}

private:
	T* _data;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack<int> a;
	a.push_back(1);
	a.push_back(2);
	a.push_back(3);
	a.push_back(4);
	a.push_back(5);
	a.Print();
	return 0;
}

对于Stack类中的_data数组,它的类型完全是由模板参数控制的,在创建时可以根据需要自由选择种类
同时,对Print函数来说,cout这一非格式化输出也是很有意义的,因为它不可能使用printf函数去输出。

另外,对于类模板,不建议把成员函数的声明和定义拆分到不同的文件(.h和.cpp)中,会导致编译错误。
原因有二:

  1. 多重定义错误
    如果将模板的定义放在.cpp文件中,并且不在头文件中声明这些成员函数,则在每个包含该头文件的编译单元中都需要重新定义这些成员函数。这会导致链接错误,因为每个翻译单元都会看到一个不同的定义。
  2. 编译时实例化需求
    当编译器遇到模板类的使用时,它需要知道整个模板类的定义,以便它可以为特定的模板参数实例化模板。如果成员函数的定义不在同一个文件中,编译器就无法生成正确的代码。

3. 2 类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类

也就是说

Stack<int> a;

类模板创建类的时候,<int>是必须的,而模板函数在一些情况下不是必须的。

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章

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

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

相关文章

【JavaEE初阶】网络原理—关于TCP协议值滑动窗口与流量控制,进来看看吧!!!

前言 &#x1f31f;&#x1f31f;本期讲解关于TCP协议的重要的机制“连接的建立和断开”~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1…

5. STM32之TIM实验--输出比较(PWM输出,电机,四轴飞行器,智能车,机器人)--(实验5:PWM驱动直流电机)

作者:Whappy,日期:2024.10.29,决战STM32 直流电机的控制就比较简单了,只有数据线和地线,正接正转,反接反转,为了方便,本实验采用H桥电路来控制电机的正反转,H桥电路也很简单,就是4个MOS管构成的2路推挽输出电路. 注:基本上大功率器件,单片机基本上是无法驱动的,都是要靠一部分…

家用wifi的ip地址固定吗?换wifi就是换ip地址吗

在探讨家用WiFi的IP地址是否固定&#xff0c;以及换WiFi是否就意味着换IP地址这两个问题时&#xff0c;我们首先需要明确几个关键概念&#xff1a;IP地址、家用WiFi网络、以及它们之间的相互作用。 一、家用WiFi的IP地址固定性 家用WiFi环境中的IP地址通常涉及两类&#xff1a…

Python——自动化发送邮件

在数字化时代&#xff0c;电子邮件是商务沟通和个人联络的重要工具。自动化邮件发送可以节省时间&#xff0c;提高效率。Python&#xff0c;作为一种强大且灵活的编程语言&#xff0c;提供了多种库来支持邮件的自动化发送。本文将详细介绍如何使用Python的smtplib和email库来编…

DRC-20开发指南:新一代区块链代币标准

区块链技术自比特币诞生以来&#xff0c;迅速发展成为全球科技与金融领域的创新中心。随着区块链技术的普及&#xff0c;数字代币作为一种新兴的数字资产类别&#xff0c;逐渐为更多人所熟知。从ERC-20到BRC-20&#xff0c;代币标准的演进一直在推动着区块链生态系统的创新与发…

从零开始的JavaScript基础!

目录 一、JavaScript 的概述 二、如何在HTML页面中使用JS &#xff08;一&#xff09;、行内式 &#xff08;二&#xff09;、内嵌式 &#xff08;三&#xff09;、外链式 &#xff08;四&#xff09;、基本执行顺序 1.从上到下线性执行&#xff1a; 2.阻塞行为&#x…

“格格不入”的星瑞东方曜,燃油市场有麻烦了

文/王俣祺 导语&#xff1a;在新能源汽车蓬勃发展的当下&#xff0c;吉利近日推出的星瑞东方曜可以说有些“格格不入”了。那么&#xff0c;这款被称为“10万以内唯一选择”的新车&#xff0c;究竟带来了哪些新变化&#xff0c;又会给燃油市场中苦苦坚持的“战友们”带来多大的…

【ACM出版,EI稳定检索,九大高校联合举办, IEEE Fellow支持】2024年计算机视觉与艺术研讨会(CVA 2024)

在线投稿&#xff1a;学术会议-学术交流征稿-学术会议在线-艾思科蓝 2024年计算机视觉与艺术国际学术会议&#xff08;CVA 2024&#xff09;作为2024年人工智能、数字媒体技术与交互设计国际学术会议&#xff08;ICADI 2024)的分会。此次大会旨在汇聚全球在计算机视觉与艺术…

【MySql】-0.1、Unbunt20.04二进制方式安装Mysql5.7和8.0

1、下载Mysql二进制报&#xff08;选择Linux的通用版本&#xff09; mysql官网地址&#xff1a;https://downloads.mysql.com/archives/community/ wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.26-linux-glibc2.12-x86_64.tar wget https://downloads.…

H7-TOOL的LUA小程序教程第16期:脉冲测量,4路PWM,多路GPIO和波形打印(2024-10-25, 更新完毕)

LUA脚本的好处是用户可以根据自己注册的一批API&#xff08;当前TOOL已经提供了几百个函数供大家使用&#xff09;&#xff0c;实现各种小程序&#xff0c;不再限制Flash里面已经下载的程序&#xff0c;就跟手机安装APP差不多&#xff0c;所以在H7-TOOL里面被广泛使用&#xff…

【Cri-Dockerd】安装cri-dockerd

cri-dockerd的作用&#xff1a; 在k8s1.24之前。k8s会通过dockershim来调用docker进行容器运行时containerd&#xff0c;并且会自动安装dockershim&#xff0c;但是从1.24版本之前k8s为了降低容器运行时的调用的复杂度和效率&#xff0c;直接调用containerd了&#xff0c;并且…

java实现的音视频格式转化器

一、前言 最近写了一款图形界面版的音视频格式转化器&#xff0c;可以实现将多种视频之间进行转化&#xff0c;非常好用&#xff0c;如将AVI转换为&#xff0c;TS&#xff0c;FLV&#xff0c;MP4等。音频可将MP3转成WAV。 二、实现 1.需引入相关maven依赖。 <!-- 核心包 -…

Java知识巩固(十二)

I/O JavaIO流了解吗&#xff1f; IO 即 Input/Output&#xff0c;输入和输出。数据输入到计算机内存的过程即输入&#xff0c;反之输出到外部存储&#xff08;比如数据库&#xff0c;文件&#xff0c;远程主机&#xff09;的过程即输出。数据传输过程类似于水流&#xff0c;因…

1.3 面向对象 C++面试问题

1.3.1 简述一下什么是面向对象,面向对象与面向过程的区别 什么是面向对象 面向对象&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是一种编程范式&#xff0c;它通过将现实世界中的实体抽象为“对象”来组织代码。面向对象编程关注对象及其交互&#x…

Visual Studio Code

代码自动保存 打开设置搜索auto save&#xff0c;设置为afterDelay 设置延迟时间&#xff0c;单位是毫秒 启用Ctrl鼠标滚轮对字体进行缩放 搜索Mouse Wheel Zoom&#xff0c;把该选项勾选上即可 Python插件 运行和调试Python

Hash表算法

哈希表 理论知识&#xff08;本文来自于代码随想录摘抄&#xff09;什么是哈希常见的三种哈希结数组&#xff1a;set:map:其他常用方法或者技巧&#xff08;自己总结的&#xff09; 练习题和讲解有效的字母移位词349. 两个数组的交集1. 两数之和454. 四数相加 II15. 三数之和 总…

广泛的四款录屏工具专业软件解析!!!

当代社会的进步&#xff0c;电脑、手机等数码产品的普及&#xff0c;改变了我们的沟通形式&#xff0c;只需要动动手指&#xff0c;就能实现视频连接&#xff0c;影像播放等。有时候就需要我们录屏留存。在市面上的录屏软件种类繁多&#xff0c;从简洁易用的小工具到功能丰富的…

通俗直观介绍ChatGPT背后的大语言模型理论知识

“AI 的 iPhone 时刻到来了”。非算法岗位的研发同学’被迫’学习 AI&#xff0c;产品岗位的同学希望了解 AI。但是&#xff0c;很多自媒体文章要么太严谨、科学&#xff0c;让非科班出身的同学读不懂&#xff1b;要么&#xff0c;写成了科幻文章&#xff0c;很多结论都没有充分…

力扣21 : 合并两个有序链表

链表style 描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例&#xff1a; 节点大小相同时&#xff0c;l1的节点在前 何解&#xff1f; 1&#xff0c;遍历两个链表&#xff0c;挨个比较节点大小 同时遍…

MATLAB——入门知识

内容源于b站清风数学建模 目录 1.帮助文档 2.注释 3.特殊字符 4.设置MATLAB数值显示格式 4.1.临时更改 4.2.永久改 5.常用函数 6.易错点 1.帮助文档 doc sum help sum edit sum 2.注释 ctrl R/T 3.特殊字符 4.设置MATLAB数值显示格式 4.1.临时更改 format lon…