C++:模板
一.初识泛型编程
不难看出以上代码存在的问题:
1. 重复冗余:针对 int 、 double 、 char 类型,交换逻辑完全一致,却要编写多份函数,增加代码量与维护成本,违背“避免重复”的编程原则 。
2. 扩展性差:若后续需支持新类型(如 string 等),必须再编写对应的重载函数,无法灵活适配新增类型,可维护性低 。
3. 未体现泛型优势:未借助C++模板机制简化代码,仍依赖手动编写重载,没能发挥泛型编程提升代码复用性、通用性的价值 。
就以上问题C++,提出来泛型编程与模板的概念。
C++ 里的泛型编程,简单说就是 用“模板”当“万能模具” ,让一套代码能处理不同类型数据 。
你可以把模板想成做月饼的模具,不管要做豆沙馅、蛋黄馅月饼(对应不同数据类型,像 int 、 double ),用同一个模具(模板)就行,不用为每种馅重新做模具。
写代码时,用 template 定义模板,里面用 T (或其他名字)当“类型占位符” 。比如写交换函数,不用给 int 、 double 分别写,搞个模板函数,让编译器根据传入的数据,自动把 T 换成对应类型,生成专属代码。
这样做能 少写重复代码 (一套逻辑适配所有类型),还能 保证类型安全 (编译器会检查类型对不对),像 STL 里的 vector 、 map 这些容器,底层就靠泛型模板实现,不管存 int 还是 string ,都用同一套框架,特方便!
模板分为以下两类:函数模板和类模板
二.函数模板
1.函数模板定义
C++函数模板是泛型工具,用“模板参数”适配多种类型,一套逻辑处理不同数据,省重复代码。
以上 C++ 函数模板定义。用 template<typename T> 声明, T 是类型参数,代表任意类型。定义 Swap 函数时,用 T 适配不同类型,调用时编译器依据实参自动推导 T 具体类型,生成对应函数,实现一套代码处理多种类型交换,体现泛型编程复用性。
(注:处理使用typename关机自定义模板参数,还可以用class定义模板参数,但不能用struct定义)
2.函数模板的原理
函数模板相当于一个模具,本身并不是函数。
以上是 C++ 函数模板的类型推导与实例化流程示意:
1. 调用触发推导: main 里调用 Swap(d1,d2) ( double 类型)、 Swap(i1,i2) ( int 类型)、 Swap(a,b) ( char 类型)时,编译器会根据实参类型推导 T 。
2. 生成专属函数:推导后,编译器为每种类型自动生成对应函数(如 double 版 Swap 用 double 替换 T , int 版同理 ),实现“一套模板、多份实例”,既复用逻辑,又保证类型适配,体现泛型编程的灵活与高效。
3.函数模板的实例化
用不同类型参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化 。
(1)隐式实例化:让编译器根据实参推演模板参数的实际类型
以上这段代码完美体现了 C++ 函数模板的隐式实例化,核心流程可以拆成三步理解:
1. 模板是“蓝图”: template<typename T> void Swap(T& a, T& b) 是模板, T 是“类型占位符”,本身不是真正能运行的函数,更像一套“逻辑蓝图”。
2. 调用触发推导: main 里调用 Swap(a, b) 时,传入的 a 和 b 是 int 类型。编译器会自动推导:“哦,这里的 T 应该是 int ”
3. 隐式生成函数:编译器悄悄帮你干了件大事 —— 根据推导结果,生成专门处理 int 类型的 Swap 函数(相当于自动写了 void Swap(int& a, int& b) { ... } )。这个过程完全“隐式”,你不用手动写 Swap<int>(a, b) ,编译器默默把模板变成了能运行的具体函数。
简单说,隐式实例化就是编译器根据你传的参数类型,自动把模板“变成”对应类型的实际函数,让你写代码时不用操心类型匹配,既复用逻辑,又写得轻松。
(2)显式实例化
显式实例化:在函数名后的<>中指定模板参数的实际类型
以上这段代码里的 Swap<int>(a, b) 、 Swap<double>(x, y) 就是 显式实例化,关键看这两点:
1. 手动指定类型:你在调用时,通过 <int> 、 <double> 直接告诉编译器:“用 int / double 替换模板里的 T !” 不像隐式实例化那样靠参数推导,这里是你主动指定类型。
2. 强制生成实例:编译器收到指令后,会专门为 int 和 double 类型生成对应的 Swap 函数(比如 Sw1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这
简单说,显式实例化就是你直接指定模板参数类型,让编译器精准生成对应版本的函数,适合需要明确控制类型,或提前生成特定实例的场景。
4.函数模板的匹配规则
(1) 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这非模板函数。
展示了 C++ 函数模板匹配规则的典型场景。当调用 Swap(a, b) 时,因存在普通 int 版 Swap 函数,优先匹配它;调用 Swap(x, y) 时,无对应普通函数,触发模板隐式实例化生成 double 版。体现普通函数优先、模板按需实例化的匹配逻辑,帮我们理解函数模板调用时的选择机制 。
(2)对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
代码呈现 C++ 函数匹配的场景。存在模板 Add 与普通 int 版 Add ,调用时: Add(a,b) 因普通函数精准匹配优先调用; Add(a,x) 、 Add(x,y) 触发模板,编译器根据实参推导类型,实例化对应模板函数,体现函数匹配规则里普通函数优先、模板按需实例化的逻辑 。
三.类模板
1.类模板的定义格式
以下从类模板定义格式与显式实例化角度,对代码进行讲解:
①类模板定义格式
代码中 template<class T> class stack 遵循 C++ 类模板标准定义规范。
- template<class T> :作为模板参数声明, class (也可替换为 typename )用于引入类型参数 T , T 本质是“类型占位符”,可代表 int 、 double 等任意数据类型,为类适配不同数据场景提供基础 。
- class stack :基于 T 构建类的整体结构,类内私有成员(如指针 T* _arr 、整型 _top 和 _capacity )、公有成员(构造函数 stack 、成员函数 push 声明 ),均借助 T 实现类型适配,使 stack 类能依据 T 的具体类型,处理对应数据的栈操作逻辑。
②显式实例化
在 main 函数中, stack<int> st1; 与 stack<double> st2; 属于类模板的显式实例化操作:
- 实例化时,需手动通过 <int> 、 <double> 明确指定类型参数,向编译器传递指令:使用 int 、 double 类型替换类模板中 T ,生成专门处理对应类型数据的栈类(即 stack<int> 用于存储与操作 int 类型数据, stack<double> 用于 double 类型 )。
- 类模板特性决定其无隐式实例化机制,必须由开发者显式指定类型,编译器才会依据模板定义,生成对应类型的具体类代码,以支撑程序运行。
简言之,类模板依托 template<class T> 搭建通用逻辑框架,使用阶段需通过显式指定类型(如 <int> 、 <double> ) 完成实例化,生成适配特定数据类型的类,达成一套模板复用、适配多类型场景的效果 。
(注:代码中构造函数内 _arr 虽经 new T[capacity] 动态分配内存,但未完善内存释放等资源管理逻辑,实际应用易引发内存泄漏等问题,需补充析构函数等进行优化 。 )