当前位置: 首页 > news >正文

关于模板的大致认识【C++】

文章目录

  • 函数模板
    • 函数模板的原理
    • 函数模板的实例化
    • 模板参数的匹配原则
  • 类模板
    • 类模板的定义格式
    • 类模板的实例化
  • 非类型模板参数
  • typename 与class
  • 模板的特化
    • 函数模板特化
    • 类模板特化
      • 全特化
      • 偏特化
  • 模板的分离编译

函数模板

函数模板的原理

template <typename T> //模板参数 ——类型 
void Swap(T& x1, T& x2)
{T tmp = x1;x1 = x2;x2 = tmp;
}
int main()
{int a = 0, b = 1;double c = 1.1, d = 2.2;swap(a, b);swap(c, d);int* p1 = &a;int* p2 = &b;swap(p1, p2);return 0;
}

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

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

函数模板的实例化

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

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.1, d2 = 20.2;//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数 cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;//实参传递的类型, 推演T的类型  cout << Add( a1, (int)d1  ) << endl;cout << Add( (double)a1, d1) << endl;//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换 cout << Add<int> (a1, d1) << endl;cout << Add<double>(a1, d1) << endl;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.1, d2 = 20.2;//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数 cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;//实参传递的类型, 推演T的类型  cout << Add( a1, (int)d1  ) << endl;cout << Add( (double)a1, d1) << endl;//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换 cout << Add<int> (a1, d1) << endl;cout << Add<double>(a1, d1) << endl;return 0;
}
template<class T >T * Alloc(int n )
{return new T[n];
}int main(){//有些函数无法自动推导函数模板的类型,实例化对应的参数,只能显式实例化double *p1 = Alloc <double>(10);return 0;}

模板参数的匹配原则

类模板

类模板 ,无法推演实例化,所以类模板都是显式实例化

class Stack
{
public :Stack(int capacity = 3){_array= new  T[capacity];_size = 0;_capacity = 0; }void Push(const T &  data){_array[_size++] = data;}~Stack(){free(_array);_size = _capacity = 0;}
private :T * _array;int _size;int _capacity;
};
int main()
{Stack <int> s1(); // int Stack <double> s2();//double Stack <char> s3();//char//Stack <int ,doule> s2();return 0; 
}

类模板的定义格式

函数类模板的声明和定义分离

template<class T>class Stack
{
public://声明 Stack(int capacity );void Push(const T& data){_array[_size++] = data;}~Stack(){free(_array);_size = _capacity = 0;}
private:T* _array;int _size;int _capacity;
};
//定义 
template<class T>Stack<T>::Stack(int capacity )
{_array = new  T[capacity];_size = 0;_capacity = 0;
}
int main()
{Stack <int> s1(); // int Stack <double> s2();//double Stack <char> s3();//char//Stack <int ,doule> s2();return 0;
}

对于普通类,类名和类型是一样的,但是对于类模板 ,类名和类型是不一样的 上面的代码中Stack是类名 ,但是Stack < T >是类型

类模板的实例化

非类型模板参数

模板参数可分为类型形参非类型形参

类型形参: 出现在模板参数列表中,跟在class或typename关键字之后的参数类型名称。

非类型形参: 用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

如果此时有一个需求,实现一个静态数组的类,就需要用到非类型模板参数

template<class T, size_t N> //N:非类型模板参数
// N是常量 ,且N必须是整形
class StaticArray
{
public:size_t arraysize(){return N;}
private:T _array[N]; //利用非类型模板参数指定静态数组的大小
};
int main()
{StaticArray<int, 10> a1; //定义一个大小为10的静态数组cout << a1.arraysize() << endl; //10StaticArray<int, 100> a2; //定义一个大小为100的静态数组cout << a2.arraysize() << endl; //100return 0;
}

注意:

1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。

typename 与class

一般来说,typename 和class 没有什么区别,但是在有一种情景下是有区别的

template<class Container>
void Print(  const Container& v  )
{//Container::const_iterator it = v.begin();是不行的// 因为编译不确定Container::const_iterator是类型还是对象// typename的作用就是明确告诉编译器这里是类型,等模板实例化再去找typename Container::const_iterator it = v.begin();while (it != v.end() ){cout << *it << " ";it++;}cout << endl;
}int main()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);Print(v);list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);Print(lt1);return 0;
}

