【C++】模板及模板的特化
目录
一,模板
1,函数模板
什么是函数模板
函数模板原理
函数模板的实例化
推演(隐式)实例化
显示实例化
模板的参数的匹配原则
2,类模板
什么是类模板
类模板的实例化
二,模板的特化
1,类模板的特化
全特化
偏特化
2,函数模板的特化——全特化
三,非类型模板参数
一,模板
1,函数模板
什么是函数模板
所谓函数模板,实际上是建立一个通用的函数,该函数类型和形参类型不具体指定,而是用一个表示任意类型的虚拟类型来代表(这里的任意类型可以任意选择,如 T)。
定义函数模板的一般形式:
template <typename T1, typename T2, ....., typename Tn>
返回值类型 函数名(参数列表)
{
// .....
}
其中template和class是关键字,typename 可以用class关键字代替,在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为
下面来看一下,完成多个不同类型的两个数据的交换
针对具体类型(常规函数)
// 完成两个整形变量的交换
void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}// 完成两个double型变量的交换
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;
}int main()
{int a = 1, b = 2;double c = 1.1, d = 2.2;char ch1 = 'a', ch2 = 'b';Swap(a, b);Swap(c, d);Swap(ch1, ch2);return 0;
}

通过上面可以看出,若想要完成以上三种不同类型的交换须要写三个交换函数,这是针对每种类型分别写出具体的交换函数;但是以上的三种交换函数除了参数的类型不一样,其他都是一样的,显得代码既冗余又不够简练。来看下面使用函数模板
跟具体类型无关(函数模板)
//函数模板
//template<class T>
template<typename T> // 声明一个类型模板参数 T>
void Swap(T& left, T& right) // 使用模板参数T来声明函数参数left和right
{T temp = left;left = right;right = temp;
}int main()
{int a = 1, b = 2;double c = 1.1, d = 2.2;char ch1 = 'a', ch2 = 'b';Swap(a, b);Swap(c, d);Swap(ch1, ch2);return 0;
}
在这个例子中,
T是一个类型模板参数,它告诉编译器我们希望这个函数能够处理多种类型。在函数模板的声明中,使用typename关键字(也可以使用class关键字,两者在函数模板中都是等价的)来声明类型模板参数。然后,使用类型模板参数
T来声明函数的参数 left和 right,它们都是类型为T的引用。这意味着可以传递任何类型的变量给swap函数,只要这两个变量的类型相同。
函数模板原理
当编译器遇到一个函数调用时,编译器会尝试根据传递给函数模板的实参类型来推导出模板参数的类型,一旦类型推导成功,编译器就会生成一个或多个具体的函数实例,这些实例的类型与推导出的模板参数类型相匹配。
注意:函数模板本身并不产生代码,它只是一个蓝图。只有在模板被实例化时,编译器才会生成具体的函数代码。


当实参a、b 是 int 时,编译器会把模板参数 T 推演成 int 类型,会实例化出一份具体类型的Swap 函数来调用;当实参a、b 是 double 时, 编译器会把模板参数 T 推演成 int 类型; char类型也一样。
注意:以上三个函数在实例化时虽然走的都是同一个函数模板,但是调用的不是同一个函数;只是用同一个函数模板实例化出了三份针对具体类型的函数

如下:

