【C++】模板(超详细!!!!!!)

文章目录

  • 前言
  • 1. 泛型编程
  • 2. 函数模板
    • 2.1 函数模板概念
    • 2.2 函数模板格式
    • 2.3 函数模板的原理
    • 2.4 函数模板的实例化
    • 2.5 模板参数的匹配原则
    • 2.6 声明和定义分离
  • 3. 类模板
    • 3.1 类模板的定义格式
    • 3.2 类模板的实例化
  • 4. 模板分离编译
    • 4.1 什么是分离编译
    • 4.2 模板的分离编译
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:
C++模板是一项强大的特性,为泛型编程提供了有力的支持。通过使用函数模板和类模板,程序员可以编写通用的代码,使其适用于不同的数据类型,从而提高代码的复用性和灵活性。本文将深入探讨C++模板的基础概念、泛型编程的思想以及函数模板和类模板的使用方法。通过深入了解模板,读者将能够更好地运用这一强大的特性,写出更具通用性和可维护性的代码。

提示:以下是本篇文章正文内容,下面案例可供参考

1. 泛型编程

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

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>
返回值类型 函数名(参数列表){}

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

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

2.3 函数模板的原理

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

#include <iostream>
using namespace std;
template<class T>
void Swap(T& left, T& right)
{
	T tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	double d1 = 2.0;
	double d2 = 5.0;
	Swap(d1, d2);
	int a1 = 10;
	int a2 = 20;
	Swap(a1, a2);

	cout << d1 <<" "<< d2 << endl;
	cout << a1 <<" "<<a2 << endl;
	return 0;
}

通过反汇编,从汇编代码中可以看出,通过模板和参数,编辑器自己推导出了两个函数,可以这么认为,编译器可以通过模板实例化出多个函数,这些函数本来需要我们自己实现,但是现在编译器可以帮我们做了!!!!
在这里插入图片描述

在这里插入图片描述

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

总结:

我们不能调用函数模板,实际上我们调用的是函数模板实例化生成的对应类型的函数

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.0, d2 = 20.0;
	Add(a1, a2);
	Add(d1, d2);

	/*
	Add(a1, d1);
	该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
	通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
	编译器无法确定此处到底该将T确定为int 或者 double类型而报错
	注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅	
	*/

	// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
	Add(a1, (int)d1);//强制转化 

	return 0;
}

2. 显式实例化:在函数名后的<>中指定模板参数的实际类型

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	Add<int>(a1, d1);

	return 0;
}

2.5 模板参数的匹配原则

1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

#include <iostream>
using namespace std;
int add(int left,int right)//非模板函数
{
	return left + right;
}
template<class T>
T add(T left, T right)//函数模板
{
	return left + right;
}
int main()
{
	int x = 10;
	int y = 20;
	cout << add(10, 20) << endl;// 与非模板函数匹配,编译器不需要特化
	cout << add<int>(10, 20) << endl; 调用编译器特化的Add版本(利用函数模板实例化后的函数)
	return 0;
}

正常情况下,非模板函数优先,如果指定类型,那么函数模板优先,这一点通过逐语句调试可以看出

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

// 专门处理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函数
}

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

2.6 声明和定义分离

template<class T>
void Swap(T& left, T& right);

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

也可以声明定义分离,不同的是模板参数声明定义都要给

3. 类模板

3.1 类模板的定义格式

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

示例(动态顺序表):
看这个代码仓库,查看代码

3.2 类模板的实例化

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

// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;

Vector类名,Vector< int >才是类型

4. 模板分离编译

4.1 什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式

4.2 模板的分离编译

模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义

// a.h
template<class T> 
void Swap(const T& left, const T& right);
// a.cpp
template<class T> 
void Swap(const T& left, const T& right) 
{
    return left + right; 
}
// main.cpp
#include"a.h"
int main()
{
    Swap(1, 2);
    Swap(1.0, 2.0);
    return 0; 
}

运行之后出现链接错误!!!LNKerro
在这里插入图片描述

在上述的代码中,由于函数模板的定义和声明被分开放置在不同的文件中(头文件 a.h 和源文件
a.cpp),你可能会遇到链接错误。为了解决这个问题,可以考虑以下几种方法:

方法一:显式实例化
a.cpp 文件的末尾显式实例化 Add 函数模板:

// a.cpp
template int Add<int>(const int& left, const int& right);
template double Add<double>(const double& left, const double& right);

这样可以确保在 main.cpp 中调用时,编译器能够找到模板的实例化。

