C++ 模板编程基础:泛型编程入门与实践
第33篇:C++ 模板编程基础:泛型编程入门与实践
一、学习目标与重点
- 掌握模板的核心概念、分类(函数模板、类模板)及基本语法
- 理解泛型编程的思想,能够独立编写函数模板和类模板
- 掌握模板的实例化、特化、偏特化等关键技术
- 解决模板使用中的常见问题(类型推导失败、编译错误等)
- 结合实际场景运用模板提升代码复用性和灵活性
- 了解模板与STL的关联,为后续STL学习奠定基础
💡 核心重点:模板的语法规则、类型参数与非类型参数的使用、模板特化的应用场景、泛型编程的核心价值
二、模板与泛型编程概述
2.1 什么是泛型编程
泛型编程(Generic Programming)是一种代码复用技术,核心思想是“编写与类型无关的通用代码,在使用时再指定具体类型”,实现“一次编写,多次复用”。
🗄️ 生活中的泛型类比:
- 快递盒:同一个快递盒(通用容器)可装手机、书籍、衣物(不同类型数据),无需为每种物品单独设计盒子
- 模板工具:如3D打印机模板,同一模板可打印不同材质(塑料、金属)的同一形状零件
2.2 为什么需要模板
在C++中,若未使用模板,针对不同类型的相同逻辑需重复编写代码,导致冗余且难以维护:
// 重复代码:int类型加法intadd_int(int a,int b){return a + b;}// 重复代码:double类型加法doubleadd_double(double a,double b){return a + b;}// 重复代码:float类型加法floatadd_float(float a,float b){return a + b;}💡 模板的价值:用一套代码适配所有兼容逻辑的类型,减少冗余、提升可维护性,同时保证类型安全(编译时类型检查)。
2.3 模板的分类
C++模板主要分为两类:
- 函数模板:用于创建通用函数,支持不同类型的参数输入
- 类模板:用于创建通用类(如容器、算法类),支持不同类型的成员变量和成员函数参数
✅ 核心优势:模板是C++泛型编程的基础,STL(标准模板库)的容器(vector、map)、算法(sort、find)均基于模板实现。
三、函数模板:通用函数的实现
3.1 函数模板的基本语法
函数模板的声明需使用template关键字,指定类型参数(或非类型参数),语法格式如下:
// 格式:template <模板参数列表> 返回值类型 函数名(参数列表) { 函数体 }template<typenameT>// T为类型参数(typename可替换为class,含义相同) 返回值类型 函数名(T 参数1, T 参数2,...){// 通用逻辑(与类型无关)}💡 语法解析:
template <typename T>:模板声明,typename表示“后面的标识符是类型参数”,T是类型占位符(可自定义名称,如Type、ElemType)- 函数参数列表中使用
T作为类型,表明参数类型由调用时指定或推导 - 函数体逻辑需与类型无关(如使用
+运算符需确保传入类型支持该运算符)
3.2 函数模板的定义与调用
💡 示例:实现通用加法函数模板
#include<iostream>usingnamespace std;// 函数模板:通用加法函数,支持任意支持+运算符的类型template<typenameT>// T为类型参数,代表任意类型 T add(T a, T b){ cout <<"模板函数调用,类型为:"<<typeid(T).name()<< endl;// 打印类型名称return a + b;}intmain(){// 1. 显式指定类型参数(推荐,可读性强)int num1 =add<int>(10,20); cout <<"int类型加法:10 + 20 = "<< num1 << endl;// 30double num2 =add<double>(3.14,2.86); cout <<"double类型加法:3.14 + 2.86 = "<< num2 << endl;// 6.0// 2. 隐式推导类型参数(编译器根据实参类型自动推导T)float num3 =add(5.5f,3.5f); cout <<"float类型加法:5.5 + 3.5 = "<< num3 << endl;// 9.0f string str1 ="Hello, ", str2 ="C++!"; string str3 =add(str1, str2);// 字符串支持+运算符,推导T为string cout <<"string类型加法:"<< str3 << endl;// Hello, C++!return0;}✅ 运行结果:
模板函数调用,类型为:int int类型加法:10 + 20 = 30 模板函数调用,类型为:double double类型加法:3.14 + 2.86 = 6 模板函数调用,类型为:float float类型加法:5.5 + 3.5 = 9 模板函数调用,类型为:std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > string类型加法:Hello, C++! 3.3 函数模板的类型推导规则
编译器会根据实参类型自动推导模板的类型参数T,推导规则如下:
- 实参类型必须一致(若函数参数均为
T类型),否则推导失败 - 引用/const修饰会被保留或忽略(需注意类型匹配)
- 数组、函数会退化为指针类型(除非显式指定为引用)
💡 示例:类型推导细节
template<typenameT>voidprint_type(T param){ cout <<"参数类型:"<<typeid(T).name()<< endl;}intmain(){int a =10;constint b =20;int& c = a;int arr[5]={1,2,3,4,5};voidfunc(int){}print_type(a);// 实参为int,推导T=intprint_type(b);// 实参为const int,推导T=int(const被忽略,param为int)print_type(c);// 实参为int&,推导T=int(引用被忽略,param为int)print_type(arr);// 实参数组,推导T=int*(数组退化为指针)print_type(func);// 实参函数,推导T=void(*)(int)(函数退化为指针)// 若需保留引用/const,需显式指定模板参数为引用类型template<typenameT>voidprint_ref_type(T& param){}print_ref_type(b);// 推导T=const int(保留const)print_ref_type(c);// 推导T=int(保留引用,param为int&)return0;}⚠️ 警告:若实参类型不一致且未显式指定类型,会导致编译错误:
// 错误:实参类型分别为int和double,T无法推导add(10,3.14);// 编译错误:no matching function for call to 'add(int, double)'// 解决:显式指定类型,编译器会进行隐式类型转换(若支持)add<double>(10,3.14);// 正确,10被转换为double类型3.4 函数模板的重载
函数模板支持重载,可与普通函数或其他函数模板构成重载关系,调用时遵循“最匹配原则”:
- 普通函数优先于模板函数(若参数完全匹配)
- 模板函数的显式特化优先于通用模板
- 更具体的模板(如多参数模板)优先于更通用的模板
💡 示例:函数模板重载
#include<iostream>usingnamespace std;// 普通函数:处理int类型加法intadd(int a,int b){ cout <<"普通函数(int)调用:";return a + b;}// 函数模板:通用加法template<typenameT> T add(T a, T b){ cout <<"模板函数("<<typeid(T).name()<<")调用:";return a + b;}// 函数模板重载:支持两个不同类型的参数template<typenameT1,typenameT2>autoadd(T1 a, T2 b)->decltype(a + b){// 尾置返回类型,推导返回值类型 cout <<"模板函数("<<typeid(T1).name()<<","<<typeid(T2).name()<<")调用:";return a + b;}intmain(){// 调用普通函数(完全匹配int类型) cout <<add(10,20)<< endl;// 输出:普通函数(int)调用:30// 调用通用模板函数(double类型匹配) cout <<add(3.14,2.86)<< endl;// 输出:模板函数(double)调用:6// 调用重载模板函数(int和double类型不同) cout <<add(10,3.14)<< endl;// 输出:模板函数(int,double)调用:13.14return0;}✅ 运行结果:
普通函数(int)调用:30 模板函数(double)调用:6 模板函数(int,double)调用:13.14 3.5 函数模板的特化
当通用模板无法满足特定类型的需求(如特殊逻辑、运算符不支持)时,可对模板进行特化(Specialization),为特定类型提供专属实现。
特化的语法格式:
template<>// 空模板参数列表,表示特化 返回值类型 函数名<特化类型>(特化类型 参数1, 特化类型 参数2,...){// 专属逻辑}💡 示例:函数模板特化(处理string类型的特殊加法)
#include<iostream>#include<string>usingnamespace std;// 通用函数模板:加法template<typenameT> T add(T a, T b){ cout <<"通用模板:";return a + b;}// 特化:为string类型提供专属实现(拼接字符串时添加空格)template<> string add<string>(string a, string b){ cout <<"string特化模板:";return a +" "+ b;// 特殊逻辑:拼接时添加空格}intmain(){ cout <<add(10,20)<< endl;// 通用模板:30 cout <<add(3.14,2.86)<< endl;// 通用模板:6 cout <<add(string("Hello"),string("World"))<< endl;// string特化模板:Hello Worldreturn0;}✅ 运行结果:
通用模板:30 通用模板:6 string特化模板:Hello World ⚠️ 注意事项:
- 特化模板必须与通用模板在同一作用域,且函数名、参数列表必须与通用模板一致
- 特化模板的返回值类型需与通用模板兼容
- 特化后,调用该类型时优先使用特化版本
四、类模板:通用类的实现
4.1 类模板的基本语法
类模板用于创建通用类,支持不同类型的成员变量和成员函数参数,语法格式如下:
template<typenameT>// 模板参数列表(可多个类型参数)class 类名 {public:// 成员变量(类型为T) T 成员变量;// 构造函数 类名(T 参数): 成员变量(参数){}// 成员函数(参数/返回值类型可使用T) T get_value()const{return 成员变量;}voidset_value(T value){ 成员变量 = value;}};💡 语法解析:
- 类模板声明需在
class关键字前加template <模板参数列表> - 类内部可使用类型参数
T定义成员变量、成员函数参数或返回值 - 类模板的成员函数若在类外定义,需再次声明模板参数列表
4.2 类模板的定义与实例化
类模板不能直接使用,需实例化(指定具体类型)后才能创建对象,实例化分为显式实例化和隐式实例化。
💡 示例:类模板的定义与实例化
#include<iostream>usingnamespace std;// 类模板:通用容器类(存储单个元素)template<typenameT>classContainer{private: T data;// 成员变量,类型为Tpublic:// 构造函数Container(T value):data(value){}// 成员函数:类内定义 T get_data()const{return data;}// 成员函数:类外定义(需再次声明模板参数)voidset_data(T value);};// 类外定义成员函数:必须加template <typename T>template<typenameT>voidContainer<T>::set_data(T value){ data = value;}intmain(){// 1. 显式实例化(推荐,可读性强) Container<int>int_container(100); cout <<"int容器初始值:"<< int_container.get_data()<< endl;// 100 int_container.set_data(200); cout <<"int容器修改后:"<< int_container.get_data()<< endl;// 200// 2. 隐式实例化(编译器根据构造函数参数推导类型) Container<double>double_container(3.14); cout <<"double容器值:"<< double_container.get_data()<< endl;// 3.14// 3. 实例化不同类型的对象(相互独立) Container<string>str_container("Hello C++"); cout <<"string容器值:"<< str_container.get_data()<< endl;// Hello C++return0;}✅ 运行结果:
int容器初始值:100 int容器修改后:200 double容器值:3.14 string容器值:Hello C++ 4.3 类模板的多参数模板
类模板支持多个类型参数(或非类型参数),参数之间用逗号分隔:
// 多类型参数:T为元素类型,Alloc为分配器类型(默认值为void)template<typenameT,typenameAlloc=void>classMyVector{// 类实现};// 非类型参数:N为常量整数(必须是编译期可确定的值)template<typenameT,int N>classArray{private: T data[N];// 固定大小的数组,N为模板参数public:intsize()const{return N;} T&operator[](int index){return data[index];}};💡 示例:带非类型参数的类模板(固定大小数组)
#include<iostream>usingnamespace std;// 类模板:固定大小数组(N为非类型参数,编译期确定大小)template<typenameT,int N>classFixedArray{private: T data[N];// 数组大小为N(编译期固定)public:// 构造函数:初始化所有元素为默认值FixedArray(){for(int i =0; i < N;++i){ data[i]=T();// T()为默认构造(如int默认0,string默认空串)}}// 赋值操作:设置指定索引的元素voidset(int index, T value){if(index >=0&& index < N){ data[index]= value;}}// 获取元素 T get(int index)const{if(index >=0&& index < N){return data[index];}returnT();// 索引越界返回默认值}// 获取数组大小intsize()const{return N;}// 打印数组voidprint()const{for(int i =0; i < N;++i){ cout << data[i]<<" ";} cout << endl;}};intmain(){// 实例化:int类型,大小为5的数组 FixedArray<int,5> int_arr; int_arr.set(0,10); int_arr.set(1,20); int_arr.set(2,30); cout <<"int数组(大小"<< int_arr.size()<<"):"; int_arr.print();// 10 20 30 0 0// 实例化:double类型,大小为3的数组 FixedArray<double,3> double_arr; double_arr.set(0,1.1); double_arr.set(1,2.2); double_arr.set(2,3.3); cout <<"double数组(大小"<< double_arr.size()<<"):"; double_arr.print();// 1.1 2.2 3.3// 实例化:string类型,大小为4的数组 FixedArray<string,4> str_arr; str_arr.set(0,"Apple"); str_arr.set(1,"Banana"); cout <<"string数组(大小"<< str_arr.size()<<"):"; str_arr.print();// Apple Banana (后两个为默认空串)return0;}✅ 运行结果:
int数组(大小5):10 20 30 0 0 double数组(大小3):1.1 2.2 3.3 string数组(大小4):Apple Banana ⚠️ 非类型参数的限制:
- 非类型参数必须是编译期可确定的常量(如字面量、const变量、enum值)
- 支持的类型:整数类型(int、long)、枚举类型、指针类型、引用类型
- 不支持浮点数(double、float)、类类型(如string)作为非类型参数
4.4 类模板的特化与偏特化
类模板同样支持特化(为特定类型提供专属实现),且支持偏特化(为部分模板参数指定类型,保留其他参数的通用性)。
4.4.1 类模板的全特化
全特化是为所有模板参数指定具体类型,语法格式:
template<>// 空模板参数列表class 类名<特化类型>{// 专属实现};💡 示例:类模板全特化
#include<iostream>#include<string>usingnamespace std;// 通用类模板:打印元素类型和值template<typenameT>classPrinter{public:voidprint(T value){ cout <<"通用模板 - 类型:"<<typeid(T).name()<<",值:"<< value << endl;}};// 全特化:为string类型提供专属实现(美化输出)template<>classPrinter<string>{public:voidprint(string value){ cout <<"string特化模板 - 字符串:\""<< value <<"\""<< endl;}};// 全特化:为int类型提供专属实现(添加单位)template<>classPrinter<int>{public:voidprint(int value){ cout <<"int特化模板 - 整数:"<< value <<"(单位:个)"<< endl;}};intmain(){ Printer<double> double_printer; double_printer.print(3.14);// 通用模板 - 类型:double,值:3.14 Printer<string> str_printer; str_printer.print("Hello C++");// string特化模板 - 字符串:"Hello C++" Printer<int> int_printer; int_printer.print(100);// int特化模板 - 整数:100(单位:个)return0;}✅ 运行结果:
通用模板 - 类型:double,值:3.14 string特化模板 - 字符串:"Hello C++" int特化模板 - 整数:100(单位:个) 4.4.2 类模板的偏特化
偏特化是为部分模板参数指定类型,保留其他参数的通用性,适用于多参数模板。语法格式:
template<保留的模板参数>class 类名<保留参数, 指定的参数>{// 偏特化实现};💡 示例:类模板偏特化
#include<iostream>usingnamespace std;// 多参数类模板:T为元素类型,U为附加类型template<typenameT,typenameU>classPair{public:Pair(T first, U second):first_val(first),second_val(second){}voiddisplay(){ cout <<"通用模板 - 第一个值("<<typeid(T).name()<<"):"<< first_val <<",第二个值("<<typeid(U).name()<<"):"<< second_val << endl;}private: T first_val; U second_val;};// 偏特化1:当第二个参数U为int时的专属实现template<typenameT>// 保留第一个参数T,指定第二个参数为intclassPair<T,int>{public:Pair(T first,int second):first_val(first),second_val(second){}voiddisplay(){ cout <<"偏特化(U=int) - 第一个值:"<< first_val <<",第二个值(整数):"<< second_val <<"(已乘以2:"<< second_val *2<<")"<< endl;}private: T first_val;int second_val;};// 偏特化2:当两个参数均为指针类型时的专属实现template<typenameT,typenameU>classPair<T*, U*>{// 两个参数均为指针类型public:Pair(T* first, U* second):first_ptr(first),second_ptr(second){}voiddisplay(){ cout <<"偏特化(T*+U*) - 第一个指针地址:"<< first_ptr <<",值:"<<*first_ptr <<";第二个指针地址:"<< second_ptr <<",值:"<<*second_ptr << endl;}private: T* first_ptr; U* second_ptr;};intmain(){// 通用模板:T=string,U=double Pair<string,double>p1("PI",3.14); p1.display();// 偏特化1:T=string,U=int Pair<string,int>p2("计数",5); p2.display();// 偏特化2:T=int*,U=double*int a =10;double b =2.5; Pair<int*,double*>p3(&a,&b); p3.display();return0;}✅ 运行结果:
通用模板 - 第一个值(std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >):PI,第二个值(double):3.14 偏特化(U=int) - 第一个值:计数,第二个值(整数):5(已乘以2:10) 偏特化(T*+U*) - 第一个指针地址:0x7ffee4b7e7ac,值:10;第二个指针地址:0x7ffee4b7e7a0,值:2.5 ⚠️ 偏特化注意事项:
- 偏特化不能改变模板参数的数量,只能为部分参数指定具体类型
- 偏特化版本的模板参数列表需与通用模板兼容
- 若存在多个偏特化版本,编译器会选择最匹配的版本
五、模板的编译机制与常见问题
5.1 模板的编译机制
C++模板采用“实例化时编译”(Compile on Instantiation)机制,核心特点:
- 模板定义本身不会生成可执行代码,仅当实例化(指定具体类型)时,编译器才会生成对应类型的函数/类代码
- 模板的声明和定义需在同一翻译单元(.cpp文件)中可见,否则会导致链接错误(“未定义的引用”)
💡 编译流程解析:
- 编译模板定义文件时,编译器仅检查模板语法是否正确(如括号匹配、关键字使用),不生成代码
- 当其他文件调用模板(如
add<int>(10,20)),编译器会生成int类型的add函数代码 - 若模板定义在.h文件中,包含该.h文件的.cpp文件均可实例化模板;若模板定义在.cpp文件中,需显式实例化才能被其他文件使用
⚠️ 常见错误:模板定义与声明分离导致链接错误
// 错误示例:// add.h(模板声明)template<typenameT> T add(T a, T b);// add.cpp(模板定义)template<typenameT> T add(T a, T b){return a + b;}// main.cpp(调用模板)#include"add.h"intmain(){add<int>(10,20);// 链接错误:undefined reference to `int add<int>(int, int)'}解决方案:
- 将模板的声明和定义放在同一.h文件中(推荐,简单高效)
- 在模板定义文件中显式实例化所需类型:
// add.cpptemplate<typenameT> T add(T a, T b){return a + b;}// 显式实例化int和double类型templateintadd<int>(int,int);templatedoubleadd<double>(double,double);5.2 模板使用的常见错误与规避
错误1:类型推导失败
原因:实参类型不一致、模板参数无法从实参推导、隐式转换不支持。
规避方案:
- 确保实参类型一致,或显式指定模板类型参数
- 避免依赖隐式转换,手动转换实参类型
// 错误:实参类型不一致,推导失败add(10,3.14);// 正确:显式指定类型add<double>(10,3.14);// 正确:手动转换实参类型add(static_cast<double>(10),3.14);错误2:模板参数不支持特定操作
原因:传入的类型不支持模板中的运算符或函数(如对自定义类使用+运算符)。
规避方案:
- 为自定义类重载所需运算符
- 使用模板特化为该类型提供专属实现
- 在模板中添加类型约束(C++20后支持
concept)
// 自定义类classPoint{public:int x, y;Point(int x=0,int y=0):x(x),y(y){}// 重载+运算符,支持模板中的加法操作 Point operator+(const Point& other){returnPoint(x + other.x, y + other.y);}};// 模板可正常处理Point类型 Point p1(1,2),p2(3,4); Point p3 =add(p1, p2);// 正确,p3=(4,6)错误3:非类型参数不是编译期常量
原因:非类型参数使用了运行时才能确定的值(如普通变量)。
规避方案:
- 非类型参数使用字面量、const变量、enum值等编译期常量
- 若需运行时确定大小,改用动态内存分配(如vector)
// 错误:n是运行时变量,不能作为非类型参数int n =5; FixedArray<int, n> arr;// 编译错误:non-type template argument is not a constant expression// 正确:使用const常量(编译期确定)constint N =5; FixedArray<int, N> arr;错误4:模板特化与通用模板不匹配
原因:特化模板的函数名、参数列表与通用模板不一致。
规避方案:
- 特化模板的参数列表、返回值类型需与通用模板严格一致
- 确保特化模板与通用模板在同一作用域
// 通用模板template<typenameT> T add(T a, T b){return a + b;}// 错误:特化模板参数列表与通用模板不一致(多了一个参数)template<>intadd<int>(int a,int b,int c){return a + b + c;}六、实战案例:基于模板实现通用链表
6.1 问题描述
实现一个通用单向链表类,支持任意类型的元素存储,提供插入、删除、查找、遍历等基础操作,通过模板实现类型无关性。
6.2 实现思路
- 定义链表节点类模板
ListNode<T>,存储T类型的元素和下一个节点的指针 - 定义链表类模板
LinkedList<T>,包含头节点指针、链表长度等成员,实现核心操作 - 支持的操作:头插法、尾插法、按索引插入、按值删除、按索引查找、遍历打印等
- 为string类型提供特化的遍历打印(美化输出)
6.3 代码实现
#include<iostream>#include<string>usingnamespace std;// 1. 链表节点类模板template<typenameT>classListNode{public: T data;// 节点数据(通用类型T) ListNode<T>* next;// 下一个节点指针// 构造函数ListNode(T value):data(value),next(nullptr){}};// 2. 链表类模板template<typenameT>classLinkedList{private: ListNode<T>* head;// 头节点指针int length;// 链表长度public:// 构造函数:初始化空链表LinkedList():head(nullptr),length(0){}// 析构函数:释放所有节点内存~LinkedList(){ ListNode<T>* curr = head;while(curr !=nullptr){ ListNode<T>* temp = curr; curr = curr->next;delete temp;} head =nullptr; length =0;}// 头插法:在链表头部插入元素voidpush_front(T value){ ListNode<T>* new_node =newListNode<T>(value); new_node->next = head; head = new_node; length++;}// 尾插法:在链表尾部插入元素voidpush_back(T value){ ListNode<T>* new_node =newListNode<T>(value);if(head ==nullptr){// 空链表,新节点作为头节点 head = new_node;}else{// 找到尾节点 ListNode<T>* curr = head;while(curr->next !=nullptr){ curr = curr->next;} curr->next = new_node;} length++;}// 按索引插入元素(索引从0开始)boolinsert(int index, T value){if(index <0|| index > length){// 索引非法 cout <<"插入失败:索引"<< index <<"非法!"<< endl;returnfalse;}if(index ==0){// 索引0,等价于头插法push_front(value);returntrue;}// 找到索引前一个节点 ListNode<T>* curr = head;for(int i =0; i < index -1;++i){ curr = curr->next;} ListNode<T>* new_node =newListNode<T>(value); new_node->next = curr->next; curr->next = new_node; length++;returntrue;}// 按值删除第一个匹配的元素boolremove(T value){if(head ==nullptr){// 空链表 cout <<"删除失败:链表为空!"<< endl;returnfalse;} ListNode<T>* curr = head; ListNode<T>* prev =nullptr;// 查找目标元素while(curr !=nullptr&& curr->data != value){ prev = curr; curr = curr->next;}if(curr ==nullptr){// 未找到元素 cout <<"删除失败:未找到值"<< value <<"!"<< endl;returnfalse;}// 删除节点if(prev ==nullptr){// 删除头节点 head = curr->next;}else{// 删除中间/尾节点 prev->next = curr->next;}delete curr; length--;returntrue;}// 按索引查找元素 T get(int index)const{if(index <0|| index >= length){ cout <<"查找失败:索引"<< index <<"非法!"<< endl;returnT();// 返回默认值} ListNode<T>* curr = head;for(int i =0; i < index;++i){ curr = curr->next;}return curr->data;}// 获取链表长度intsize()const{return length;}// 遍历打印链表(通用版本)voidprint()const{if(head ==nullptr){ cout <<"链表为空!"<< endl;return;} cout <<"链表元素(长度"<< length <<"):"; ListNode<T>* curr = head;while(curr !=nullptr){ cout << curr->data <<" -> "; curr = curr->next;} cout <<"nullptr"<< endl;}};// 3. 特化:为string类型提供专属print函数(美化输出)template<>voidLinkedList<string>::print()const{if(head ==nullptr){ cout <<"链表为空!"<< endl;return;} cout <<"字符串链表(长度"<< length <<"):"; ListNode<string>* curr = head;while(curr !=nullptr){ cout <<"\""<< curr->data <<"\" -> "; curr = curr->next;} cout <<"nullptr"<< endl;}// 测试代码intmain(){// 测试int类型链表 cout <<"===== 测试int类型链表 ====="<< endl; LinkedList<int> int_list; int_list.push_back(10); int_list.push_back(20); int_list.push_front(5); int_list.insert(2,15);// 在索引2插入15 int_list.print();// 输出:链表元素(长度4):5 -> 10 -> 15 -> 20 -> nullptr cout <<"索引2的元素:"<< int_list.get(2)<< endl;// 15 int_list.remove(10);// 删除值10 int_list.print();// 输出:链表元素(长度3):5 -> 15 -> 20 -> nullptr// 测试string类型链表(特化print) cout <<"\n===== 测试string类型链表 ====="<< endl; LinkedList<string> str_list; str_list.push_back("Apple"); str_list.push_back("Banana"); str_list.push_front("Orange"); str_list.insert(1,"Grape"); str_list.print();// 输出:字符串链表(长度4):"Orange" -> "Grape" -> "Apple" -> "Banana" -> nullptr str_list.remove("Grape"); str_list.print();// 输出:字符串链表(长度3):"Orange" -> "Apple" -> "Banana" -> nullptr// 测试double类型链表 cout <<"\n===== 测试double类型链表 ====="<< endl; LinkedList<double> double_list; double_list.push_back(1.1); double_list.push_back(2.2); double_list.push_front(0.5); double_list.print();// 输出:链表元素(长度3):0.5 -> 1.1 -> 2.2 -> nullptrreturn0;}6.4 运行结果
===== 测试int类型链表 ===== 链表元素(长度4):5 -> 10 -> 15 -> 20 -> nullptr 索引2的元素:15 链表元素(长度3):5 -> 15 -> 20 -> nullptr ===== 测试string类型链表 ===== 字符串链表(长度4):"Orange" -> "Grape" -> "Apple" -> "Banana" -> nullptr 字符串链表(长度3):"Orange" -> "Apple" -> "Banana" -> nullptr ===== 测试double类型链表 ===== 链表元素(长度3):0.5 -> 1.1 -> 2.2 -> nullptr ✅ 结论:通过模板实现的通用链表支持int、string、double等多种类型,代码复用率高,且通过特化为string类型提供了美化输出,兼顾了通用性和灵活性。实际开发中,类似STL的vector、list等容器均采用类似的模板设计。
七、模板与STL的关联
STL(Standard Template Library,标准模板库)是C++泛型编程的典范,其核心组件(容器、算法、迭代器)均基于模板实现:
- 容器:vector、list、map<K,V> 等均为类模板,支持任意兼容类型的元素存储
- 算法:sort、find、reverse 等均为函数模板,可作用于不同类型的容器
- 迭代器:作为容器与算法的桥梁,也是模板类型,适配不同容器的遍历逻辑
💡 示例:STL模板的使用(vector容器+sort算法)
#include<iostream>#include<vector>#include<algorithm>// 包含sort算法usingnamespace std;intmain(){// vector<int>:类模板实例化,存储int类型 vector<int> nums ={5,2,9,1,5,6};// sort算法:函数模板,作用于vector容器sort(nums.begin(), nums.end());// 升序排序 cout <<"排序后的vector:";for(int num : nums){ cout << num <<" ";} cout << endl;// 输出:1 2 5 5 6 9// vector<string>:实例化存储string类型 vector<string> fruits ={"Apple","Banana","Orange","Grape"};sort(fruits.begin(), fruits.end());// 字符串按字典序排序 cout <<"排序后的string vector:";for(const string& fruit : fruits){ cout << fruit <<" ";} cout << endl;// 输出:Apple Banana Grape Orangereturn0;}✅ 核心启示:模板是STL的基础,掌握模板编程后,能更深入理解STL的设计思想,甚至自定义适配STL的容器或算法。
八、总结
- 模板是C++泛型编程的核心,分为函数模板和类模板,核心价值是“一次编写,多次复用”,兼顾类型安全和灵活性。
- 函数模板支持显式/隐式实例化、重载、特化,适用于创建通用函数(如加法、排序)。
- 类模板支持多参数、非类型参数、全特化、偏特化,适用于创建通用容器(如链表、数组)。
- 模板的编译机制为“实例化时编译”,需注意声明与定义的一致性,避免链接错误。
- 模板是STL的底层实现基础,掌握模板编程是深入学习STL和C++高级特性的关键。
通过本文学习,你应能独立编写函数模板和类模板,解决实际开发中的代码复用问题,并理解模板特化、偏特化等高级技术的应用场景。下一篇将深入探讨C++的异常处理机制,提升代码的健壮性和容错能力!