函数模板的实例化
推演(隐式)实例化
隐式实例化:编译器在调用一个模板函数时,根据提供的参数类型自动推断出模板参数的类型,并生成相应的函数实例。这个过程是编译器自动完成的,不需要程序员显式指定模板参数的类型。
// 声明一个函数模板
template <typename T>
T Add(T a, T b)
{return a + b;
}int main()
{int a1 = 3, a2 = 5;double d1 = 3.5, d2 = 6.5;// 隐式实例化:编译器根据参数类型(int)推断出模板参数T为intAdd(a1, a2); // 生成了 add<int>(int, int) 的实例 // 编译器根据参数类型(double)推断出模板参数T为doubleAdd(d1, d2); // 生成了 add<double>(double, double) 的实例 Add(a1, d1); // a1和d1是不同的类型,此时编译报错,编译器无法推演出具体的类型return 0;
}
上述第三个 Add(a1, d1); 该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为int 或者 double类型而报错
解决方法:1. 可以改成多参数的函数模板 如:template<typename T1, typename T2>
2. 手动强制类型转换 如:上面的 Add(a1, (int)d1); 或 Add((double)a1, d1);
3. 就是下面要说的 显式实例化
显示实例化
函数模板允许编写通用的函数,这些函数可以处理不同类型的数据。但是,在某些情况下,可能想要为特定的类型显式地实例化函数模板,以便在编译时生成具体的函数版本。
即:在函数名后的<>中指定模板参数的实际类型
// 声明一个函数模板
template <typename T>
T Add(T a, T b)
{return a + b;
}int main()
{int a1 = 3, a2 = 5;double d1 = 3.5, d2 = 6.5;// Add(a1, d1); // a1和d1是不同的类型,此时编译报错,编译器无法推演出具体的类型Add<int>(a1, d1); // 显式实例化 可强制为 int 类型实例化,并将参数 d1 强制转换为 int 类型// 或Add<double>(a1, d1); // 显式实例化 可强制为 double 类型实例化,并将参数 a1 强制转换为 double 类型return 0;
}
上面的模板与函数调用 Add(a1, d1) 不匹配,因为该模板要求两个函数参数的类型相同。但通过使用 Add<int>(a1, d1), 可强制为 int 类型实例化,并将参数 d1 强制转换为 int 类型,这样就可以与函数 Add<int>(int, int) 的第二个参数匹配。
模板的参数的匹配原则
1. 同时存在性:一个非模板函数可以和一个同名的函数模板同时存在。此外,这个函数模板还可以被实例化为这个非模板函数。
2. 优先调用非模板函数:当非模板函数和同名函数模板在参数上相匹配时,编译器会优先调用非模板函数,而不是从模板产生出一个实例(即有现成的就吃现成的)
3. 模板的更好匹配:如果模板可以产生一个具有更好匹配的函数,那么编译器会选择模板而不是非模板函数(即有更合适的就吃更合适的,没有就将就吃)。
// 函数模板,可以处理任何类型的加法
template <typename T>
T Add(T a, T b)
{return a + b;
}// 非模板函数,专门处理int类型的加法
int Add(int a, int b)
{return a + b;
}int main()
{int a1 = 3, a2 = 5;double d1 = 3.5, d2 = 6.5;// 调用非模板函数,因为参数是int类型,与非模板函数匹配 Add<int>(a1, a2); Add<double>(d1, d2); // 调用模板函数,因为参数是double类型,与非模板函数不匹配Add<double>(a1, d1); // 调用模板函数,模板函数会生成更匹配的,因为参数是double类型,与非模板函数不匹配 (a1会被强转成 double)return 0;
}