方法二:将模板定义和声明放在头文件中
将函数模板的定义和声明都放在头文件 a.h 中:

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

这样可以避免分离编译时的链接错误,同时也符合模板的典型使用方式。

方法三:使用inline关键字
a.h 文件中使用 inline 关键字:

// a.h
template<class T> 
inline T Add(const T& left, const T& right) 
{
    return left + right; 
}

inline 关键字将告诉编译器在每个使用模板的地方生成模板的实例,从而避免链接错误。

选择其中一种方法即可,根据实际需求和项目规模来决定使用哪种方式。
总结:个人理解,函数模板和类模板不支持声明和定义分离,因为在编译器在推导类型时,只是推到的是声明中的类型,但是链接时发现,链接的函数类型T确实不可知的,这里不好理解,建议记住也行!!!!(本质还是T不明确的问题

总结

C++模板是一种强大的编程工具,使得泛型编程在语言层面得以实现。通过函数模板和类模板,我们能够编写通用、灵活的代码,适应不同的数据类型。泛型编程的思想带来了更高的代码抽象程度和可重用性。通过本文的学习,读者将能够深入理解C++模板的核心概念,并能够运用它们解决实际问题,提高代码的可维护性和适用性。

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

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

相关文章

python-分享篇-GUI界面开发-PyQt5-禁止窗体显示最大化按钮及调整窗体大小

代码 # -*- coding: utf-8 -*-# Form implementation generated from reading ui file nochange.ui # # Created by: PyQt5 UI code generator 5.11.3 # # WARNING! All changes made in this file will be lost! 禁止窗体显示最大化按钮及调整窗体大小from PyQt5 import QtCo…

CleanMyMac X2024中文版值不值得考虑下载?

CleanMyMac X是一款值得考虑的Mac电脑清理和优化工具。它提供了多种功能&#xff0c;如智能清理、系统垃圾清理、恶意软件移除、个人隐私保护、优化加速等&#xff0c;可以帮助用户解决Mac系统维护问题&#xff0c;保持Mac电脑的最佳运行状态。 CleanMyMac X全新版下载如下: …

C++的进阶泛型编程学习(1):函数模板的基本概念和机制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、模板1.1 模板的概念1.1.1 形象的解释&#xff1a;模板就是通用的模具&#xff0c;目的是提高通用性1.1.1 模板的特点&#xff1a;1.1.2 综述模板的作用 1.2…

上位机图像处理和嵌入式模块部署(上位机主要功能)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前关于机器视觉方面&#xff0c;相关的软件很多。比如说商业化的halcon、vision pro、vision master&#xff0c;当然也可以用opencv、pytorch自…

计算机服务器中了360后缀勒索病毒怎么办?360后缀勒索病毒处理流程

网络技术的不断应用与发展&#xff0c;为企业的生产运营提供了有利保障&#xff0c;越来越多的企业走向数字化办公模式&#xff0c;并且企业的发展离不开数据支撑&#xff0c;重视数据安全成为了众多企业关心的主要话题。春节前后&#xff0c;云天数据恢复中心接到很多企业的求…

用163邮箱或者outlook接收国科大邮箱的邮件

使用如图下路径&#xff0c;创建一个新的密码&#xff0c;用于在163大师邮箱或者outlook登录即可 如果不行&#xff0c;则需要手动配置邮箱服务器 参考网址&#xff1a;中国科学院邮件系统帮助中心

cool Node后端 中实现中间件的书写

1.需求 在node后端中&#xff0c;想实现一个专门鉴权的文件配置&#xff0c;可以这样来解释 就是 有些接口需要token调用接口&#xff0c;有些接口不需要使用token 调用 这期来详细说明一下 什么是中间件中间件顾名思义是指在请求和响应中间,进行请求数据的拦截处理&#xf…

【sql】sqlite3数据库

一、介绍 SQLite是一个轻量级的、开源的嵌入式数据库&#xff0c;由D. Richard Hipp使用C语言编写。由于其资源占用少、性能良好和零管理成本的特点&#xff0c;SQLite在嵌入式系统中得到了广泛应用&#xff0c;如Android和iPhone等操作系统中都有内置的SQLite数据库供开发人员…

尚硅谷最新Node.js 学习笔记(二)

目录 五、HTTP协议 5.1、概念 5.2、请求报文的组成 5.3、HTTP 的请求行 5.4、HTTP 的请求头 5.5、HTTP 的请求体 5.6、响应报文的组成 5.7、创建HTTP服务 操作步骤 测试 注意事项 5.8、浏览器查看 HTTP 报文 查看请求行和请求头 查看请求体 查看URL查询字符串 …

如何在Django中使用分布式定时任务并结合消息队列

如何在Django中使用分布式定时任务并结合消息队列 如何在Django中使用分布式定时任务并结合消息队列项目背景与意义实现步骤1. 安装Celery和Django-celery-beat2. 配置Celery3. 配置Django-celery-beat4. 定义定时任务5. 启动Celery worker 和 beat6. Celery 指令7. 对接消息队…

ClickHouse--08--SQL DDL 操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 SQL DDL 操作1 创建库2 查看数据库3 删除库4 创建表5 查看表6 查看表的定义7 查看表的字段8 删除表9 修改表9.1 添加列9.2 删除列9.3 清空列9.4 给列修改注释9.5 修…

基于LightGBM的回归任务案例

在本文中&#xff0c;我们将学习先进的机器学习模型之一&#xff1a;Lightgbm。在对XGB模型进行了越来越多的改进以获得更好的性能之后&#xff0c;XGBoost是一种极限梯度提升机器&#xff0c;但通过lightgbm&#xff0c;我们可以在没有太多计算的情况下实现类似或更好的结果&a…

对(一维)数组与指针的深入理解(1)

目录 1.数组名的理解2.使用指针访问&#xff08;一维&#xff09;数组3.&#xff08;一维&#xff09;数组传参的本质 1.数组名的理解 以前我们在使用指针访问数组内容时&#xff0c;有这样的代码&#xff1a; #include <stdio.h>int main() {int arr[10] { 1,2,3,4,5…

CTF-web 之 burp suite 使用

burp suite 使用 一般其是作为一个辅助工具&#xff0c;直接使用来解题的部分是少数&#xff0c;我们可以使用它来观察请 求和响应&#xff0c;并且可以反复的提交&#xff0c;关键的是他还带有很多其他的功能&#xff0c;在我们做题的过程中&#xff0c; 使用的关键点包括&…

C++LNK1207中的 PDB 格式不兼容;请删除并重新生成

在打开别人发的C文件时&#xff0c;可能出现该报错 解决办法 打开资源管理器&#xff0c;找到原来的路径 进入Debug&#xff0c; 找到对应的PDB文件删除即可。

OpenAI ChatGPT 记忆功能怎么实现?

你的聊天助手现在能“记住”你的对话了&#xff01; 2月14日凌晨&#xff0c;OpenAI宣布正在测试ChatGPT的新功能——记住用户提问内容&#xff0c;并自由控制内存。这意味着&#xff0c;ChatGPT能帮你记住那些重要的聊天内容&#xff0c;让你的对话更流畅、更自然。 想象一下…

政安晨:【示例演绎】【用TensorFlow编写线性分类器】—— 同时了解一点TensorFlow与Keras的基本概念

环境准备 如果小伙伴们第一次接触TensorFlow与Keras&#xff0c;可以先看一下我的这篇文章做些环境准备&#xff08;可以先忽略这篇文章里面代码实现部分&#xff0c;仅查看这里的环境准备部分即可&#xff09;。 文章如下&#xff1a; 政安晨&#xff1a;【详细解析】【用T…

Ps:焦点堆栈

焦点堆栈 Focus Stacking是一种摄影和图像处理技术&#xff0c;通过合并多张在不同焦距拍摄的照片来创建一张具有更大景深的图像&#xff0c;特别适用于微距摄影、风景摄影和任何需要在整个场景中保持尖锐对焦的情况。 ◆ ◆ ◆ 拍摄注意事项 1、使用三脚架 为了确保图像之间…

Spring 事务原理总结四

作为一名认知有限的中国人&#xff0c;我对年的喜爱&#xff0c;胜过其他一切&#xff0c;因为它给了我拒绝一切的合理理由。每到这个时候&#xff0c;我都会用各种理由来为自己的不作为开脱&#xff0c;今年亦是如此。看着频频发出警报的假期余额&#xff0c;我内心的焦躁变得…

VSCode无法连接远程服务器的两种解决方法

文章目录 VSCode Terminal 报错解决方式1解决方式2you are connected to an OS version that is unsupported by Visual Studio Code解决方法 VSCode Terminal 报错 直接在terminal或cmd中使用ssh命令可以连接服务器&#xff0c;但是在vscode中存在报错&#xff0c;最后一行为…
最新文章