以上情景需要使用typename

模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理

模板的特化 即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。
模板特化中分为函数模板特化 和 类模板特化。

函数模板特化

1、首先必须要有一个基础的函数模板。
2、关键字template后面接一对空的尖括号<>。
3、函数名后跟一对尖括号,尖括号中指定需要特化的类型。
4、函数形参表必须要和模板函数的基础参数类型完全相同,否则不同的编译器可能会报一些奇怪的错误。

template<class T>
bool Less(T left ,T right)
{return left < right;
}
//函数模板的特化
template<>
bool Less<int * >(int * left, int* right)
{return *left < *right;
}
int main()
{int a = 1, b = 2;cout << Less(1, 2);cout << endl;cout << Less(&a, &b);return 0;
}

类模板特化

不仅函数模板可以进行特化,类模板也可以针对特殊类型进行特殊化实现,并且类模板的特化又可分为全特化和偏特化(半特化)。

全特化

全特化即是将模板参数列表中所有的参数都确定化。

例如,对于以下类模板:

template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}private:T1 _d1;T2 _d2;
};template<>
class Data<int, char>
{
public:Data() {cout<<"Data<int, char>" <<endl;}private:int _d1;char _d2;
};void TestVector()
{Data<int, int> d1;Data<int, char> d2;
}

偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。

偏特化有以下两种表现方式:
1、部分特化 ,将模板参数类表中的一部分参数特化