2,类模板
什么是类模板
类模板是对一批成员数据类型不同的类的抽象。只需为这一批类所组成的整个类家族创建一个类模板,并给出一套程序代码,就可以用来生成多种具体的类(这类可以看作是类模板的实例),从而大大提高编程的效率。
类模板的基本结构如下:
//template <typename 参数名, typename ...> // 可以有多个类型参数,使用逗号分隔
template < class T> // 或使用typename代替class
class 类名{
// 类的定义,可以使用类型T作为成员变量、函数参数或返回类型
};其中,template 关键字用于声明一个模板,<class 参数名, ...> 部分定义了模板参数,这些参数在模板内部可以用作类型。class关键字是用来指示随后的标识符是一个类型名称的,但也可以使用 typename 关键字代替 class。
栈模板类可以定义如下:
// 类模板
template<class T>
class Stack
{
public:Stack(int capacity = 4){cout << "Stack(int capacity = 4)" << endl;_a = new T[capacity];_top = 0;_capacity = capacity;}~Stack(){cout << "~Stack()" << endl;delete[] _a;_a = nullptr;_top = 0;_capacity = 0;}
private:T* _a;int _top;int _capacity;
};
在上面的例子中,定义了一个名为 Stack的类模板,它接受一个类型参数 T。这个 T 类型被用作栈中元素的类型。
类模板的实例化
int main()
{// 显示实例化Stack<int> st1; // 实例化一份存储int 类型的栈Stack<double> st2; // 实例化一份存储double 类型的栈return 0;
}
在
main函数中,分别实例化了两个Stack对象:一个用于存储整数(Stack<int>),另一个用于存储浮点数(Stack<double>)。这两个对象都使用了相同的Stack类模板,但是它们内部处理的数据类型是不同的。这就是类模板的强大之处:通过编写一次代码,就可-以创建出多种类型安全的栈类。注意:1. 对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;这种方法把模板形参设置为int是错误的,类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A<int> m。
2. 模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
二,模板的特化
1,类模板的特化
类模板特化是针对特定类型的模板参数提供定制的类模板实现。它允许在某些情况下,使用与通用模板不同的实现方式。类模板特化分为全特化和偏特化(局部特化)两种
全特化
对模板参数列表中的所有模板类型都进行具体化。例如,如果有一个模板类
Test<T1, T2>,我们可以为T1和T2都是int的情况提供一个全特化的版本
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
};// 全特化为 int和char
template<>
class Data<int, char>
{
public:Data() { cout << "Data<int, char>" << endl; }
private:int _d1;char _d2;
};// 全特化为 int int
template <>
class Data<int, int>
{
public:Data() { cout << "Data<int, int>" << endl; }
private:
};int main()
{Data<int, int> d1;Data<int, char> d2;return 0;
}
写一个类的全特化,就相当于写一个新的类一样,你可以自己定义任何东西,不管是函数、数据成员、静态数据成员等等;根据自己的需求
偏特化
偏特化就是如果这个模板有多个类型,那么只限定其中的一部分,即只对模板的部分类型进行明确指定
特化部分参数:将模板参数类表中的一部分参数特化
有一个 Data的类模板,它接受两个类型参数 T1和T2。T2为 int 的情况提供了偏特化:
//原模版
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
};// 特化部分参数
template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
};int main()
{Data<char, int> d1; // 走特化版本 Data<T1, int>Data<int, int> d2;Data<int, char> d3; // 走原模板,第二个参数为char与第二个模板参数int不匹配return 0;
}
当T2是int时,编译器将使用偏特化的Data类模板,而不是原始的模板。如果T2不是int,则编译器将使用原始的模板
对参数类型进行一定限制。比如:限制是指针或者引用等
//对参数类型限制为指针
template <class T1, class T2>
class Data<T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
};//对参数类型限制为引用
template <class T1, class T2>
class Data<T1&, T2&>
{
public:Data() { cout << "Data<T1&, T2&>" << endl; }
};//对参数类型T1限制为指针,T2限制为引用
template <class T1, class T2>
class Data<T1*, T2&>
{
public:Data() { cout << "Data<T1*, T2&>" << endl; }
};int main()
{Data<char*, int*> d4; // 走特化版本 Data<T1*, T2*>Data<int*, int*> d5;Data<int&, int*> d6; // 走原模板 Data<int&, int&> d6; // 走特化版本 Data<T1&, T2&>Data<int*, int&> d7; // 走特化版本 Data<T1*, T2&>return 0;
}
注意:偏特化有一些限制。不能为一个非类型模板参数提供偏特化,也不能为一个函数模板提供偏特化。同时,偏特化的结果仍然是一个模板,而不是一个具体的类
2,函数模板的特化——全特化
//函数模板
template<typename T1, typename T2>
void fun(T1 a, T2 b)
{cout << "函数模板" << endl;
}//全特化
template<>
void fun<int, char >(int a, char b)
{cout << "全特化" << endl;
}//函数不存在偏特化:下面的代码是错误的
/*
template<typename T2>
void fun<char, T2>(char a, T2 b)
{cout << "偏特化" << endl;
}
*/
注意:对于函数模板,只有全特化,不能偏特化
三,非类型模板参数
模板参数分为类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称(上面已经介绍过)
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
// 定义一个模板类型的静态数组
template<class T, size_t N>
class Array
{
public: // ... 其他成员函数 ...private:T data[N];
}; int main()
{ Array<int, 5> arr; // 创建一个大小为5的整型数组 // ...
}
注意:
1. 非类型模板参数必须是常量表达式,也就是说它们必须在编译时就能确定其值
2. 非类型模板参数的类型通常是整数类型(如
int、size_t等)、指针(如 指向函数的指针)或引用,但不能是类类型或其他复杂类型。3. 非类型模板参数在模板实例化时会被替换为具体的值,因此它们会影响生成的代码的类型和布局
相关文章:
【C++】模板及模板的特化
目录 一,模板 1,函数模板 什么是函数模板 函数模板原理 函数模板的实例化 推演(隐式)实例化 显示实例化 模板的参数的匹配原则 2,类模板 什么是类模板 类模板的实例化 二,模板的特化 1,类模板的特化 全特化…...
2001-2023年上市公司数字化转型测算数据(含原始数据+处理代码+计算结果)
2001-2023年上市公司数字化转型测算数据(含原始数据处理代码计算结果)(吴非) 1、时间:2001-2023年 2、来源:上市公司年报 3、指标:行业代码、行业名称、证券简称、是否发生ST或ST或PT、是否发生暂停上市…...
ICRA 2024:基于视觉触觉传感器的物体表⾯分类的Sim2Real双层适应⽅法
⼈们通常通过视觉来感知物体表⾯的性质,但有时需要通过触觉信息来补充或替代视觉信息。在机器⼈感知物体属性⽅⾯,基于视觉的触觉传感器是⽬前的最新技术,因为它们可以产⽣与表⾯接触的⾼分辨率 RGB 触觉图像。然⽽,这些图像需要⼤…...
代理模式(设计模式)
文章目录 静态代理动态代理代理模式的应用场景动态代理和静态代理的区别 代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。代理对象是目标对象的代表,其他需要与这个目标对象打交道的操作…...
C++函数参数传递
C 函数传参 在C中,函数传递参数的方式主要有三种: 按值传递(pass by value)按引用传递(pass by reference)按指针传递(pass by pointer)。 比较与总结 按值传递:适用…...
软考初级网络管理员_09_网络单选题
1.下列Internet应用中对实时性要求最高的是()。 电子邮件 Web浏览 FTP文件传输 IP电话 2.在Internet中的大多数服务(如WWW、FTP等)都采用()模型。 星型 主机/终端 客户机/服务器 网状 3.子网掩码的作用是()。 可以用来寻找网关 可以区分IP和MAC 可以识别子网 可以…...
曲线拟合 | 二次B样条拟合曲线
B 样条曲线拟合实例:能平滑化曲线 1. 实例1 为MASS包中mcycle数据集。它测试了一系列模拟的交通车事故中,头部的加速度,以此来评估头盔的性能。times为撞击时间(ms),accel为加速度(g)。首先导入数据&#…...
delphi FDMemTable1.SourceView遍历各行数据,取任意行数据无需Next移动指针了。TFDDatSView
for m : 0 to FDMemTable1.SourceView.Rows.Count - 1 do begin if FDMemTable_SP.SourceView.Rows.ItemsI[m].GetData(0) varNull then Continue; end; 9行7列的值。 FDMemTable1.Data.DataView.Rows.ItemsI[9].ValueI[7]; FDMemTable1.Table.Ro…...
为什么选择 ABBYY FineReader PDF ?
帮助用户们对PDF文件进行快速的编辑处理,同时也可以快速识别PDF文件里的文字内容,并且可以让用户们进行文本编辑,所以可以有效提升办公效率。 ABBYY-ABBYY Finereader 15 Win-安装包:https://souurl.cn/OY2L3m 高级转换功能 ABBY…...
php遇到的问题
1、 underfined at line 3 in xxx.php , 错误提示,注释这行代码 // error_reporting(DEBUG ? E_ALL : 0); 目录:config/config.php...
零基础入门学用Arduino 第二部分(二)
重要的内容写在前面: 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。个人把这个教程学完之后,整体感觉是很好的,如果有条件的可以先学习一些相关课程,学起来会更加轻松,相关课程有数字电路…...
旅游行业电商平台:数字化转型的引擎与未来发展趋势
引言 旅游行业数字化转型的背景和重要性 随着信息技术的飞速发展,数字化转型成为各行业发展的必然趋势。旅游行业,作为一个高度依赖信息和服务的领域,数字化转型尤为重要。通过数字化手段,旅游行业能够实现资源的高效配置、服务的…...
Ubuntu 22.04安装 docker
安装过程和指令 # 1.升级 apt sudo apt update # 2.安装docker sudo apt install docker.io docker-compose # 3.将当前用户加入 docker组 sudo usermod -aG docker ${USER} # 4. 重启 # 5. 查看镜像 docker ps -a 或者 docker images # 6. 下载镜像 docker pull hello-world …...
【Gitlab】访问默认PostgreSQL数据库
本地访问PostgreSQL gitlab有可以直接访问内部PostgreSQL的命令 sudo gitlab-rails dbconsole # 或者 sudo gitlab-psql -d gitlabhq_production效果截图 常用SQL # 查看用户状态 select id,name,email,state,last_sign_in_at,updated_at,last_credential_check_at,last_act…...
乐鑫ESP32-C3芯片应用,启明云端WT32C3-S5模组:简化产品硬件设计
在数字化浪潮的推动下,物联网(IoT)正迅速成为连接现实世界与数字世界的桥梁。芯片作为智能设备的心脏,其重要性不言而喻。 乐鑫推出的ESP32-C3芯片以其卓越的性能和丰富的功能,为智能物联网领域带来了新的活力,我将带您深入了解这…...
算法刷题【二分法】
题目: 注意题目中说明了数据时非递减的,那么这样就存在二分性,能够实现logn的复杂度。二分法每次只能取寻找特定的某一个值,所以我们要分别求左端点和有端点。 分析第一组用例得到结果如下: 成功找到左端点8 由此可知࿰…...
.NET MAUI Sqlite程序应用-数据库配置(一)
项目名称:Ownership(权籍信息采集) 一、安装 NuGet 包 安装 sqlite-net-pcl 安装 SQLitePCLRawEx.bundle_green 二、创建多个表及相关字段 Models\OwnershipItem.cs using SQLite;namespace Ownership.Models {public class fa_rural_base//基础数据…...
基于WPF技术的换热站智能监控系统09--封装水泵对象
1、添加用户控件 2、编写水泵UI 控件中用到了Viewbox控件,Viewbox控件是WPF中一个简单的缩放工具,它可以帮助你放大或缩小单个元素,同时保持其宽高比。通过样式和属性设置,你可以创建出既美观又功能丰富的用户界面。在实际开发中…...
GLM+vLLM 部署调用
GLMvLLM 部署调用 vLLM 简介 vLLM 框架是一个高效的大型语言模型(LLM)推理和部署服务系统,具备以下特性: 高效的内存管理:通过 PagedAttention 算法,vLLM 实现了对 KV 缓存的高效管理,减少了…...
leetcode 122 买卖股票的最佳时机||(动态规划解法)
题目分析 题目描述的已经十分清楚了,不做过多阐述 算法原理 状态表示 我们假设第i天的最大利润是dp[i] 我们来画一下状态机 有两个状态,买入后和卖出后,我们就可以使用两个dp表来解决问题 f[i]表示当天买入后的最大利润 g[i]表示当天卖出…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...
Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...
高效的后台管理系统——可进行二次开发
随着互联网技术的迅猛发展,企业的数字化管理变得愈加重要。后台管理系统作为数据存储与业务管理的核心,成为了现代企业不可或缺的一部分。今天我们要介绍的是一款名为 若依后台管理框架 的系统,它不仅支持跨平台应用,还能提供丰富…...
虚幻基础:角色旋转
能帮到你的话,就给个赞吧 😘 文章目录 移动组件使用控制器所需旋转:组件 使用 控制器旋转将旋转朝向运动:组件 使用 移动方向旋转 控制器旋转和移动旋转 缺点移动旋转:必须移动才能旋转,不移动不旋转控制器…...
