【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.mzph.cn/news/683380.shtml

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

相关文章

记录一下最近遇到的几个二叉树的题型(附好用的遍历模板)

107. 二叉树的层序遍历 II 102. 二叉树的层序遍历 987. 二叉树的垂序遍历 以上三题可共用一个模板&#xff08;dfs记录数的col和row)&#xff0c;不同之处就是使用哈希表的时候调整一下key和value&#xff1a; # Definition for a binary tree node. # class TreeNode: # …

【Linux笔记】进程间通信之管道

一、匿名管道 我们在之前学习进程的时候就知道了一个概念&#xff0c;就是进程间是互相独立的&#xff0c;所以就算是两个进程是父子关系&#xff0c;其中一个进程退出了也不会影响另一个进程。 也因为进程间是互相独立的&#xff0c;所以两个进程间就不能直接的传递信息或者…

网络安全威胁,如何解决缓冲区溢出攻击

目录 一、什么是网络安全 二、什么是缓冲区 三、缓冲区溢出 四、缓冲区溢出攻击的类型 一、什么是网络安全 网络安全&#xff08;Network Security&#xff09;指的是保护计算机网络及其相关设备、系统和数据免受未经授权访问、破坏、篡改、窃取或滥用的威胁和攻击。随着网…

【doghead】VS2022 win11 安装配置WSL2 以编译linux端的cmake项目并运行1

Visual Studio 2022 在Windows上编译调试WSL2 CMake Linux工程 好像是我自己的vs2022的一个插件支持rust https://github.com/kitamstudios/rust-analyzer.vs/blob/master/PREREQUISITES.md Latest rustup (Rust Toolchain Installer). Install from here. Welcome to Rust!Th…

ElasticSearch分词器和相关性详解

目录 ES分词器详解 基本概念 分词发生时期 分词器的组成 切词器&#xff1a;Tokenizer 词项过滤器&#xff1a;Token Filter 停用词 同义词 字符过滤器&#xff1a;Character Filter HTML 标签过滤器&#xff1a;HTML Strip Character Filter 字符映射过滤器&#x…

『 C++ - STL 』位图(BitMap)与布隆过滤器(Bloom Filter)

文章目录 &#x1f9f8; 位图(BitMap)概念&#x1f9f8; 位图的实现&#x1fa85; 总体框架&#x1fa85; 位图的数据插入&#x1f9e9; 左移操作与右移操作的区别 &#x1fa85; 位图的数据删除&#x1fa85; 位图的数据查找&#x1fa85; 位图整体代码(供参考) &#x1f9f8;…

(06)Hive——正则表达式

Hive版本&#xff1a;hive-3.1.2 一、Hive的正则表达式概述 正则表达式是一种用于匹配和操作文本的强大工具&#xff0c;它是由一系列字符和特殊字符组成的模式&#xff0c;用于描述要匹配的文本模式。 Hive的正则表达式灵活使用解决HQL开发过程中的很多问题&#xff0c;本篇文…

代码随想录算法训练营第32天| 122.买卖股票的最佳时机II、55. 跳跃游戏、45.跳跃游戏II

122.买卖股票的最佳时机II 完成 思路&#xff1a; 把每天的利润最大化&#xff0c;即可达到总利润的最大化。 把利润拆分到每一天&#xff0c;能获利就买。 代码 class Solution {public int maxProfit(int[] prices) {int res 0;for (int i 0; i < prices.length-1; i…

Activation of network connection failed(ubuntu连不上网)

ubuntu连不上网&#xff0c;看了好几个方法找到个有用的记录一下 1. 还原默认设置 2. 更改适配器&#xff1a;加上vmware bridge protocol

使用Vue.js输出一个hello world

导入vue.js <script src"https://cdn.jsdelivr.net/npm/vue2/dist/vue.js"></script> 创建一个标签 <div id"app">{{message}}</div> 接管标签内容&#xff0c;创建vue实例 <script type"text/javascript">va…

关于Django的中间件使用说明。

目录 1.中间件2. 为什么要中间件&#xff1f;3. 具体使用中间件3.1 中间件所在的位置&#xff1a;在django的settings.py里面的MIDDLEWARE。3.2 中间件的创建3.3 中间件的使用 4. 展示成果 1.中间件 中间件的大概解释&#xff1a;在浏览器在请求服务器的时候&#xff0c;首先要…

【无标题】JAVA学习-集合.编写equals和hashCode

编写equals()和hashCode()方法需要遵循以下原则&#xff1a; 1. equals()方法&#xff1a; - 使用instanceof关键字检查传入的对象是否为当前类的实例。 - 检查传入的对象是否为null。 - 检查传入的对象的类型是否与当前对象的类型相同。 - 比较对象的属性是否相等&#x…

如何在 Ubuntu VPS 上使用 Celery 与 RabbitMQ 来做队列

简介 异步或非阻塞处理是一种将某些任务的执行与程序的主要流程分离的方法。这为您提供了几个优势&#xff0c;包括允许用户界面代码在没有中断的情况下运行。 消息传递是程序组件用来通信和交换信息的一种方法。它可以同步或异步实现&#xff0c;并且可以允许离散进程进行无问…

2月14日作业

1、安装tftp服务器和nfs服务器&#xff0c;准备需要下载到开发板文件&#xff0c;存放在指定下载文件夹下&#xff0c;准备需要挂载到开发板文件夹&#xff0c;存放在指定挂载文件夹中。 2、ubuntu和开发板组网&#xff0c;关闭防火墙&#xff0c;关闭杀毒软件&#xff0c;配置…

探索XGBoost:自动化机器学习(AutoML)

探索XGBoost&#xff1a;自动化机器学习&#xff08;AutoML&#xff09; 导言 自动化机器学习&#xff08;AutoML&#xff09;是一种通过自动化流程来构建、训练和部署机器学习模型的方法。XGBoost作为一种强大的机器学习算法&#xff0c;也可以用于AutoML。本教程将介绍如何…

NLP快速入门

NLP入门 课程链接&#xff1a;https://www.bilibili.com/video/BV17K4y1W7yb/?p1&vd_source3f265bbf5a1f54aab2155d9cc1250219 参考文档链接1&#xff1a;NLP知识点&#xff1a;Tokenizer分词器 - 掘金 (juejin.cn) 一、分词 分词是什么&#xff1f; 每个字母都有对应…

【Web】从零开始的js逆向学习笔记(上)

目录 一、逆向基础 1.1 语法基础 1.2 作用域 1.3 窗口对象属性 1.4 事件 二、浏览器控制台 2.1 Network Network-Headers Network-Header-General Network-Header-Response Headers Network-Header-Request Headers 2.2 Sources 2.3 Application 2.4 Console 三、…

车载诊断协议DoIP系列 —— DoIP会话模式(安全与非安全)

车载诊断协议DoIP系列 —— DoIP会话模式(安全与非安全) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖…

react 插槽

问题开发当中会经常出现组件十分相似的组件&#xff0c;只有一部分是不同的 解决&#xff1a; 父组件:在引用的时候 import { Component } from "react"; import Me from "../me";const name <div>名称</div> class Shoop extends Compone…

MongoDB聚合操作符:$accumulator

$accumulator可以定义自定义累加器操作符。累加器是一种操作符&#xff0c;可在文档通过管道时保持其状态&#xff08;如&#xff1a;总数、最大值、最小值和相关数据&#xff09;。$accumulator操作符支持执行自定义的JavaScript函数&#xff0c;可以实现MongoDB查询语言不支持…