template<class T1, class T2>
class Data
{
public:Data(){cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:Data() {cout<<"Data<T1, int>" <<endl;}
private:T1 _d1;int _d2;
};
int main()
{Data<double, int> d1;//偏特化Data<int, double> d2;//调用基础的模板return 0;
}

2、参数更进一步的限制

template<class T1, class T2>
class Data
{
public:Data(){cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};
//偏特化:对类型的进一步限制
template<class T1, class T2>
class Data<T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
private:
};int main()
{Data<int, int > d1;Data<int, double > d2;Data<int*, double > d3;Data<int*, double* > d4;Data<void*, void* > d5;return 0;
}

模板的分离编译

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

在分离编译模式下,我们一般创建三个文件

一个头文件用于进行函数声明
一个源文件用于对头文件中声明的函数进行定义
最后一个源文件用于调用头文件当中的函数

举个例子:如果对一个加法函数模板进行分离编译

在这里插入图片描述

如果这三个文件生成可执行文件时,会在链接阶段产生报错
这是为什么呢?

C / C++程序要运行起来一般要经历以下四个步骤:
预处理、编译、汇编、链接

如果需要详细的了解这四个步骤,请点击这里

在这里插入图片描述

这三个文件经过预处理后就只剩下两个文件了

Visual Studio平台:

预处理后就进入编译阶段,
虽然在 main.i 当中有调用Add函数的代码,但是在 main.i 里面也有Add函数模板的声明,在编译阶段并不会发现任何语法错误,在编译阶段将 Add.i 和 main.i 翻译成了汇编语言,即将 Add.i 和 main.i 变成了Add.s 和 main.s

进入汇编阶段,利用 Add.s 和 main.s 这两个文件分别生成了两个目标文件,
即将 Add.s 和 main.s变成了Add.o和 main.o

最后将Add.o和 main.o进行链接操作生成a.out,
但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义
原因是函数模板T还没有实例化,可以将模板定义的位置显式实例化。(这种方法不实用,不推荐使用)。

总结:
编译阶段看有没有声明,声明是一种承诺
在编译阶段,检查声明,查看函数名、参数、返回值是否对上,如果对上,则编译阶段通过
进入链接阶段,编译器会拿着修饰后的函数去其他文件符号表查找,如果查到,则链接阶段通过

模板分离编译失败的原因:
在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。

解决方法

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。
  2. 模板定义的位置显式实例化。(这种方法不实用,不推荐使用)。

如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注你们的每一次支持都将转化为我前进的动力!!!

相关文章:

关于模板的大致认识【C++】

文章目录 函数模板函数模板的原理函数模板的实例化模板参数的匹配原则 类模板类模板的定义格式类模板的实例化 非类型模板参数typename 与class模板的特化函数模板特化类模板特化全特化偏特化 模板的分离编译 函数模板 函数模板的原理 template <typename T> //模板参数…...

C#如何遍历类的属性,并获取描述/注释

要获取属性的描述/注释&#xff0c;需要使用System.ComponentModel命名空间中的DescriptionAttribute。可以通过反射获取属性上的DescriptionAttribute&#xff0c;并获取其Description属性值。 首先&#xff0c;需要引入System.ComponentModel命名空间&#xff1a; using Sy…...

ffmpeg 子进程从内存读取文件、提取图片到内存

除了网络、文件io&#xff0c;由python或java或go或c等语言开启的ffmpeg子进程还支持pipe&#xff0c;可以从stdin读入数据&#xff0c;输出转化后的图像到stdout。无需编译 ffmpeg&#xff0c;直接调用 ffmpeg.exe不香么&#xff01; “从内存读”可用于边下载边转码&#xf…...

Springboot+Netty+WebSocket搭建简单的消息通知

SpringbootNettyWebSocket搭建简单的消息通知 一、快速开始 1、添加依赖 <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.36.Final</version> </dependency> <dependency>…...

@RefreshScope静态变量注入

RefreshScope注解通常用于注入实例变量&#xff0c;而不是静态变量。由于静态变量与类直接关联&#xff0c;刷新操作无法直接影响它们。 如果你需要动态刷新静态变量的值&#xff0c;一种可行的方案是使用一个通过Value注解注入的实例变量&#xff0c;并在该实例变量的getter方…...

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测。 模型描…...

SAP 凭证项目文本 增强 demo2

SAP 凭证项目文本 增强 demo2 增强点 AC_DOCUMENT METHOD if_ex_ac_document~change_initial. DATA: ls_item TYPE accit, ls_exitem TYPE accit_sub, lv_sgtxt TYPE bseg-sgtxt, lv_bktxt TYPE bkpf-bktxt, lv_zuonr TYPE bseg-zuonr, lv_blart TYPE bkpf-blart, lv_zprono TY…...

一套基于C#语言开发的LIMS实验室信息管理系统源码

实验室信息管理系统&#xff08;LIMS)是指帮助实验室组织和管理实验数据的计算机软件系统&#xff0c;它将实验室操作有机地组织在一起&#xff0c;以满足实验室工作流程的所有要求。它能以不同的方式支持实验室的工作&#xff0c;从简单的过程(如样品采集和入库)到复杂的流程(…...

kubesphere部署rocketmq5.x,并对外暴露端口

kubesphere是青云开源的k8s管理工具&#xff0c;用户可以方便的通过页面进行k8s部署的部署&#xff0c;rocketmq则是阿里开源的一款mq平台&#xff0c;现在版本为5.1.3版本&#xff0c;较比4.x版本的rocketmq有比较大的调整&#xff1a;比如客户端的轻量化&#xff08;统一通过…...

5.8 汇编语言:汇编高效除法运算

通常情况下计算除法会使用div/idiv这两条指令&#xff0c;该指令分别用于计算无符号和有符号除法运算&#xff0c;但除法运算所需要耗费的时间非常多&#xff0c;大概需要比乘法运算多消耗10倍的CPU时钟&#xff0c;在Debug模式下&#xff0c;除法运算不会被优化&#xff0c;但…...

如何通过python来给手机发送一条短信?

要通过Python发送短信到手机,您可以使用不同的短信服务提供商的API。以下是一个使用Twilio和Sinch服务提供商的示例,您可以根据自己的选择来决定使用哪个。 使用Twilio发送短信: 首先,注册一个Twilio账户并获取您的账户SID、认证令牌和Twilio号码。 安装 twilio 包,如果您…...

无涯教程-PHP - IntlChar类

在PHP7中&#xff0c;添加了一个新的 IntlChar 类&#xff0c;该类试图公开其他ICU函数。此类定义了许多静态方法和常量&#xff0c;可用于操作unicode字符。使用此类之前&#xff0c;您需要先安装 Intl 扩展名。 <?phpprintf(%x, IntlChar::CODEPOINT_MAX);print (IntlCh…...

【Linux操作系统】Linux系统编程中信号捕捉的实现

在Linux系统编程中&#xff0c;信号是一种重要的机制&#xff0c;用于实现进程间通信和控制。当某个事件发生时&#xff0c;如用户按下CtrlC键&#xff0c;操作系统会向进程发送一个信号&#xff0c;进程可以捕获并相应地处理该信号。本篇博客将介绍信号的分类、捕获与处理方式…...

【PHP】基础语法变量常量

文章目录 PHP简介前置知识了解静态网站的特点动态网站特点 PHP基础语法代码标记注释语句分隔(结束)符变量变量的基本概念变量的使用变量命名规则预定义变量可变变量变量传值内存分区 常量基本概念常量定义形式命名规则使用形式系统常量魔术常量 PHP简介 PHP定义&#xff1a;一…...

Failed to resolve: com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0

在allprojects下的repositories闭包里面添加jcenter()和maven {url https://jitpack.io}&#xff0c;具体可以看你的第三方框架需要添加什么仓库&#xff0c;大多数都只需要上面两个。 我的build.gradle&#xff08;Project&#xff09;完整内容如下&#xff1a; buildscript …...

常用 Python IDE 汇总(非常详细)从零基础入门到精通,看完这一篇就够了

写 Python 代码最好的方式莫过于使用集成开发环境&#xff08;IDE&#xff09;了。它们不仅能使你的工作更加简单、更具逻辑性&#xff0c;还能够提升编程体验和效率。 每个人都知道这一点。而问题在于&#xff0c;如何从众多选项中选择最好的 Python 开发环境。初级开发者往往…...

【Hive】HQL Map 『CRUD | 相关函数』

文章目录 1. Map 增删改查1.1 声明 Map 数据类型1.2 增1.3 删1.4 改1.5 查 2. Map 相关函数2.1 单个Map 3. Map 与 String3.1 Map 转 string3.2 string 转 Map 1. Map 增删改查 1.1 声明 Map 数据类型 语法&#xff1a;map<基本数据类型, 基本数据类型> 注意是<>…...

ELF修复基本工作原理

ELF修复基本工作原理 ELF(Executable and Linkable Format)是一种常见的可执行文件和可链接文件的格式,广泛用于Linux和UNIX系统中。ELF修复是指对ELF文件进行修改或修复,以确保其正确加载和执行。 ELF修复的基本工作原理如下: 识别ELF文件:首先,需要识别和验证目标文…...

matlab实现输出的几种方式(disp函数、fprintf函数、print函数)

matlab实现输出的几种方式&#xff08;disp函数、fprintf函数、print函数&#xff09; 输出为文本、文件、打印 1、disp函数 显示变量的值&#xff0c;如果变量包含空数组&#xff0c;则会返回 disp&#xff0c;但不显示任何内容。 矩阵 A [1 0]; disp(A)结果 字符串 S …...

C/C++数据库编程

文章目录 0. Mysql安装与开发环境配置1. win10 Navicat 连接虚拟机的MySQL需要关闭防火墙2. 由于找不到libmysql.dIl, 无法继续执行代码。重新安装程序可能会解决此问题。3. 测试连接数据库&#xff0c;并插入数据4. C封装MySQL增删改查操作 0. Mysql安装与开发环境配置 MySQL…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

stm32wle5 lpuart DMA数据不接收

配置波特率9600时&#xff0c;需要使用外部低速晶振...

MySQL的pymysql操作

本章是MySQL的最后一章&#xff0c;MySQL到此完结&#xff0c;下一站Hadoop&#xff01;&#xff01;&#xff01; 这章很简单&#xff0c;完整代码在最后&#xff0c;详细讲解之前python课程里面也有&#xff0c;感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...

归并排序:分治思想的高效排序

目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法&#xff0c;由约翰冯诺伊曼在1945年提出。其核心思想包括&#xff1a; 分割(Divide)&#xff1a;将待排序数组递归地分成两个子…...

Java设计模式:责任链模式

一、什么是责任链模式&#xff1f; 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09; 是一种 行为型设计模式&#xff0c;它通过将请求沿着一条处理链传递&#xff0c;直到某个对象处理它为止。这种模式的核心思想是 解耦请求的发送者和接收者&#xff0c;…...