【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]表示当天卖出…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
机器学习的数学基础:线性模型
线性模型 线性模型的基本形式为: f ( x ) ω T x b f\left(\boldsymbol{x}\right)\boldsymbol{\omega}^\text{T}\boldsymbol{x}b f(x)ωTxb 回归问题 利用最小二乘法,得到 ω \boldsymbol{\omega} ω和 b b b的参数估计$ \boldsymbol{\hat{\omega}}…...
基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究
摘要:在消费市场竞争日益激烈的当下,传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序,探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式,分析沉浸式体验的优势与价值…...
数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...
python打卡第47天
昨天代码中注意力热图的部分顺移至今天 知识点回顾: 热力图 作业:对比不同卷积层热图可视化的结果 def visualize_attention_map(model, test_loader, device, class_names, num_samples3):"""可视化模型的注意力热力图,展示模…...
