C++ 模板编程基础:泛型编程入门与实践

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++模板主要分为两类:

  1. 函数模板:用于创建通用函数,支持不同类型的参数输入
  2. 类模板:用于创建通用类(如容器、算法类),支持不同类型的成员变量和成员函数参数

✅ 核心优势:模板是C++泛型编程的基础,STL(标准模板库)的容器(vector、map)、算法(sort、find)均基于模板实现。

三、函数模板:通用函数的实现

3.1 函数模板的基本语法

函数模板的声明需使用template关键字,指定类型参数(或非类型参数),语法格式如下:

// 格式:template <模板参数列表> 返回值类型 函数名(参数列表) { 函数体 }template<typenameT>// T为类型参数(typename可替换为class,含义相同) 返回值类型 函数名(T 参数1, T 参数2,...){// 通用逻辑(与类型无关)}

💡 语法解析:

  • template <typename T>:模板声明,typename表示“后面的标识符是类型参数”,T是类型占位符(可自定义名称,如TypeElemType
  • 函数参数列表中使用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,推导规则如下:

  1. 实参类型必须一致(若函数参数均为T类型),否则推导失败
  2. 引用/const修饰会被保留或忽略(需注意类型匹配)
  3. 数组、函数会退化为指针类型(除非显式指定为引用)

💡 示例:类型推导细节

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 函数模板的重载

函数模板支持重载,可与普通函数或其他函数模板构成重载关系,调用时遵循“最匹配原则”:

  1. 普通函数优先于模板函数(若参数完全匹配)
  2. 模板函数的显式特化优先于通用模板
  3. 更具体的模板(如多参数模板)优先于更通用的模板

💡 示例:函数模板重载

#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 

⚠️ 注意事项:

  1. 特化模板必须与通用模板在同一作用域,且函数名、参数列表必须与通用模板一致
  2. 特化模板的返回值类型需与通用模板兼容
  3. 特化后,调用该类型时优先使用特化版本

四、类模板:通用类的实现

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 

⚠️ 非类型参数的限制:

  1. 非类型参数必须是编译期可确定的常量(如字面量、const变量、enum值)
  2. 支持的类型:整数类型(int、long)、枚举类型、指针类型、引用类型
  3. 不支持浮点数(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 

⚠️ 偏特化注意事项:

  1. 偏特化不能改变模板参数的数量,只能为部分参数指定具体类型
  2. 偏特化版本的模板参数列表需与通用模板兼容
  3. 若存在多个偏特化版本,编译器会选择最匹配的版本

五、模板的编译机制与常见问题

5.1 模板的编译机制

C++模板采用“实例化时编译”(Compile on Instantiation)机制,核心特点:

  1. 模板定义本身不会生成可执行代码,仅当实例化(指定具体类型)时,编译器才会生成对应类型的函数/类代码
  2. 模板的声明和定义需在同一翻译单元(.cpp文件)中可见,否则会导致链接错误(“未定义的引用”)

💡 编译流程解析:

  1. 编译模板定义文件时,编译器仅检查模板语法是否正确(如括号匹配、关键字使用),不生成代码
  2. 当其他文件调用模板(如add<int>(10,20)),编译器会生成int类型的add函数代码
  3. 若模板定义在.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)'}
解决方案:
  1. 将模板的声明和定义放在同一.h文件中(推荐,简单高效)
  2. 在模板定义文件中显式实例化所需类型:
// 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 实现思路

  1. 定义链表节点类模板ListNode<T>,存储T类型的元素和下一个节点的指针
  2. 定义链表类模板LinkedList<T>,包含头节点指针、链表长度等成员,实现核心操作
  3. 支持的操作:头插法、尾插法、按索引插入、按值删除、按索引查找、遍历打印等
  4. 为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++泛型编程的典范,其核心组件(容器、算法、迭代器)均基于模板实现:

  1. 容器:vector、list、map<K,V> 等均为类模板,支持任意兼容类型的元素存储
  2. 算法:sort、find、reverse 等均为函数模板,可作用于不同类型的容器
  3. 迭代器:作为容器与算法的桥梁,也是模板类型,适配不同容器的遍历逻辑

💡 示例: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的容器或算法。

八、总结

  1. 模板是C++泛型编程的核心,分为函数模板和类模板,核心价值是“一次编写,多次复用”,兼顾类型安全和灵活性。
  2. 函数模板支持显式/隐式实例化、重载、特化,适用于创建通用函数(如加法、排序)。
  3. 类模板支持多参数、非类型参数、全特化、偏特化,适用于创建通用容器(如链表、数组)。
  4. 模板的编译机制为“实例化时编译”,需注意声明与定义的一致性,避免链接错误。
  5. 模板是STL的底层实现基础,掌握模板编程是深入学习STL和C++高级特性的关键。

通过本文学习,你应能独立编写函数模板和类模板,解决实际开发中的代码复用问题,并理解模板特化、偏特化等高级技术的应用场景。下一篇将深入探讨C++的异常处理机制,提升代码的健壮性和容错能力!

Read more

零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体

零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体

零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体 灵珠平台简介 okid 自研 AI 开发平台,基于多模态大模型与轻量化架构,打造零门槛、全栈化 AI 开发体系。平台提供可视化编排、预置能力组件,支持原型到云端、端侧一站式敏捷部署,并深度适配 Rokid Glasses 智能眼镜,通过专属硬件接口与低功耗优化,实现 AI 应用高效端侧落地,助力开发者快速打造视觉识别、语音交互等穿戴式 AI 应用,拓展 AI + 物理世界的交互边界可视化编排工具,拖拽式快速搭建应用预置丰富能力组件库,涵盖对话引擎、视觉识别等核心模块支持从原型设计到云端、端侧的一站式敏捷部署提供设备专属适配接口,实现硬件深度协同搭载低功耗运行优化方案,保障端侧持久稳定运行 实战:搭建旅游类AR智能体 1、进入灵珠平台 登录灵珠平台后,你将看到简洁直观的工作台界面 点击创建智能体按钮,

By Ne0inhk
【读点论文】Metric3D v2: A Versatile Monocular Geometric Foundation Model for Zero-shot MD and SNE坐标系变换

【读点论文】Metric3D v2: A Versatile Monocular Geometric Foundation Model for Zero-shot MD and SNE坐标系变换

Metric3D v2: A Versatile Monocular Geometric Foundation Model for Zero-shot Metric Depth and Surface Normal Estimation Abstract * Metric3D v2是一个几何基础模型,用于从一幅图像中进行零样本深度和表面法线估计,这对度量3D恢复至关重要。虽然深度和法线在几何上是相关的,并且高度互补,但它们存在不同的挑战。最先进的(SoTA)单目深度方法通过学习仿射不变深度来实现零样本泛化,同时,由于缺乏大规模标记数据,SoTA法线估计方法的零样本性能有限。为了解决这些问题,我们提出了度量深度估计和表面法线估计的解决方案。对于度量深度估计,我们指出,零样本单视图模型的关键在于解决各种相机模型和大规模数据训练的度量模糊性。我们提出了一个规范的相机空间转换模块,它明确地解决了模糊性问题,可以毫不费力地插入到现有的单目模型中。 * 对于表面法向估计,我们提出了一个联合深度-法向优化模块,从度量深度中提取多样化的数据知识,使法向估计器能够超越法向标签进行学习。配备了这

By Ne0inhk
区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)

