C 模板初阶完全指南一篇帮你彻底搞懂 C 模板基础的复习笔记一、泛型编程1.1 为什么需要泛型编程问题如何实现一个通用的交换函数// 传统方式函数重载voidSwap(intleft,intright){inttempleft;leftright;righttemp;}voidSwap(doubleleft,doubleright){doubletempleft;leftright;righttemp;}voidSwap(charleft,charright){chartempleft;leftright;righttemp;}// ...... 每种类型都要写一遍1.2 函数重载的缺点缺点说明代码复用率低仅仅是类型不同却要重复编写可维护性差一处出错所有重载都要修改扩展困难新类型出现需要手动添加函数1.3 泛型编程概念泛型编程编写与类型无关的通用代码是代码复用的一种手段。模板是泛型编程的基础。┌─────────────────────────────────────┐ │ 泛型编程思想 │ ├─────────────────────────────────────┤ │ │ │ 模板 (模具) │ │ │ │ │ ├──→ 填充 int → int 版本 │ │ ├──→ 填充 double → double 版本 │ │ └──→ 填充 char → char 版本 │ │ │ │ 编译器自动生成无需手写重复代码 │ │ │ └─────────────────────────────────────┘二、函数模板2.1 概念函数模板代表了一个函数家族与类型无关在使用时被参数化根据实参类型产生函数的特定类型版本。2.2 定义格式templatetypenameT1,typenameT2,...,typenameTn返回值类型 函数名(参数列表){// 函数体}2.3 示例templatetypenameTvoidSwap(Tleft,Tright){T templeft;leftright;righttemp;}intmain(){inta1,b2;doublec1.0,d2.0;charea,fb;Swap(a,b);// 编译器生成 int 版本Swap(c,d);// 编译器生成 double 版本Swap(e,f);// 编译器生成 char 版本return0;}⚠️注意typename用来定义模板参数关键字也可以使用class但不能使用struct// 以下两种写法等价templatetypenameTtemplateclassT// 错误写法templatestructT// ❌ 不允许2.4 函数模板的原理┌───────────────────────────────────────────────────┐ │ 编译器工作流程 │ ├───────────────────────────────────────────────────┤ │ │ │ 源代码阶段 │ │ templatetypename T │ │ void Swap(T left, T right) { ... } │ │ ↓ │ │ 这只是蓝图/模具不是真正的函数 │ │ │ │ 编译阶段 │ │ Swap(int, int) → 推演 T int │ │ Swap(double, double) → 推演 T double │ │ ↓ │ │ 编译器自动生成对应类型的具体函数 │ │ │ └───────────────────────────────────────────────────┘核心思想将本来应该我们做的重复的事情交给了编译器三、函数模板的实例化用不同类型的参数使用函数模板时称为函数模板的实例化。3.1 隐式实例化让编译器根据实参推演模板参数的实际类型。templateclassTTAdd(constTleft,constTright){returnleftright;}intmain(){inta110,a220;doubled110.0,d220.0;Add(a1,a2);// T 推演为 intAdd(d1,d2);// T 推演为 double// ❌ 编译错误// Add(a1, d1);// a1 推演 T intd1 推演 T double// 只有一个 T编译器无法确定类型return0;}3.2 解决类型冲突的两种方式intmain(){inta10;doubleb20.0;// 方式一用户强制转换Add(a,(int)b);// b 强转为 int// 方式二显式实例化Addint(a,b);// 指定 T 为 intreturn0;}3.3 显式实例化在函数名后的中指定模板参数的实际类型。templateclassTTAdd(constTleft,constTright){returnleftright;}intmain(){inta10;doubleb20.0;// 显式实例化Addint(a,b);// 指定 T intb 会隐式转换为 intreturn0;}⚠️ 如果类型不匹配编译器会尝试进行隐式类型转换无法转换则报错。四、模板参数的匹配原则4.1 原则一非模板函数与模板可共存// 专门处理 int 的加法函数intAdd(intleft,intright){returnleftright;}// 通用加法函数模板templateclassTTAdd(T left,T right){returnleftright;}voidTest(){Add(1,2);// 调用非模板函数完全匹配Addint(1,2);// 调用模板实例化版本}4.2 原则二优先匹配非模板函数intAdd(intleft,intright){returnleftright;}templateclassTTAdd(T left,T right){returnleftright;}voidTest(){Add(1,2);// 优先调用非模板函数不需要实例化}4.3 原则三模板可产生更好匹配intAdd(intleft,intright){returnleftright;}templateclassT1,classT2T1Add(T1 left,T2 right){returnleftright;}voidTest(){Add(1,2);// 调用非模板函数int, int 完全匹配Add(1,2.0);// 调用模板生成 Add(int, double) 更匹配}4.4 原则四模板不自动类型转换templateclassTTAdd(T left,T right){returnleftright;}intmain(){inta10;doubleb20.0;// Add(a, b); // ❌ 编译错误模板不会自动转换类型Add(a,(int)b);// ✅ 手动转换Addint(a,b);// ✅ 显式实例化return0;}⚠️注意编译器一般不会对模板参数进行类型转换因为一旦转换出问题编译器需要背黑锅4.5 匹配原则速查表场景调用结果非模板函数完全匹配✅ 调用非模板函数模板函数更匹配✅ 调用模板实例化都不匹配但非模板可转换✅ 调用非模板函数都不匹配且模板不可转换❌ 编译错误五、类模板5.1 定义格式templateclassT1,classT2,...,classTnclass类模板名{// 类内成员定义};5.2 示例栈类模板#includeiostreamusingnamespacestd;templatetypenameTclassStack{public:Stack(size_t capacity4){_arraynewT[capacity];_capacitycapacity;_size0;}voidPush(constTdata);private:T*_array;size_t _capacity;size_t _size;};// 类外定义成员函数templateclassTvoidStackT::Push(constTdata){// 扩容逻辑_array[_size]data;_size;}5.3 类模板的实例化intmain(){Stackintst1;// int 栈Stackdoublest2;// double 栈return0;}⚠️重要Stack是类名Stackint才是类型5.4 类模板 vs 函数模板实例化对比项函数模板类模板实例化方式可隐式推演必须显式指定语法func(args)或funcT(args)ClassNameT类型确定编译器推演用户指定六、模板注意事项6.1 声明和定义分离// ❌ 不建议声明和定义分离到 .h 和 .cpp// 会出现链接错误// ✅ 建议声明和定义放在同一个头文件中原因后续章节会讲解模板实例化需要完整定义可见6.2 模板参数命名// 单个参数templatetypenameT// 多个参数templatetypenameT1,typenameT2// 混合使用templatetypenameT,intN// N 是非类型模板参数七、一图总结┌───────────────────────────────────────────────────────┐ │ C 模板初阶总结 │ ├───────────────────────────────────────────────────────┤ │ │ │ 【泛型编程】 │ │ 目的编写与类型无关的通用代码 │ │ 核心模板是泛型编程的基础 │ │ │ │ 【函数模板】 │ │ 格式templatetypename T │ │ 实例化隐式 / 显式 │ │ 原理编译器根据类型生成具体函数 │ │ │ │ 【实例化规则】 │ │ 隐式编译器推演类型 │ │ 显式用户指定 类型 │ │ │ │ 【匹配原则】 │ │ 1. 非模板函数与模板可共存 │ │ 2. 优先匹配非模板函数 │ │ 3. 模板可产生更好匹配时选模板 │ │ 4. 模板不自动类型转换 │ │ │ │ 【类模板】 │ │ 实例化必须显式指定类型 │ │ Stackint 才是类型Stack 只是类名 │ │ │ └───────────────────────────────────────────────────────┘八、记忆口诀模板模具造函数类型填充自动生 隐式推演显式定优先匹配非模板 类模板要显式写Stackint 才是型 声明定义放一起分离编译会出错希望这篇笔记能帮助你快速复习 C 模板初阶知识如有疑问欢迎讨论交流。