【c++学习笔记】【基础】9.模板初阶:从函数模板到类模板
前言在 C 中我们经常会遇到这样的代码voidSwap(intleft,intright);voidSwap(doubleleft,doubleright);voidSwap(charleft,charright);这些函数处理的数据类型不同但完成的事情完全相同交换两个变量的值。函数重载虽然能够解决问题但如果以后还需要支持float、long等更多类型就要继续编写新的重载。代码逻辑没有变化却因为类型不同被重复实现了很多次。于是就产生了一个很自然的问题能不能只写一份代码把具体的数据类型也抽象出来让同一套逻辑适用于不同类型这就是 C模板Template要解决的问题。模板是 C 实现泛型编程的重要基础。它允许我们先编写一份与具体类型无关的通用代码再由编译器根据实际使用的类型生成对应的具体代码。本文主要围绕模板最基础的两个部分展开函数模板让同一套函数逻辑适用于不同类型类模板让同一套类结构适用于不同类型。在这个过程中还需要理解模板参数、模板实例化、类型推导以及函数模板的匹配规则。整篇文章可以沿着这样一条主线来理解重复代码 ↓ 把类型抽象出来 ↓ 泛型编程 ↓ 模板 ├── 函数模板 └── 类模板 ↓ 模板实例化 ↓ 得到具体代码一、为什么需要模板模板出现的根本原因是为了减少一类特殊的重复代码代码逻辑完全相同只有处理的数据类型不同。还是以交换函数为例voidSwap(intleft,intright){inttempleft;leftright;righttemp;}voidSwap(doubleleft,doubleright){doubletempleft;leftright;righttemp;}仔细观察会发现两份代码真正发生变化的只有类型int → double交换的逻辑并没有发生任何变化。如果能够把这里的类型也抽象成一个参数那么就不需要针对每一种类型重新写一遍函数。于是可以写成templatetypenameTvoidSwap(Tleft,Tright){T templeft;leftright;righttemp;}这里的T不再代表某一种确定的数据类型而是一个模板参数。调用inta10;intb20;Swap(a,b);此时T对应int。如果调用doublex1.1;doubley2.2;Swap(x,y);此时T对应double。因此只需要维护一份交换逻辑就能够适用于不同的数据类型。这种编写与具体类型无关的通用代码的方式就是泛型编程。而模板就是 C 实现泛型编程的重要工具。本节小结函数重载可以让同名函数处理不同类型但当多个函数只有类型不同、逻辑完全相同时会产生大量重复代码。模板进一步把类型本身也参数化不同类型 相同逻辑 ↓ 模板 ↓ 一份通用代码二、函数模板把类型也变成参数函数模板解决的是函数逻辑相同但参与运算的数据类型不同。普通函数中的参数可以让传入的“数据”发生变化而模板进一步把“类型”也抽象成参数。1. 函数模板的基本格式函数模板的基本形式为templatetypenameT返回值类型 函数名(参数列表){// 函数实现}例如templatetypenameTvoidSwap(Tleft,Tright){T templeft;leftright;righttemp;}其中templatetypenameT表示声明一个模板参数T。接下来在函数中出现的T就代表这个暂时还没有确定的类型。模板参数也可以不止一个templatetypenameT1,typenameT2T1Add(T1 left,T2 right){returnleftright;}此时T1 和 T2可以分别表示不同的类型。2.typename和class声明类型模板参数时可以写templatetypenameT也可以写templateclassT例如templateclassTvoidSwap(Tleft,Tright){T templeft;leftright;righttemp;}在这里typename和class的作用相同都表示T是一个类型模板参数。需要注意的是templatestructT不能用来替代这里的typename或class。本节小结函数模板可以简单理解为先用模板参数代替具体类型描述一套通用的函数逻辑。例如templatetypenameTvoidSwap(Tx,Ty);此时T还没有确定它究竟是int、double还是其他类型要等真正使用模板时才能知道。三、函数模板是如何变成具体函数的定义templatetypenameTTAdd(constTleft,constTright){returnleftright;}并不意味着程序中已经存在一个确定类型的Add函数。因为此时T ?还是未知的。真正调用inta10;intb20;Add(a,b);编译器才可以根据实参确定T int然后根据函数模板生成对应类型的函数。从理解原理的角度可以把生成的函数看成intAdd(constintleft,constintright){returnleftright;}如果又调用doublex1.1;doubley2.2;Add(x,y);则T double编译器会生成对应double类型的版本。整个过程可以理解为AddT │ ┌────────────┴────────────┐ ▼ ▼ Add(1, 2) Add(1.1, 2.2) │ │ Tint Tdouble │ │ ▼ ▼ Addint Adddouble因此需要建立一个很重要的认识函数模板本身不是某一个具体类型的函数而是编译器生成具体函数的一份模板。根据模板生成对应具体函数的过程就是模板实例化。本节小结模板帮我们减少的是“重复编写代码”的工作而不是让所有类型真的共用同一个具体函数。可以简单理解为函数模板 ↓ 确定模板参数 ↓ 模板实例化 ↓ 生成具体函数这也是理解后面模板参数推导的基础。四、模板实例化模板参数是怎么确定的模板要生成具体函数就必须先确定模板参数。例如templateclassTTAdd(constTleft,constTright){returnleftright;}这里的T最终必须变成一个具体类型。根据模板参数的确定方式可以分为隐式实例化和显式实例化。1. 隐式实例化隐式实例化就是让编译器根据传入的实参自己推导模板参数。例如inta110;inta220;Add(a1,a2);编译器根据两个参数得到a1 → int a2 → int 因此 T int于是实例化出int类型的函数。同理doubled11.1;doubled22.2;Add(d1,d2);可以得到T double这就是模板参数的自动推导。2. 为什么Add(int, double)会出现问题继续使用templateclassTTAdd(constTleft,constTright){returnleftright;}现在调用inta10;doubled20.0;Add(a,d);编译器推导T时会得到两个结果根据 a T int 根据 d T double但函数模板中只有一个TAdd(constTleft,constTright)同一个模板参数无法在这次推导中同时被确定成int和double因此模板参数推导失败。这里最容易产生一个误区int和double明明可以做加法为什么模板不能调用问题其实不在运算。真正的问题发生得更早编译器首先要确定 T 是什么但两个实参给出了不同的推导结果。因此模板参数推导阶段首先需要解决的是类型是否能够推导一致。3. 显式指定模板参数既然问题出在编译器不知道T应该选择哪个类型那么也可以直接告诉它Addint(a,d);这里int明确指定T int于是编译器不再需要根据两个实参推导T。如果实际参数和指定的模板参数类型不同只要能够完成相应的类型转换就可以继续调用。因此可以把两种实例化方式简单区分成Add(a, b) ↓ 编译器根据实参推导 T ↓ 隐式实例化 Addint(a, b) ↓ 程序员明确指定 T ↓ 显式实例化本节小结模板实例化解决的是如何从通用模板得到具体函数其中隐式实例化 → 编译器根据实参推导模板参数 显式实例化 → 程序员主动指定模板参数特别要注意模板参数推导失败并不一定意味着某个运算本身不能进行也可能只是编译器无法得到唯一一致的模板参数。五、函数模板的匹配到底调用哪一个有了函数模板以后同一个函数名下面可能同时存在普通函数和函数模板。例如intAdd(intleft,intright){returnleftright;}templateclassTTAdd(T left,T right){returnleftright;}这时调用Add(1,2);就产生了一个问题普通函数和函数模板都能处理这两个参数到底应该选择谁1. 匹配程度相同时优先普通函数对于Add(1,2);普通函数intAdd(int,int);可以直接匹配。函数模板同样可以实例化出Addint(int,int);两者都能够很好地匹配。这种情况下编译器会优先使用已经存在的普通函数。如果明确写Addint(1,2);则表示主动使用函数模板生成的版本。2. 模板也可能提供更好的匹配因此不能简单把规则记成“只要普通函数存在就一定调用普通函数。”真正需要考虑的是匹配程度。如果普通函数需要进行类型转换而函数模板能够根据实参生成一个更加合适的版本那么模板也可能被选择。所以更准确的理解是普通函数和函数模板匹配程度相同时优先选择普通函数如果函数模板能够产生更加匹配的版本则选择更合适的版本。本节小结函数模板的匹配规则不需要死记很多情况当前阶段先抓住一个核心编译器会寻找更加合适的匹配。其中普通函数和函数模板匹配程度相同时优先普通函数而不是“任何情况下普通函数都优先”。六、类模板把泛型从函数扩展到类前面解决的都是函数中的重复问题相同函数逻辑 不同数据类型 ↓ 函数模板但相同的问题也会出现在类中。例如实现一个栈如果只能保存intclassStack{private:int*_array;size_t _capacity;size_t _size;};以后又希望保存double如果重新实现一个DoubleStack那么栈的扩容、入栈等逻辑都需要再写一遍。实际上栈的结构和操作逻辑没有变化变化的仍然只是栈中保存的数据类型。因此可以把同样的泛型思想扩展到类相同类结构 不同数据类型 ↓ 类模板1. 类模板的基本格式类模板的基本形式为templateclassT1,classT2,...,classTnclass类模板名{// 成员};例如templatetypenameTclassStack{public:Stack(size_t capacity4){_arraynewT[capacity];_capacitycapacity;_size0;}voidPush(constTdata);private:T*_array;size_t _capacity;size_t _size;};原来固定的int*_array;变成T*_array;也就是说Stack不再提前规定自己只能保存哪一种类型。2. 类模板的成员函数如果成员函数直接定义在类中正常使用模板参数即可。如果在类外定义则需要重新声明模板参数templateclassTvoidStackT::Push(constTdata){_array[_size]data;_size;}这里需要注意StackT::Push而不是Stack::Push因为当前描述的是StackT这个类模板对应的成员函数。3. 类模板的实例化函数模板在调用时可以通过函数实参推导模板参数。类模板则需要在使用时明确给出具体类型Stackintst1;Stackdoublest2;这里Stackint表示使用int实例化Stack而Stackdouble表示使用double实例化Stack。需要特别区分Stack ↓ 类模板 Stackint ↓ 实例化后的具体类型因此Stackintst1;Stackdoublest2;得到的是两个不同的具体类型。整个过程可以理解为StackT │ ┌─────────┴─────────┐ ▼ ▼ Stackint Stackdouble │ │ 保存 int 保存 double本节小结类模板和函数模板的核心思想其实完全一致函数模板 → 把函数中变化的类型抽象出来 类模板 → 把类中变化的类型抽象出来而且需要特别记住Stack是类模板Stackint才是模板实例化后得到的具体类型。七、模板的声明和定义普通函数或者普通类通常可以把声明和定义分开.h → 声明 .cpp → 定义模板在使用时涉及根据具体类型进行实例化因此模板的组织方式和普通函数、普通类存在一定区别。例如templatetypenameTclassStack{public:voidPush(constTdata);};成员函数在类外定义时templatetypenameTvoidStackT::Push(constTdata){// ...}在实际使用模板时通常不建议简单按照普通代码的方式把模板声明放在.h中再把模板定义单独放进.cpp中否则可能出现链接错误。当前阶段先记住这个使用特点即可模板的定义通常需要和模板声明放在一起使模板在实例化时能够看到完整定义。模板为什么会产生这种现象与模板实例化以及 C 的编译、链接过程有关可以在后续继续深入理解。本节小结模板与普通函数、普通类在代码组织上有所不同。当前阶段最重要的是知道普通代码 → 经常声明、定义分离 模板代码 → 通常让模板定义保持可见不要简单照搬普通类.h .cpp的组织方式。八、总结回过头来看模板要解决的核心问题其实非常简单当代码逻辑不变只有数据类型发生变化时不应该为每一种类型重复编写一份代码。C 通过模板把“类型”也抽象成参数从而实现泛型编程。函数模板解决的是函数逻辑的复用函数模板 ↓ 模板参数推导 / 指定 ↓ 模板实例化 ↓ 得到具体函数其中模板参数可以通过两种方式确定隐式实例化 → 编译器根据实参推导 显式实例化 → 程序员主动指定如果普通函数和函数模板同时存在则需要比较匹配程度多个候选函数 ↓ 比较匹配程度 ↓ 选择更加合适的版本普通函数和函数模板匹配程度相同时优先使用普通函数。类模板则把相同的思想从函数扩展到了类类模板 ↓ 指定具体类型 ↓ 模板实例化 ↓ 得到具体类型例如Stack → 类模板 Stackint → 具体类型 Stackdouble → 另一个具体类型因此整个模板初阶的知识可以压缩成下面这张图泛型编程 │ 编写与具体类型无关的代码 │ ▼ 模板 │ ┌────────────┴────────────┐ ▼ ▼ 函数模板 类模板 │ │ 确定模板参数 指定类型 │ │ ┌───────┴───────┐ │ ▼ ▼ │ 隐式推导 显式指定 │ │ │ │ └───────┬───────┘ │ ▼ ▼ 模板实例化 模板实例化 │ │ ▼ ▼ 具体函数 具体类型如果以后回来复习这一部分不需要先去背templatetypename T的语法。先回忆一句话模板的本质是先编写一份与具体类型无关的通用代码再根据实际使用的类型实例化出具体代码。围绕这句话再向下回忆为什么需要模板 ↓ 减少只有类型不同的重复代码 函数模板怎么使用 ↓ 模板参数 模板实例化 T 怎么确定 ↓ 隐式推导 / 显式指定 多个函数都能调用怎么办 ↓ 比较匹配程度 类怎么实现泛型 ↓ 类模板 Stack 和 Stackint 有什么区别 ↓ 一个是类模板一个是具体类型把这条主线建立起来函数模板、模板实例化、参数匹配和类模板就不再是几个零散的语法点而是一套完整的泛型编程逻辑。

相关新闻

TqSdk 异步任务怎么写?并发订阅与可维护结构

TqSdk 异步任务怎么写?并发订阅与可维护结构

TqSdk 的异步任务适合在同一个线程、同一个 API 连接中组织多个合约或多个独立监听职责。api.create_task 用来注册协程,api.register_update_notify 为任务提供更新通知,主程序仍通过 wait_update 推进内核。它能减少复制多份事件循环的代码&#xff0c…

2026/8/13 8:08:38 阅读更多 →
Spring AI Alibaba实战:构建Human-in-the-Loop智能客服系统

Spring AI Alibaba实战:构建Human-in-the-Loop智能客服系统

1. 项目缘起:当AI决策需要一双“人眼”时最近在折腾一个基于Spring AI Alibaba的智能客服项目,遇到了一个挺典型的问题。我们让AI模型去处理用户的工单分类和初步回复,效果在大部分情况下都还不错。但有一次,一个用户输入了“我的…

2026/8/13 8:08:38 阅读更多 →
从SUBTERRA音乐项目到工业音色工程化:声音设计与自动化生成实践

从SUBTERRA音乐项目到工业音色工程化:声音设计与自动化生成实践

这次我们来看一个名为「SUBTERRA」的音乐项目。它不是传统的AI模型或开发工具,而是一个专注于特定音乐风格的音频作品或专辑。从标题“2小时地底迷航”和“极致黑暗/酸性/工业底噪”的描述来看,它很可能是一张氛围音乐、工业噪音或实验电子乐的合集&…

2026/8/13 8:07:38 阅读更多 →

最新新闻

如何免费搭建私人云游戏平台:Sunshine游戏串流终极指南

如何免费搭建私人云游戏平台:Sunshine游戏串流终极指南

如何免费搭建私人云游戏平台:Sunshine游戏串流终极指南 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 你是否厌倦了在不同设备间切换游戏的麻烦?想要在客厅…

2026/8/13 9:55:49 阅读更多 →
大模型幻觉治理:从原理到工程实践,构建可信AI应用

大模型幻觉治理:从原理到工程实践,构建可信AI应用

1. 从“一本正经地胡说八道”说起:大模型幻觉的本质 最近在跟几个做AI应用落地的朋友聊天,大家不约而同地提到了同一个头疼的问题:自家的产品,无论是智能客服、内容生成助手还是知识问答系统,时不时就会“编故事”。用…

2026/8/13 9:55:49 阅读更多 →
AMD Ryzen终极性能调优:免费开源硬件调试工具完整指南

AMD Ryzen终极性能调优:免费开源硬件调试工具完整指南

AMD Ryzen终极性能调优:免费开源硬件调试工具完整指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://gi…

2026/8/13 9:55:49 阅读更多 →
终极Sketch标注插件:Sketch MeaXure如何提升设计交付效率300%

终极Sketch标注插件:Sketch MeaXure如何提升设计交付效率300%

终极Sketch标注插件:Sketch MeaXure如何提升设计交付效率300% 【免费下载链接】sketch-meaxure 项目地址: https://gitcode.com/gh_mirrors/sk/sketch-meaxure 还在为设计标注的繁琐流程而苦恼吗?Sketch MeaXure是一款专为Sketch设计师打造的智能…

2026/8/13 9:55:49 阅读更多 →
WebGPU、Ollama与llama.cpp:大模型本地部署方案深度对比与实战指南

WebGPU、Ollama与llama.cpp:大模型本地部署方案深度对比与实战指南

1. 项目概述:当大模型遇见WebGPU最近在折腾本地大模型部署的朋友,可能都绕不开几个关键词:Ollama、llama.cpp、GGUF,还有那个听起来有点“次世代”感觉的WebGPU。我自己也是从最早在服务器上吭哧吭哧配环境,到后来在个…

2026/8/13 9:55:49 阅读更多 →
TensorFlow 2.x实战:从零构建LSTM模型,解决文本情感分类任务

TensorFlow 2.x实战:从零构建LSTM模型,解决文本情感分类任务

1. 从“记不住”到“忘不掉”:为什么我们需要LSTM? 如果你尝试过用传统的神经网络来处理时间序列数据,比如股票价格预测、文本生成或者语音识别,大概率会遇到一个让人头疼的问题:模型好像“记性”不太好。它处理当前输…

2026/8/13 9:54:49 阅读更多 →

日新闻

Visual Studio新建项目解决方案为空:系统性排查与修复指南

Visual Studio新建项目解决方案为空:系统性排查与修复指南

1. 问题现象与本质剖析如果你是一位.NET开发者,或者正准备踏入这个领域,那么Visual Studio(后面简称VS)绝对是你绕不开的伙伴。但有时候,这个伙伴会跟你开一个不大不小的玩笑:你满怀期待地点击“创建新项目…

2026/8/13 0:00:09 阅读更多 →
长春建设厅网站:普通人买房办事必看的真实指南与避坑攻略

长春建设厅网站:普通人买房办事必看的真实指南与避坑攻略

说实话,每次提起“长春建设厅网站”这几个字,我心里都挺有感触的。不是因为它有多高大上,也不是因为那里藏着什么不可告人的秘密,恰恰相反,是因为它太“接地气”了,或者说,它是咱们普通人想要在这个城市好好生活、安稳买房时,必须得翻过的一座“数据山”。很多新朋友第…

2026/8/13 0:00:09 阅读更多 →
Windows家庭版远程桌面多用户破解完整指南:RDPWrap终极解决方案

Windows家庭版远程桌面多用户破解完整指南:RDPWrap终极解决方案

Windows家庭版远程桌面多用户破解完整指南:RDPWrap终极解决方案 【免费下载链接】rdpwrap.ini RDPWrap.ini for RDP Wrapper Library by StasM 项目地址: https://gitcode.com/GitHub_Trending/rd/rdpwrap.ini 你是否曾为Windows家庭版无法支持多用户远程桌面…

2026/8/13 0:00:09 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/13 2:38:34 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/12 1:11:09 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/12 1:11:08 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/11 17:09:45 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/12 1:11:10 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/11 17:09:45 阅读更多 →