区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)

区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)(原命名为时间证明公式算法(TCC)) 本共识算法以「时间长河」为核心设计理念,通过时间节点服务器按固定最小时间间隔打包区块,构建不可篡改的历史数据链,兼顾区块链的金融属性与信用属性,所有优化机制形成完整闭环,无核心逻辑漏洞,具体总结如下: 一、核心机制(闭环无漏洞) 1. 节点准入与初始化:候选时间节点需先完成全链质押,首个时间节点由所有质押节点投票选举产生,彻底杜绝系统指定带来的初始中心化问题,实现去中心化初始化。 2. 时间节点推导与防作弊:下一任时间节点通过共同随机数算法从上一区块推导(输入参数:上一区块哈希、时间戳、固定数据顺序),推导规则公开可验证;时间节点需对数据顺序签名,任一节点发现作弊(篡改签名、操控随机数等),该节点立即失去时间节点资格并扣除全部质押。质押的核心目的是防止节点为持续获取区块打包奖励作弊,作弊损失远大于收益,确保共同随机数推导百分百不可作弊。 3. 节点容错机制:每个时间节点均配置一组合规质押节点构成的左侧顺邻节点队列(队列长度可随全网节点规

By Ne0inhk
《机器人实践开发①:Foxglove 开发环境完整搭建指南(含常见坑位) 》

《机器人实践开发①:Foxglove 开发环境完整搭建指南(含常见坑位) 》

导语: 在机器人项目中,调试工具往往比算法本身更耗时间。Foxglove 作为新一代机器人可视化平台,提供了强大的话题订阅、视频显示、3D 展示和日志分析能力。本篇从零开始,手把手带你完成 Foxglove 的环境搭建,包含依赖安装、连接配置以及常见踩坑点。 《机器人实践开发》系列文章索引 《机器人实践开发①:Foxglove 开发环境完整搭建指南(含常见坑位)》 《机器人实践开发②:Foxglove 嵌入式移植 + CMake 集成》 《机器人实践开发③:Foxglove可视化机器人的眼睛-视频》 《机器人实践开发④:Foxglove可视化机器人的耳朵-声音》 《机器人实践开发⑤:Foxglove可视化机器人的3D显示》 《机器人实践开发⑥:Foxglove可视化机器人传感器数据》 《机器人实践开发⑦:Foxglove可视化机器人的日志显示》 《机器人实践开发⑧:Foxglove可视化机器人的地图显示》 《机器人实践开发⑨:Foxglove可视化机器人的MyBag 数据回放》 foxglove 官网 Foxglove 是一个专为机器人团队打造的平台,用于收

By Ne0inhk