C++模板进阶:非类型模板参数、模板特化与分离编译一次搞懂
C模板进阶非类型模板参数、模板特化与分离编译一次搞懂文章目录C模板进阶非类型模板参数、模板特化与分离编译一次搞懂一、非类型模板参数1 类型模板参数2 非类型模板参数3 非类型模板参数的特点必须是编译期能够确定的值浮点数、类对象以及字符串不能作为非类型模板参数二、模板特化1 为什么需要模板特化三、函数模板特化1 函数模板特化的基本形式第一第二第三第四2 函数模板特化之后发生了什么3 为什么函数模板一般不推荐特化四、类模板特化五、类模板全特化1 什么是全特化2 全特化的调用六、类模板偏特化1 什么是偏特化七、偏特化形式一部分参数特化八、偏特化形式二进一步限制参数类型1 指针类型偏特化九、引用类型偏特化十、类模板特化的匹配Datadouble, intDataint, doubleDataint*, int*Dataint, int十一、模板特化在排序中的应用排序普通对象十二、排序指针时的问题十三、通过类模板特化解决指针排序问题十四、模板分离编译什么是分离编译十五、模板为什么容易出现分离编译问题十六、为什么模板分离编译容易出问题十七、解决模板分离编译的方法方法一声明和定义放在同一个文件方法二显式实例化十八、模板的优点1 代码复用2 灵活性高3 STL 的基础十九、模板的缺点1 可能造成代码膨胀2 编译时间可能变长3 模板错误信息复杂模板特化与分离编译的核心关系一、非类型模板参数模板参数主要分为两类类型模板参数非类型模板参数1 类型模板参数类型模板参数就是我们平时最常见的写法templateclassTclassStack{// ...};这里的T就是类型模板参数使用时可以传入不同的类型Stackints1;Stackdoubles2;Stackchars3;T最终会被替换成对应的类型2 非类型模板参数非类型模板参数不是传入一个类型而是传入一个编译期能够确定的常量例如templateclassT,size_t N10classArray{public:Toperator[](size_t index){return_array[index];}constToperator[](size_t index)const{return_array[index];}size_tsize()const{return_size;}boolempty()const{return_size0;}private:T _array[N];size_t _size;};这里classT是类型模板参数而size_t N是非类型模板参数因此Arrayint,10a1;Arraydouble,20a2;其中int和double是类型参数10和20是非类型参数3 非类型模板参数的特点非类型模板参数有两个非常重要的特点必须是编译期能够确定的值例如Arrayint,10a;这里的10在编译阶段就已经确定不能使用运行过程中才能确定的普通变量作为模板参数浮点数、类对象以及字符串不能作为非类型模板参数例如templatedoubleNclassA{};这种形式是不允许的因此理解非类型模板参数时可以简单记住类型模板参数传类型非类型模板参数传编译期常量二、模板特化1 为什么需要模板特化模板最大的作用就是让代码能够适用于多种类型例如templateclassTboolLess(T left,T right){returnleftright;}对于普通类型来说没有问题Less(1,2);Less(1.0,2.0);因为整数和浮点数都可以直接使用进行比较但是遇到指针就可能出现问题例如Dated1(2022,7,7);Dated2(2022,7,8);Date*p1d1;Date*p2d2;Less(p1,p2);此时模板推导出TDate*于是模板实际上变成类似boolLess(Date*left,Date*right){returnleftright;}这里比较的不是两个日期对象而是两个指针保存的地址也就是说leftright比较的是地址大小而我们真正希望比较的是*left*right也就是两个指针所指向的日期对象这时候就需要模板特化三、函数模板特化1 函数模板特化的基本形式首先必须已经存在一个基础函数模板templateclassTboolLess(T left,T right){returnleftright;}然后针对特殊类型进行特化templateboolLessDate*(Date*left,Date*right){return*left*right;}这里需要注意几个地方第一前面必须有基础模板templateclassTboolLess(T left,T right)第二特化时写template尖括号里面为空第三函数名后面指定特化的类型LessDate*第四函数参数列表需要与基础模板对应的参数类型保持一致Date*left,Date*right2 函数模板特化之后发生了什么普通类型仍然使用普通模板Less(1,2);相当于使用templateclassTboolLess(T left,T right){returnleftright;}而Less(p1,p2);由于参数类型是Date*会匹配我们专门针对Date*写的特化版本templateboolLessDate*(Date*left,Date*right){return*left*right;}因此最终比较的是*left*right而不是leftright3 为什么函数模板一般不推荐特化函数模板特化虽然可以解决特殊类型的问题但是实际使用中通常还有一种更简单的方式直接重新定义一个普通函数例如boolLess(Date*left,Date*right){return*left*right;}这样代码更加直观因此函数模板如果遇到不能处理或者处理有误的类型为了实现简单通常直接给出一个普通函数函数模板特化涉及的参数类型越复杂代码越容易变得难以理解所以实际开发中函数模板通常不建议使用特化四、类模板特化类模板同样可以进行特化类模板特化主要分为全特化偏特化五、类模板全特化1 什么是全特化全特化就是把模板参数列表中的所有参数都确定下来例如基础模板templateclassT1,classT2classData{public:Data(){coutDataT1, T2endl;}private:T1 _d1;T2 _d2;};这个模板有两个类型参数T1 T2现在针对intchar进行完全特殊化templateclassDataint,char{public:Data(){coutDataint, charendl;}private:int_d1;char_d2;};这就是全特化因为T1 T2全部被确定了2 全特化的调用Dataint,intd1;Dataint,chard2;对于Dataint,int没有对应的全特化版本因此使用基础模板对于Dataint,char存在对应的全特化版本因此使用Dataint,char这个特化版本可以理解成基础模板负责通用情况全特化负责某一种完全确定的特殊情况六、类模板偏特化1 什么是偏特化偏特化并不只是简单地理解成只特化一部分参数更准确的理解是针对模板参数进一步进行条件限制从而设计出一个更加特殊的版本例如基础模板templateclassT1,classT2classData{public:Data(){coutDataT1, T2endl;}private:T1 _d1;T2 _d2;};七、偏特化形式一部分参数特化例如只把第二个参数限制为inttemplateclassT1classDataT1,int{public:Data(){coutDataT1, intendl;}private:T1 _d1;int_d2;};这里T1仍然是未知类型但是T2已经被限制为int所以它属于偏特化例如Datadouble,intd1;符合DataT1,int因此使用偏特化版本而Dataint,doubled2;不符合这个条件因此使用基础模板八、偏特化形式二进一步限制参数类型偏特化还可以进一步限制模板参数必须满足某种形式例如把两个参数都限制成指针templatetypenameT1,typenameT2classDataT1*,T2*{public:Data(){coutDataT1*, T2*endl;}private:T1 _d1;T2 _d2;};此时Dataint*,double*d;就符合这个偏特化版本因为T1intT2double最终形成Dataint*,double*1 指针类型偏特化templatetypenameT1,typenameT2classDataT1*,T2*{};它限制的是第一个参数必须是指针 第二个参数必须是指针例如Dataint*,double*d1;可以匹配DataT1*,T2*但是Dataint,doubled2;不能匹配九、引用类型偏特化同样可以针对引用类型进行偏特化templatetypenameT1,typenameT2classDataT1,T2{public:Data(constT1d1,constT2d2):_d1(d1),_d2(d2){coutDataT1, T2endl;}private:constT1_d1;constT2_d2;};这个版本要求两个模板参数都是引用类型例如Dataint,intd4(1,2);就可以匹配这个偏特化版本十、类模板特化的匹配通过下面几个例子可以理解不同版本之间的匹配关系Datadouble,intd1;Dataint,doubled2;Dataint*,int*d3;Dataint,intd4(1,2);Datadouble, int匹配DataT1,int所以使用第二个参数为int的偏特化版本Dataint, double不满足前面的特殊条件所以使用基础模板Dataint*, int*满足DataT1*,T2*所以使用指针偏特化版本Dataint, int满足DataT1,T2所以使用引用偏特化版本因此理解类模板偏特化的关键就是看当前模板参数是否满足某个特化版本的条件十一、模板特化在排序中的应用模板特化一个非常典型的应用场景就是排序例如定义一个比较器templateclassTstructLess{booloperator()(constTx,constTy)const{returnxy;}};这个类模板本质上就是一个函数对象使用方式LessDate()(d1,d2);内部最终执行d1d2排序普通对象例如vectorDatev1;v1.push_back(d1);v1.push_back(d2);v1.push_back(d3);sort(v1.begin(),v1.end(),LessDate());这里v1存放的是Date对象因此LessDate中的returnxy;比较的是两个Date对象如果Date已经实现了operator就可以正常按照日期进行排序十二、排序指针时的问题现在如果容器中存放的是指针vectorDate*v2;v2.push_back(d1);v2.push_back(d2);v2.push_back(d3);然后直接sort(v2.begin(),v2.end(),LessDate*());此时模板中的T是Date*因此比较器相当于structLessDate*{booloperator()(Date*x,Date*y)const{returnxy;}};这里比较的是xy也就是两个指针的地址而不是指针指向的日期对象因此排序结果可能不是按照日期升序排列十三、通过类模板特化解决指针排序问题针对Date*进行特化templatestructLessDate*{booloperator()(Date*x,Date*y)const{return*x*y;}};注意这里发生了一个非常重要的变化普通版本returnxy;比较的是指针特化版本return*x*y;比较的是指针指向的对象这样sort(v2.begin(),v2.end(),LessDate*());在排序过程中就会按照Date对象本身进行比较这就是类模板特化非常典型的应用十四、模板分离编译什么是分离编译一个完整的 C 项目通常由多个源文件组成例如a.h a.cpp main.cpp每个源文件先单独编译生成目标文件最后再把多个目标文件链接起来形成可执行程序这种方式称为分离编译模式十五、模板为什么容易出现分离编译问题普通函数经常采用// a.hintAdd(intleft,intright);然后// a.cppintAdd(intleft,intright){returnleftright;}最后// main.cpp#includea.hintmain(){Add(1,2);}这是正常的但是模板不一样例如// a.htemplateclassTTAdd(constTleft,constTright);然后把定义放到// a.cpptemplateclassTTAdd(constTleft,constTright){returnleftright;}最后// main.cpp#includea.hintmain(){Add(1,2);Add(1.0,2.0);}这种写法可能导致链接阶段出现问题十六、为什么模板分离编译容易出问题模板不是普通函数编译器在使用模板时需要根据实际传入的类型生成对应的实例例如Add(1,2);需要生成类似Addint而Add(1.0,2.0);需要生成类似Adddouble如果当前编译单元只有模板声明却看不到模板的完整定义编译器就无法正常完成对应的实例化过程所以模板通常不适合采用传统的.h 放声明 .cpp 放定义这种方式进行分离十七、解决模板分离编译的方法方法一声明和定义放在同一个文件这是最常用的方法例如直接放到xxx.hpp中templateclassTTAdd(constTleft,constTright){returnleftright;}然后使用时#includexxx.hpp这样使用模板的时候编译器能够看到完整的模板定义因此可以根据实际类型完成实例化这种方法也比较推荐方法二显式实例化另一种方式是在模板定义的位置显式实例化例如可以针对确定的类型进行实例化但是这种方式需要提前知道需要哪些类型模板本身的灵活性会受到影响因此实际使用中一般不推荐这种方式十八、模板的优点模板最大的价值就是代码复用例如一个模板函数可以适用于多种类型templateclassTTAdd(constTleft,constTright){returnleftright;}不需要分别编写intAdd(int,int);doubleAdd(double,double);floatAdd(float,float);而是通过一个模板统一描述因此模板具有以下特点1 代码复用减少重复代码2 灵活性高同一套代码可以适配不同类型3 STL 的基础C 标准模板库 STL 大量使用模板实现例如vector list map set等容器都建立在模板机制之上十九、模板的缺点模板虽然强大但也存在明显缺点1 可能造成代码膨胀不同类型使用同一个模板时编译器可能生成不同的实例例如AddintAdddoubleAddfloat这些都可能产生对应的代码当模板使用非常广泛时可能造成代码体积增加2 编译时间可能变长模板需要在编译过程中进行实例化项目规模较大时大量模板代码可能增加编译时间3 模板错误信息复杂模板出现编译错误时编译器可能输出非常长的错误信息尤其是 STL 和多层模板嵌套时错误信息往往不容易直接定位因此使用模板时需要重点关注模板参数是否正确 模板实例化是否符合预期 是否存在对应的特化版本模板特化与分离编译的核心关系整个模板进阶中最重要的几个关系可以归纳为非类型模板参数 ↓ 模板参数不仅可以是类型也可以是编译期常量 模板特化 ↓ 通用模板无法正确处理某些特殊类型 ↓ 针对特殊情况提供特殊实现 函数模板特化 ↓ 可以针对特定类型提供特殊函数实现 ↓ 实际开发中通常直接写普通函数更简单 类模板特化 ↓ 全特化 偏特化 ↓ 可以针对特定类型或特定类型组合提供特殊实现 模板分离编译 ↓ 模板实例化需要看到完整定义 ↓ 通常将模板声明和定义放在同一个 .hpp 文件中理解这些内容后模板进阶的核心逻辑就是模板负责通用代码特化负责特殊情况非类型参数负责把编译期常量传给模板而模板通常需要让编译器在使用时能够看到完整定义

相关新闻

XinText 更新|一套编辑器,Windows/macOS/Linux都可用!

XinText 更新|一套编辑器,Windows/macOS/Linux都可用!

兄弟们,时代变了! 以前大家都说我的编辑器只能 Windows 用,太可惜了! 每次有人问我:“大佬什么时候出 Mac / Linux 版本?” 这次!直接一次性拉满!!🔥 Xin…

2026/10/9 10:19:41 阅读更多 →
叮当小宝CS实测:拼多多回复率怎么提?5分钟回复线与兜底话术

叮当小宝CS实测:拼多多回复率怎么提?5分钟回复线与兜底话术

一家做家居收纳的拼多多店铺找到我们的时候,回复率刚好卡在平台考核线附近来回跳。店主的感受很典型:明明大部分咨询都回了,回复率还是三天两头往下掉,掉了就影响活动报名,报不上活动流量又掉,最后只能靠人…

2026/10/9 10:19:41 阅读更多 →
Stitch+Antigravity 打造 AI 设计工作流:用 TaoToken 统一 Key 打通 MCP 与 Next.js

Stitch+Antigravity 打造 AI 设计工作流:用 TaoToken 统一 Key 打通 MCP 与 Next.js

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:19:41 阅读更多 →

最新新闻

基于Python+MILP的风光储联合调度:电池与废弃矿井抽蓄互补优化

基于Python+MILP的风光储联合调度:电池与废弃矿井抽蓄互补优化

这两年搞新能源消纳的调度研究,有个词绕不开:互补。风电场最常见的情况是深夜大风、负荷却躺在地板上,光伏正好相反,正午出力冲顶、电网一时间吃不下。单靠任何一种电源都没法把这条曲线磨平,于是风电、光伏和储能组成…

2026/10/9 10:58:44 阅读更多 →
深度聚类开源代码库实战指南:从DEC到对比学习的工程落地

深度聚类开源代码库实战指南:从DEC到对比学习的工程落地

在无标注数据这块,很多人习惯性地打开sklearn直接跑一个KMeans,但凡是真正做过几年聚类项目的人都有体会:高维图像、文本向量、用户行为序列这类数据,传统聚类几乎每次都会翻车。原因不是聚类算法本身不行,而是输入特征…

2026/10/9 10:58:44 阅读更多 →
微网虚拟电厂多场景随机规划与CVaR风险优化调度策略

微网虚拟电厂多场景随机规划与CVaR风险优化调度策略

开场:为什么风险量化成了微网调度的硬需求做调度的人,不管是在传统电力系统还是园区级微网,现在绕不开一个词:风险。风光出力天生不稳定,负荷预测也不可能百分之百准,以前我们做确定性调度习惯了&#xff0…

2026/10/9 10:58:44 阅读更多 →
数理统计大作业实战:从假设检验到Python实现的全流程指南

数理统计大作业实战:从假设检验到Python实现的全流程指南

简介:这是一份面向数理统计课程学习者与机器学习初学者的完整大作业报告,围绕鸢尾花数据集展开多方法分析。报告以花萼与花瓣的四个属性为输入,使用马氏距离度量样本相似性,通过混合高斯模型实现聚类,借助主成分分析与…

2026/10/9 10:58:44 阅读更多 →
硅基流动+Chatbox:零成本长期运行DeepSeek-R1的本地AI工作流

硅基流动+Chatbox:零成本长期运行DeepSeek-R1的本地AI工作流

简介:本资源是一份面向初级开发者与个人AI实践者的低成本大模型应用搭建指南,聚焦如何利用硅基流动平台的DeepSeek API与开源跨平台AI助手Chatbox,构建稳定、免费且响应流畅的本地化AI应用。内容覆盖硅基流动高额度免费Token(新用…

2026/10/9 10:58:44 阅读更多 →
基于JWT/JWE的跨系统安全数据透传方案详解

基于JWT/JWE的跨系统安全数据透传方案详解

先说结论:这套“基于JWT/JWE的跨系统安全数据透传方案”,解决的是两个不同域、不同技术栈、甚至不同运维体系的服务之间,如何安全地把一段结构化数据从A端交付到B端——既保证数据在“路上”不被看、不被改,又保证接收方能够验证数…

2026/10/9 10:57:43 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →