跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

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

综述由AI生成C++ 模板编程涵盖函数模板与类模板的基础语法、实例化机制及特化技术。文章通过加法函数、容器类及通用链表实战案例,演示了泛型编程如何提升代码复用性与类型安全性。重点解析了模板推导规则、编译时实例化原理及常见错误规避方法,为理解 STL 底层设计奠定基础。

暗影行者发布于 2026/3/16更新于 2026/4/276 浏览
C++ 模板编程基础:泛型编程入门与实践

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

一、学习目标与重点

  • 掌握模板的核心概念、分类(函数模板、类模板)及基本语法
  • 理解泛型编程的思想,能够独立编写函数模板和类模板
  • 掌握模板的实例化、特化、偏特化等关键技术
  • 解决模板使用中的常见问题(类型推导失败、编译错误等)
  • 结合实际场景运用模板提升代码复用性和灵活性
  • 了解模板与 STL 的关联,为后续 STL 学习奠定基础

核心重点:模板的语法规则、类型参数与非类型参数的使用、模板特化的应用场景、泛型编程的核心价值

二、模板与泛型编程概述

2.1 什么是泛型编程

泛型编程(Generic Programming)是一种代码复用技术,核心思想是'编写与类型无关的通用代码,在使用时再指定具体类型',实现'一次编写,多次复用'。

生活中的泛型类比:

  • 快递盒:同一个快递盒(通用容器)可装手机、书籍、衣物(不同类型数据),无需为每种物品单独设计盒子
  • 模板工具:如 3D 打印机模板,同一模板可打印不同材质(塑料、金属)的同一形状零件
2.2 为什么需要模板

在 C++ 中,若未使用模板,针对不同类型的相同逻辑需重复编写代码,导致冗余且难以维护:

// 重复代码:int 类型加法
int add_int(int a, int b) {
    return a + b;
}
// 重复代码:double 类型加法
double add_double(double a, double b) {
    return a + b;
}
// 重复代码:float 类型加法
float add_float(float a, float b) {
    return a + b;
}

模板的价值:用一套代码适配所有兼容逻辑的类型,减少冗余、提升可维护性,同时保证类型安全(编译时类型检查)。

2.3 模板的分类

C++ 模板主要分为两类:

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

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

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

3.1 函数模板的基本语法

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

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

语法解析:

  • template <typename T>:模板声明,typename 表示'后面的标识符是类型参数',T 是类型占位符(可自定义名称,如 Type、ElemType)
  • 函数参数列表中使用 T 作为类型,表明参数类型由调用时指定或推导
  • 函数体逻辑需与类型无关(如使用 + 运算符需确保传入类型支持该运算符)
3.2 函数模板的定义与调用

示例:实现通用加法函数模板

#include <iostream>
using namespace std;

// 函数模板:通用加法函数,支持任意支持 + 运算符的类型
template<typename T> // T 为类型参数,代表任意类型
T add(T a, T b) {
    cout << "模板函数调用,类型为:" << typeid(T).name() << endl; // 打印类型名称
    return a + b;
}

int main() {
    // 1. 显式指定类型参数(推荐,可读性强)
    int num1 = add<int>(10, 20);
    cout << "int 类型加法:10 + 20 = " << num1 << endl; // 30
    
    double 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++!
    
    return 0;
}

运行结果:

模板函数调用,类型为: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<typename T>
void print_type(T param) {
    cout << "参数类型:" << typeid(T).name() << endl;
}

int main() {
    int a = 10;
    const int b = 20;
    int& c = a;
    int arr[5] = {1, 2, 3, 4, 5};
    void func(int) {}
    
    print_type(a); // 实参为 int,推导 T=int
    print_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<typename T>
    void print_ref_type(T& param) {}
    print_ref_type(b); // 推导 T=const int(保留 const)
    print_ref_type(c); // 推导 T=int(保留引用,param 为 int&)
    
    return 0;
}

警告:若实参类型不一致且未显式指定类型,会导致编译错误:

// 错误:实参类型分别为 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>
using namespace std;

// 普通函数:处理 int 类型加法
int add(int a, int b) {
    cout << "普通函数(int)调用:";
    return a + b;
}

// 函数模板:通用加法
template<typename T> T add(T a, T b) {
    cout << "模板函数(" << typeid(T).name() << ")调用:";
    return a + b;
}

// 函数模板重载:支持两个不同类型的参数
template<typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
    // 尾置返回类型,推导返回值类型
    cout << "模板函数(" << typeid(T1).name() << "," << typeid(T2).name() << ")调用:";
    return a + b;
}

int main() {
    // 调用普通函数(完全匹配 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.14
    
    return 0;
}

运行结果:

普通函数(int)调用:30 
模板函数(double)调用:6 
模板函数(int,double)调用:13.14 
3.5 函数模板的特化

当通用模板无法满足特定类型的需求(如特殊逻辑、运算符不支持)时,可对模板进行特化(Specialization),为特定类型提供专属实现。

特化的语法格式:
template<> // 空模板参数列表,表示特化
返回值类型 函数名<特化类型>(特化类型 参数 1, 特化类型 参数 2,...) {
    // 专属逻辑
}

示例:函数模板特化(处理 string 类型的特殊加法)

#include <iostream>
#include <string>
using namespace std;

// 通用函数模板:加法
template<typename T> T add(T a, T b) {
    cout << "通用模板:";
    return a + b;
}

// 特化:为 string 类型提供专属实现(拼接字符串时添加空格)
template<> string add<string>(string a, string b) {
    cout << "string 特化模板:";
    return a + " " + b; // 特殊逻辑:拼接时添加空格
}

int main() {
    cout << add(10, 20) << endl; // 通用模板:30
    cout << add(3.14, 2.86) << endl; // 通用模板:6
    cout << add(string("Hello"), string("World")) << endl; // string 特化模板:Hello World
    
    return 0;
}

运行结果:

通用模板:30 
通用模板:6 
string 特化模板:Hello World 

注意事项:

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

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

4.1 类模板的基本语法

类模板用于创建通用类,支持不同类型的成员变量和成员函数参数,语法格式如下:

template<typename T> // 模板参数列表(可多个类型参数)
class 类名 {
public:
    // 成员变量(类型为 T)
    T 成员变量;
    
    // 构造函数
    类名 (T 参数): 成员变量 (参数) {}
    
    // 成员函数(参数/返回值类型可使用 T)
    T get_value() const {
        return 成员变量;
    }
    
    void set_value(T value) {
        成员变量 = value;
    }
};

语法解析:

  • 类模板声明需在 class 关键字前加 template <模板参数列表>
  • 类内部可使用类型参数 T 定义成员变量、成员函数参数或返回值
  • 类模板的成员函数若在类外定义,需再次声明模板参数列表
4.2 类模板的定义与实例化

类模板不能直接使用,需实例化(指定具体类型)后才能创建对象,实例化分为显式实例化和隐式实例化。

示例:类模板的定义与实例化

#include <iostream>
using namespace std;

// 类模板:通用容器类(存储单个元素)
template<typename T>
class Container {
private:
    T data; // 成员变量,类型为 T
public:
    // 构造函数
    Container(T value): data(value) {}
    
    // 成员函数:类内定义
    T get_data() const {
        return data;
    }
    
    // 成员函数:类外定义(需再次声明模板参数)
    void set_data(T value);
};

// 类外定义成员函数:必须加 template <typename T>
template<typename T>
void Container<T>::set_data(T value) {
    data = value;
}

int main() {
    // 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++
    
    return 0;
}

运行结果:

int 容器初始值:100 int 容器修改后:200 
double 容器值:3.14 
string 容器值:Hello C++ 
4.3 类模板的多参数模板

类模板支持多个类型参数(或非类型参数),参数之间用逗号分隔:

// 多类型参数:T 为元素类型,Alloc 为分配器类型(默认值为 void)
template<typename T, typename Alloc = void>
class MyVector {
    // 类实现
};

// 非类型参数:N 为常量整数(必须是编译期可确定的值)
template<typename T, int N>
class Array {
private:
    T data[N]; // 固定大小的数组,N 为模板参数
public:
    int size() const {
        return N;
    }
    T& operator[](int index) {
        return data[index];
    }
};

示例:带非类型参数的类模板(固定大小数组)

#include <iostream>
using namespace std;

// 类模板:固定大小数组(N 为非类型参数,编译期确定大小)
template<typename T, int N>
class FixedArray {
private:
    T data[N]; // 数组大小为 N(编译期固定)
public:
    // 构造函数:初始化所有元素为默认值
    FixedArray() {
        for (int i = 0; i < N; ++i) {
            data[i] = T(); // T() 为默认构造(如 int 默认 0,string 默认空串)
        }
    }
    
    // 赋值操作:设置指定索引的元素
    void set(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];
        }
        return T(); // 索引越界返回默认值
    }
    
    // 获取数组大小
    int size() const {
        return N;
    }
    
    // 打印数组
    void print() const {
        for (int i = 0; i < N; ++i) {
            cout << data[i] << " ";
        }
        cout << endl;
    }
};

int main() {
    // 实例化: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 (后两个为默认空串)
    
    return 0;
}

运行结果:

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>
using namespace std;

// 通用类模板:打印元素类型和值
template<typename T>
class Printer {
public:
    void print(T value) {
        cout << "通用模板 - 类型:" << typeid(T).name() << ",值:" << value << endl;
    }
};

// 全特化:为 string 类型提供专属实现(美化输出)
template<>
class Printer<string> {
public:
    void print(string value) {
        cout << "string 特化模板 - 字符串:\"" << value << "\"" << endl;
    }
};

// 全特化:为 int 类型提供专属实现(添加单位)
template<>
class Printer<int> {
public:
    void print(int value) {
        cout << "int 特化模板 - 整数:" << value << "(单位:个)" << endl;
    }
};

int main() {
    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(单位:个)
    
    return 0;
}

运行结果:

通用模板 - 类型:double,值:3.14 
string 特化模板 - 字符串:"Hello C++" 
int 特化模板 - 整数:100(单位:个) 
4.4.2 类模板的偏特化

偏特化是为部分模板参数指定类型,保留其他参数的通用性,适用于多参数模板。语法格式:

template<保留的模板参数>
class 类名<保留参数,指定的参数> {
    // 偏特化实现
};

示例:类模板偏特化

#include <iostream>
using namespace std;

// 多参数类模板:T 为元素类型,U 为附加类型
template<typename T, typename U>
class Pair {
public:
    Pair(T first, U second): first_val(first), second_val(second) {}
    
    void display() {
        cout << "通用模板 - 第一个值(" << typeid(T).name() << "):" << first_val << ",第二个值(" << typeid(U).name() << "):" << second_val << endl;
    }
private:
    T first_val;
    U second_val;
};

// 偏特化 1:当第二个参数 U 为 int 时的专属实现
template<typename T>
class Pair<T, int> {
public:
    Pair(T first, int second): first_val(first), second_val(second) {}
    
    void display() {
        cout << "偏特化(U=int) - 第一个值:" << first_val << ",第二个值(整数):" << second_val << "(已乘以 2:" << second_val * 2 << ")" << endl;
    }
private:
    T first_val;
    int second_val;
};

// 偏特化 2:当两个参数均为指针类型时的专属实现
template<typename T, typename U>
class Pair<T*, U*> {
    // 两个参数均为指针类型
public:
    Pair(T* first, U* second): first_ptr(first), second_ptr(second) {}
    
    void display() {
        cout << "偏特化(T*+U*) - 第一个指针地址:" << first_ptr << ",值:" << *first_ptr << ";第二个指针地址:" << second_ptr << ",值:" << *second_ptr << endl;
    }
private:
    T* first_ptr;
    U* second_ptr;
};

int main() {
    // 通用模板: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();
    
    return 0;
}

运行结果:

通用模板 - 第一个值(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<typename T> T add(T a, T b);

// add.cpp(模板定义)
template<typename T> T add(T a, T b) {
    return a + b;
}

// main.cpp(调用模板)
#include "add.h"
int main() {
    add<int>(10, 20); // 链接错误:undefined reference to `int add<int>(int, int)`
}

解决方案:

  1. 将模板的声明和定义放在同一.h 文件中(推荐,简单高效)
  2. 在模板定义文件中显式实例化所需类型:
// add.cpp
template<typename T> T add(T a, T b) {
    return a + b;
}

// 显式实例化 int 和 double 类型
template int add<int>(int, int);
template double add<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)
// 自定义类
class Point {
public:
    int x, y;
    Point(int x = 0, int y = 0): x(x), y(y) {}
    
    // 重载 + 运算符,支持模板中的加法操作
    Point operator+(const Point& other) {
        return Point(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 常量(编译期确定)
const int N = 5;
FixedArray<int, N> arr;
错误 4:模板特化与通用模板不匹配

原因:特化模板的函数名、参数列表与通用模板不一致。
规避方案:

  • 特化模板的参数列表、返回值类型需与通用模板严格一致
  • 确保特化模板与通用模板在同一作用域
// 通用模板
template<typename T> T add(T a, T b) {
    return a + b;
}

// 错误:特化模板参数列表与通用模板不一致(多了一个参数)
template<> int add<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>
using namespace std;

// 1. 链表节点类模板
template<typename T>
class ListNode {
public:
    T data; // 节点数据(通用类型 T)
    ListNode<T>* next; // 下一个节点指针
    
    // 构造函数
    ListNode(T value): data(value), next(nullptr) {}
};

// 2. 链表类模板
template<typename T>
class LinkedList {
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;
    }
    
    // 头插法:在链表头部插入元素
    void push_front(T value) {
        ListNode<T>* new_node = new ListNode<T>(value);
        new_node->next = head;
        head = new_node;
        length++;
    }
    
    // 尾插法:在链表尾部插入元素
    void push_back(T value) {
        ListNode<T>* new_node = new ListNode<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 开始)
    bool insert(int index, T value) {
        if (index < 0 || index > length) {
            cout << "插入失败:索引" << index << "非法!" << endl;
            return false;
        }
        if (index == 0) {
            // 索引 0,等价于头插法
            push_front(value);
            return true;
        }
        // 找到索引前一个节点
        ListNode<T>* curr = head;
        for (int i = 0; i < index - 1; ++i) {
            curr = curr->next;
        }
        ListNode<T>* new_node = new ListNode<T>(value);
        new_node->next = curr->next;
        curr->next = new_node;
        length++;
        return true;
    }
    
    // 按值删除第一个匹配的元素
    bool remove(T value) {
        if (head == nullptr) {
            cout << "删除失败:链表为空!" << endl;
            return false;
        }
        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;
            return false;
        }
        
        // 删除节点
        if (prev == nullptr) {
            // 删除头节点
            head = curr->next;
        } else {
            // 删除中间/尾节点
            prev->next = curr->next;
        }
        delete curr;
        length--;
        return true;
    }
    
    // 按索引查找元素
    T get(int index) const {
        if (index < 0 || index >= length) {
            cout << "查找失败:索引" << index << "非法!" << endl;
            return T(); // 返回默认值
        }
        ListNode<T>* curr = head;
        for (int i = 0; i < index; ++i) {
            curr = curr->next;
        }
        return curr->data;
    }
    
    // 获取链表长度
    int size() const {
        return length;
    }
    
    // 遍历打印链表(通用版本)
    void print() 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<>
void LinkedList<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;
}

// 测试代码
int main() {
    // 测试 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 -> nullptr
    
    return 0;
}
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 算法
using namespace std;

int main() {
    // 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 Orange
    
    return 0;
}

核心启示:模板是 STL 的基础,掌握模板编程后,能更深入理解 STL 的设计思想,甚至自定义适配 STL 的容器或算法。

八、总结

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

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

目录

  1. C++ 模板编程基础:泛型编程入门与实践
  2. 一、学习目标与重点
  3. 二、模板与泛型编程概述
  4. 2.1 什么是泛型编程
  5. 2.2 为什么需要模板
  6. 2.3 模板的分类
  7. 三、函数模板:通用函数的实现
  8. 3.1 函数模板的基本语法
  9. 3.2 函数模板的定义与调用
  10. 3.3 函数模板的类型推导规则
  11. 3.4 函数模板的重载
  12. 3.5 函数模板的特化
  13. 特化的语法格式:
  14. 四、类模板:通用类的实现
  15. 4.1 类模板的基本语法
  16. 4.2 类模板的定义与实例化
  17. 4.3 类模板的多参数模板
  18. 4.4 类模板的特化与偏特化
  19. 4.4.1 类模板的全特化
  20. 4.4.2 类模板的偏特化
  21. 五、模板的编译机制与常见问题
  22. 5.1 模板的编译机制
  23. 5.2 模板使用的常见错误与规避
  24. 错误 1:类型推导失败
  25. 错误 2:模板参数不支持特定操作
  26. 错误 3:非类型参数不是编译期常量
  27. 错误 4:模板特化与通用模板不匹配
  28. 六、实战案例:基于模板实现通用链表
  29. 6.1 问题描述
  30. 6.2 实现思路
  31. 6.3 代码实现
  32. 6.4 运行结果
  33. 七、模板与 STL 的关联
  34. 八、总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 非技术人员如何利用 AI 开发效率工具与智能体应用
  • LangChain 基本概念与链式应用解析
  • Layui 框架下解决 Unity WebGL 渲染在 Tab 切换时黑屏问题
  • C++ STL 容器适配器:优先队列 priority_queue 原理与实现
  • Windows 部署 OpenClaw:基于 WSL2 与 Docker 环境搭建
  • Mac Mini M4 本地部署大模型:Ollama 与 Llama 实战指南
  • LeetCode 744. 寻找比目标字母大的最小字母(二分查找)
  • 无人机智能 AI 巡检平台:航线规划与 YOLO 视频算法应用
  • 申请 Hugging Face 访问令牌:以 Meta-Llama-3.1-8B-Instruct 为例
  • Java 全栈工程师面试实录:从基础到复杂场景
  • OpenClaw 实战:为什么它是执行型 AI 助理而不是 ChatGPT
  • Kafka 核心架构与分布式存储深度解析
  • 银行个人贷款违约风险预测:基于逻辑回归模型
  • Dify 工作流发布为 MCP Server 实战指南
  • VS Code 中 GitHub Copilot 高效使用指南:安装与配置
  • Qt QWebChannel 前后端通信原理与示例
  • 企业微信群机器人 Webhook 配置与消息发送流程
  • Linux 系统下 Vim 编辑器基础操作指南
  • Python 结合 Godot 的游戏开发完整流程指南
  • OpenClaw Web 控制台使用全解析——可视化配置与监控

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online