函数模板与类模板——泛型编程

C++ 模板(泛型编程)

C++ 模板是实现泛型编程的核心机制,通过将数据类型参数化,让一段代码适配多种数据类型,避免重复编写同逻辑不同类型的代码,大幅提升代码复用性。

函数模板

核心概念

  • 目的 :将函数的参数类型、返回值类型 "参数化",让同一个函数逻辑处理不同数据类型(如加法函数同时支持intdoublestd::string)。
  • 本质:模板本身不是可执行代码,编译器在调用模板时,会根据传入的具体类型 "实例化" 出对应函数代码(模板实例化),最终运行的是实例化后的具体函数。
  • 关键术语
    • 模板声明:template <typename T>template <class T>(两者等价,typename更直观),声明 "类型占位符";
    • 模板参数:T(可自定义名称,如TypeT1)是类型占位符,代表任意数据类型,同一模板中参数名必须唯一;
    • 模板函数:模板实例化后生成的具体函数(如add<int>是针对int类型的模板函数)。

基本语法

模板定义

cpp 复制代码
/**
 * 通用加法函数模板
 * @brief 实现任意支持"+"运算符类型的加法操作
 * @tparam T 类型占位符,代表任意支持"+"运算符的类型(如int、double、std::string)
 * @param a 第一个加数,类型为T
 * @param b 第二个加数,类型为T(自动推导时需与a类型严格一致)
 * @return T 两个数相加的结果,类型与参数一致
 * @note 模板仅声明类型规则,编译器调用时才生成具体类型的函数代码
 */
template <typename T>
T add(T a, T b) {
    return a + b;
}

调用方式

调用方式 示例 详细说明
自动类型推导 add(10, 20); 编译器根据实参类型(int)推导T=int,生成int add(int, int)并调用
显式指定类型 add<double>(3.14, 2.5); 强制生成double add(double, double),无视实参的自动推导结果
空模板参数列表 add<>(10, 20); 强制调用模板版本(即使存在同名普通函数),编译器仍推导T=int

显式指定类型规则

  • 精确匹配优先:显式指定的类型与模板参数完全一致的版本优先选择;
  • 特化模板优先:针对指定类型的特化模板(全特化 / 偏特化)优先于通用模板;
  • 参数数量匹配:显式指定的类型数量需与模板参数数量一致(除非模板有默认参数);
  • 类型转换限制:显式指定类型后,实参允许隐式转换为指定类型(如add<double>(10, 3.14)intdouble);自动推导时不支持隐式转换;
  • 重载决议:按 "最具体" 原则选择模板(SFINAE 原则:替换失败不是错误,仅跳过不匹配的模板)。

函数模板与普通函数的区别

对比项 普通函数 函数模板
代码生成时机 编译阶段直接生成对应代码 调用时根据实参 / 指定类型实例化生成代码
类型支持 仅支持定义时的固定类型(如int add(int, int)仅处理int 支持任意满足操作要求的类型(如add需类型支持+运算符)
隐式类型转换 支持(如add(3, 4.5)4.5隐式转int 自动推导:不支持(实参类型需严格匹配); 显式指定:允许实参隐式转指定类型
性能 无额外生成开销,仅一份代码 不同类型生成不同代码,可能导致 "代码膨胀"(现代编译器会优化重复逻辑)
匹配优先级 同名场景下优先级最高 低于普通函数,高于模板特化版本

模板参数规则

多参数模板

一个模板可声明多个类型参数,适配不同类型的参数组合:

cpp 复制代码
/**
 * 通用乘法函数模板(多参数)
 * @brief 实现两种不同类型数据的乘法操作
 * @tparam T1 第一个乘数的类型占位符
 * @tparam T2 第二个乘数的类型占位符
 * @param a 第一个乘数,类型为T1
 * @param b 第二个乘数,类型为T2
 * @return auto 返回值类型由`a*b`的结果类型推导(C++14起支持;C++11需写:auto multiply(T1 a, T2 b) -> decltype(a*b))
 * @note 多参数模板的类型可独立推导,无需一致
 */
template <typename T1, typename T2>
auto multiply(T1 a, T2 b) {
    return a * b;
}

// 调用示例:T1=int、T2=double,返回值为double(int*double的结果类型)
multiply(3, 4.5);

默认模板参数

可为模板参数指定默认类型,调用时未指定 / 推导类型则使用默认值:

cpp 复制代码
/**
 * 通用自增函数模板(带默认参数)
 * @brief 实现任意类型的自增操作(+1)
 * @tparam T 类型占位符,默认类型为int
 * @param value 待自增的值,类型为T
 * @return T 自增后的结果,类型与参数一致
 * @note 空模板列表<>可覆盖默认类型
 */
template <typename T = int>
T increment(T value) {
    return value + 1;
}

// 调用示例
increment(5);      // 未指定类型,使用默认T=int,返回6
increment<>(5.0);  // 空模板列表强制推导,T=double,返回6.0
increment<double>(5); // 显式指定T=double,5隐式转为double,返回6.0

模板重载与优先级

模板重载指定义多个同名模板(或普通函数),编译器按以下优先级选择:

  • 普通函数 > 模板特化版本 > 通用模板版本;
  • 更 "具体" 的模板优先(如T*模板优先于T模板);
  • 空模板列表<> 可强制调用模板版本(跳过普通函数)。
cpp 复制代码
#include <iostream>
using namespace std;

/**
 * 普通打印函数(int专用)
 * @brief 处理int类型的打印,优先级最高
 * @param x 待打印的int值
 * @return void
 */
void print(int x) {
    cout << "普通函数: " << x << endl;
}

/**
 * 通用打印模板函数
 * @brief 处理任意类型的打印,优先级低于普通函数和特化模板
 * @tparam T 类型占位符
 * @param x 待打印的值,类型为T
 * @return void
 */
template <typename T>
void print(T x) {
    cout << "通用模板函数: " << x << endl;
}

/**
 * 打印模板的int特化版本
 * @brief 针对int类型的优化打印,优先级高于通用模板、低于普通函数
 * @param x 待打印的int值
 * @return void
 * @note 函数模板仅支持全特化,不支持偏特化
 */
template <>
void print<int>(int x) {
    cout << "int特化模板函数: " << x << endl;
}

// 调用测试
int main() {
    print(10);          // 匹配普通函数 → 输出:普通函数: 10
    print<>(10);        // 强制调用模板 → 匹配int特化版本 → 输出:int特化模板函数: 10
    print(3.14);        // 无普通函数匹配 → 调用通用模板 → 输出:通用模板函数: 3.14
    return 0;
}

模板特例化

为特定类型提供定制化实现(优化逻辑 / 适配特殊类型),分为全特化(所有参数指定具体类型)和偏特化(仅指定部分参数,仅类模板支持)。

函数模板全特化

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

/**
 * 通用打印模板函数
 * @tparam T 任意类型占位符
 * @param s 待打印的值
 * @return void
 */
template <typename T>
void print(T s) {
    cout << "通用模板: " << s << endl;
}

/**
 * 打印模板的std::string全特化版本
 * @brief 为字符串类型定制打印逻辑
 * @param s 待打印的字符串
 * @return void
 * @note 特化模板需与通用模板的函数签名完全匹配
 */
template <>
void print<std::string>(std::string s) {
    cout << "字符串特化: " << s << endl;
}

int main() {
    print(std::string("hello")); // 显式转string,匹配特化版本 → 输出:字符串特化: hello
    print(100);                  // 匹配通用模板 → 输出:通用模板: 100
    return 0;
}

类模板偏特化(函数模板不支持)

为模板的部分参数 / 类型修饰(如指针、引用)定制实现:

cpp 复制代码
/**
 * 通用容器类模板
 * @tparam T 容器存储的类型
 */
template <typename T>
class Box {
public:
    T content;
    void show() { cout << "通用Box: " << content << endl; }
};

/**
 * Box类模板的指针类型偏特化
 * @brief 为存储指针的Box定制实现(解引用打印)
 * @tparam T 指针指向的类型
 */
template <typename T>
class Box<T*> {
public:
    T* content;
    void show() { cout << "指针Box: " << *content << endl; } // 解引用打印
};

// 调用示例
int main() {
    Box<int> intBox;
    intBox.content = 42;
    intBox.show(); // 输出:通用Box: 42

    int num = 100;
    Box<int*> ptrBox;
    ptrBox.content = &num;
    ptrBox.show(); // 输出:指针Box: 100
    return 0;
}

类模板

类模板将类的成员变量、成员函数类型参数化,实例化后生成适配不同类型的 "模板类"。

类模板定义与使用

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

/**
 * 通用容器类模板Box
 * @brief 存储任意类型的单个数据,提供设置/获取接口
 * @tparam T 容器存储的数据类型
 */
template <class T>
class Box {
private:
    T content; // 成员变量类型参数化
public:
    /**
     * 设置容器内容
     * @param val 要存储的值,类型为T
     * @return void
     */
    void set(T val) { 
        content = val; 
    }

    /**
     * 获取容器内容
     * @return T 存储的内容,类型为T
     */
    T get() { 
        return content; 
    }
};

// 类模板使用:必须显式指定模板参数(无法自动推导)
int main() {
    Box<int> intBox;    // 实例化int类型的Box类(模板类)
    intBox.set(42);     
    cout << intBox.get() << endl; // 输出42

    Box<string> strBox; // 实例化string类型的Box类
    strBox.set("hello");
    cout << strBox.get() << endl; // 输出hello
    return 0;
}

类模板作为函数参数

指定具体类型(最局限)

仅接受某一具体实例化的模板类:

cpp 复制代码
/**
 * 打印int类型Box的内容
 * @brief 仅处理Box<int>类型的对象
 * @param box Box<int>类型的对象引用
 * @return void
 */
void printBox(Box<int>& box) {
    cout << box.get() << endl;
}

// 调用:仅能传入Box<int>
Box<int> ib;
ib.set(10);
printBox(ib); // 正确
// Box<double> db; printBox(db); // 错误:类型不匹配

参数模板化(适配任意 Box 实例)

函数模板接收任意类型的 Box 类:

cpp 复制代码
/**
 * 打印任意类型Box的内容
 * @brief 处理Box<T>类型的任意实例
 * @tparam T Box的模板参数类型
 * @param box Box<T>类型的对象引用
 * @return void
 */
template <class T>
void printBoxTemplate(Box<T>& box) {
    cout << box.get() << endl;
}

// 调用:支持任意Box实例
Box<int> ib; ib.set(10);
Box<double> db; db.set(3.14);
printBoxTemplate(ib); // 正确
printBoxTemplate(db); // 正确

整个类模板化(适配任意模板类)

函数模板接收任意带get()方法的模板类:

cpp 复制代码
/**
 * 打印任意带get()方法的模板类内容
 * @brief 处理任意有get()成员函数的类对象
 * @tparam T 任意类类型(需有get()方法)
 * @param boxObj T类型的对象引用
 * @return void
 */
template <class T>
void printBoxFull(T& boxObj) {
    cout << boxObj.get() << endl;
}

// 调用:支持Box、自定义模板类等
Box<string> sb; sb.set("test");
printBoxFull(sb); // 正确

// 自定义模板类也可适配
template <typename U>
class Bag {
public:
    U val;
    U get() { return val; }
};
Bag<double> bg; bg.val = 5.5;
printBoxFull(bg); // 正确

类模板的静态成员

类模板的静态成员属于实例化后的每个模板类,不同类型的模板类拥有独立的静态成员,互不共享:

cpp 复制代码
/**
 * 带静态成员的类模板
 * @brief 演示类模板静态成员的独立性
 * @tparam T 任意类型
 */
template <class T>
class MyClass {
public:
    static T count; // 静态成员,类型参数化
};

/**
 * 类模板静态成员的类外初始化
 * @brief 必须通过模板参数限定,为每个实例化类初始化静态成员
 */
template <class T>
T MyClass<T>::count = 0; // 初始值为0

// 使用示例
int main() {
    MyClass<int>::count++;    // int版本count → 1
    MyClass<double>::count++; // double版本count → 1
    MyClass<int>::count++;    // int版本count → 2

    cout << MyClass<int>::count << " " << MyClass<double>::count << endl; // 输出:2 1
    return 0;
}

关键概念对比

概念 详细说明
函数模板 vs 类模板 函数模板:参数化函数类型,实例化生成具体函数; 类模板:参数化类类型,实例化生成具体类
模板函数 函数模板实例化后的具体函数(如add<int>
模板类 类模板实例化后的具体类(如Box<int>
类模板静态成员 每个模板类(如MyClass<int>/MyClass<double>)拥有独立静态成员,互不共享
全特化 vs 偏特化 全特化:为所有模板参数指定具体类型(函数 / 类模板均支持); 偏特化:仅指定部分参数(仅类模板支持)

模板核心机制与注意事项

两次编译规则

模板编译分两个阶段,确保语法和类型双重检查:

  • 第一阶段(模板定义阶段):检查语法正确性(如括号、分号),不检查类型相关逻辑(如a + b+是否支持);
  • 第二阶段(实例化阶段):生成具体类型代码时,检查该类型是否支持模板中的操作(如自定义类未重载+则编译失败)。

类型参数限制

  • 自动推导时:模板参数需与实参类型严格匹配,不支持隐式转换;显式指定类型时,实参可隐式转换为指定类型:

    cpp 复制代码
    add(10, 3.14);          // 错误:int和double推导T冲突
    add<double>(10, 3.14);  // 正确:10隐式转double
  • 操作限制:模板中的操作(如+、成员调用)需适配实例化类型,否则编译失败:

    cpp 复制代码
    struct MyStruct {};
    add(MyStruct{}, MyStruct{}); // 错误:MyStruct未重载+运算符

代码膨胀问题

不同类型的模板实例会生成独立代码,可能导致可执行文件体积变大。优化方式:

  • 复用相同类型的模板实例;
  • 依赖编译器对重复逻辑的合并优化;
  • 对高频类型提前实例化模板。

模板声明与定义位置

模板的声明和定义通常需放在同一文件(如头文件)------ 实例化时需要完整的模板定义(确保模板定义、实例化请求在同一个编译单元 ),若定义放在.cpp文件,编译器可能找不到定义导致链接错误。

示例:链表模板(头文件实现)

链表是经典的通用容器,适合用类模板实现(支持任意类型存储)。以下是完整的单向链表模板,包含核心操作 + 详细注释,符合模板 "声明 + 定义放头文件" 的最佳实践:

头文件 MyLink.h

cpp 复制代码
#ifndef MY_LINK_H
#define MY_LINK_H
#include <iostream>
#include <stdexcept> // 异常处理
#include <string>    // 支持string类型示例
/**
 * 通用单向链表类模板
 * @brief 实现任意类型的单向链表,支持头插、尾插、删除、遍历、清空等核心操作
 * @tparam T1 链表存储的元素类型(支持int、double、string等任意可拷贝/打印的类型)
 */
template <typename T1>
class MyLink {
private:
    // 内部节点结构体重命名为Node,避免与外部类冲突
    struct Node {
        T1 data;       // 节点存储的数据
        Node* next;    // 指向下一个节点的指针(核心修正:值类型→指针类型)

        /**
         * 节点构造函数
         * @param val 节点初始化数据
         * @param nxt 下一个节点的指针(默认nullptr)
         */
        Node(const T1& val, Node* nxt = nullptr) : data(val), next(nxt) {}
    };

    Node* head; // 链表头节点指针(空链表时为nullptr)
    size_t len; // 链表长度(避免遍历统计,提升效率)

public:
    /**
     * 链表构造函数
     * @brief 初始化空链表(头节点=nullptr,长度=0)
     */
    MyLink();

    /**
     * 链表析构函数
     * @brief 释放所有节点内存,避免内存泄漏
     */
    ~MyLink();

    /**
     * 尾插法添加元素
     * @brief 将元素添加到链表末尾
     * @param val 要添加的元素(const引用避免拷贝开销)
     * @return void
     */
    void pushBack(const T1& val);

    /**
     * 头插法添加元素
     * @brief 将元素添加到链表头部(效率O(1))
     * @param val 要添加的元素
     * @return void
     */
    void pushFront(const T1& val);

    /**
     * 删除第一个匹配的元素
     * @brief 找到第一个等于val的节点并释放内存
     * @param val 要删除的元素值
     * @return bool 删除成功返回true,元素不存在返回false
     */
    bool remove(const T1& val);

    /**
     * 遍历打印链表
     * @brief 从头部到尾部打印所有元素,空链表提示"空链表"
     * @return void
     */
    void print() const;

    /**
     * 获取链表长度
     * @return size_t 链表节点个数(无符号整数)
     */
    size_t size() const;

    /**
     * 清空链表
     * @brief 释放所有节点内存,恢复为空链表
     * @return void
     */
    void clear();

    /**
     * 获取指定索引的元素
     * @brief 支持随机访问(需遍历,效率O(n))
     * @param idx 元素索引(从0开始)
     * @return T1& 元素的引用(支持修改)
     * @throw std::out_of_range 索引越界时抛出异常
     */
    T1& at(size_t idx);
};

// ========== 类模板成员函数定义(必须在头文件中) ==========
template <typename T1>
MyLink<T1>::MyLink() : head(nullptr), len(0) {}

template <typename T1>
MyLink<T1>::~MyLink() {
    clear(); // 复用clear()释放所有节点
}

template <typename T1>
void MyLink<T1>::pushBack(const T1& val) {
    Node* newNode = new Node(val); // 创建新节点
    if (head == nullptr) { // 空链表:新节点作为头节点
        head = newNode;
    } else { // 非空链表:遍历到尾部
        Node* cur = head;
        while (cur->next != nullptr) {
            cur = cur->next;
        }
        cur->next = newNode;
    }
    len++; // 长度+1
}

template <typename T1>
void MyLink<T1>::pushFront(const T1& val) {
    // 新节点的next指向原头节点,再更新头节点
    head = new Node(val, head);
    len++;
}

template <typename T1>
bool MyLink<T1>::remove(const T1& val) {
    if (head == nullptr) return false; // 空链表直接返回

    // 情况1:删除头节点
    if (head->data == val) {
        Node* temp = head;
        head = head->next;
        delete temp;
        len--;
        return true;
    }

    // 情况2:删除中间/尾部节点
    Node* cur = head;
    while (cur->next != nullptr && cur->next->data != val) {
        cur = cur->next;
    }
    if (cur->next == nullptr) return false; // 未找到元素

    // 找到匹配节点,释放内存
    Node* temp = cur->next;
    cur->next = cur->next->next;
    delete temp;
    len--;
    return true;
}

template <typename T1>
void MyLink<T1>::print() const {
    if (head == nullptr) {
        std::cout << "链表为空" << std::endl;
        return;
    }
    Node* cur = head;
    std::cout << "链表元素: ";
    while (cur != nullptr) {
        std::cout << cur->data << " ";
        cur = cur->next;
    }
    std::cout << std::endl;
}

template <typename T1>
size_t MyLink<T1>::size() const {
    return len;
}

template <typename T1>
void MyLink<T1>::clear() {
    Node* cur = head;
    while (cur != nullptr) {
        Node* temp = cur;
        cur = cur->next;
        delete temp; // 逐个释放节点
    }
    head = nullptr; // 恢复空链表状态
    len = 0;
}

template <typename T1>
T1& MyLink<T1>::at(size_t idx) {
    if (idx >= len) {
        throw std::out_of_range("MyLink: 索引越界");
    }
    Node* cur = head;
    for (size_t i = 0; i < idx; i++) {
        cur = cur->next;
    }
    return cur->data;
}

#endif // MY_LINK_H

使用示例(main.cpp)

cpp 复制代码
#include <iostream>
#include "MyLink.h"
#include <string>
int main() {
    // ========== 示例1:int类型链表 ==========
    MyLink<int> intLink;
    intLink.pushBack(10);   // 尾插10
    intLink.pushBack(20);   // 尾插20
    intLink.pushFront(5);   // 头插5
    intLink.print();        // 输出:链表元素: 5 10 20
    std::cout << "链表长度: " << intLink.size() << std::endl; // 输出:3

    intLink.remove(10);     // 删除10
    intLink.print();        // 输出:链表元素: 5 20
    std::cout << "索引0的元素: " << intLink.at(0) << std::endl; // 输出:5

    // ========== 示例2:string类型链表 ==========
    MyLink<std::string> strLink;
    strLink.pushBack("hello");
    strLink.pushBack("template");
    strLink.print();        // 输出:链表元素: hello template

    // ========== 清空链表 ==========
    intLink.clear();
    intLink.print();        // 输出:链表为空

    return 0;
}

编译运行说明

  • 编译命令(GCC/Clang):

    bash 复制代码
    g++ main.cpp -o MyLinkDemo
    ./MyLinkDemo
  • 输出结果:

    复制代码
    链表元素: 5 10 20 
    链表长度: 3
    链表元素: 5 20 
    索引0的元素: 5
    链表元素: hello template 
    链表为空

核心注意事项(模板特性相关)

  • 模板编译规则 :类模板的成员函数定义必须和声明放在头文件中(如上述MyLink.h),若拆分到.cpp文件(不含main函数),需手动显式实例化(仅支持指定类型):

    MyLink.hpp

    cpp 复制代码
    #ifndef MY_LINK_H
    #define MY_LINK_H
    
    #include <iostream>
    #include <stdexcept> // 异常处理
    #include <string>    // 支持string类型示例
    
    /**
     * 通用单向链表类模板
     * @brief 实现任意类型的单向链表,支持头插、尾插、删除、遍历、清空等核心操作
     * @tparam T1 链表存储的元素类型(支持int、double、string等任意可拷贝/打印的类型)
     */
    template <typename T1>
    class MyLink {
    private:
        // 修正:内部节点结构体重命名为Node,避免与外部类冲突
        struct Node {
            T1 data;       // 节点存储的数据
            Node* next;    // 指向下一个节点的指针(核心修正:值类型→指针类型)
    
            /**
             * 节点构造函数
             * @param val 节点初始化数据
             * @param nxt 下一个节点的指针(默认nullptr)
             */
            Node(const T1& val, Node* nxt = nullptr) : data(val), next(nxt) {}
        };
    
        Node* head; // 链表头节点指针(空链表时为nullptr)
        size_t len; // 链表长度(避免遍历统计,提升效率)
    
    public:
        /**
         * 链表构造函数
         * @brief 初始化空链表(头节点=nullptr,长度=0)
         */
        MyLink();
    
        /**
         * 链表析构函数
         * @brief 释放所有节点内存,避免内存泄漏
         */
        ~MyLink();
    
        /**
         * 尾插法添加元素
         * @brief 将元素添加到链表末尾
         * @param val 要添加的元素(const引用避免拷贝开销)
         * @return void
         */
        void pushBack(const T1& val);
    
        /**
         * 头插法添加元素
         * @brief 将元素添加到链表头部(效率O(1))
         * @param val 要添加的元素
         * @return void
         */
        void pushFront(const T1& val);
    
        /**
         * 删除第一个匹配的元素
         * @brief 找到第一个等于val的节点并释放内存
         * @param val 要删除的元素值
         * @return bool 删除成功返回true,元素不存在返回false
         */
        bool remove(const T1& val);
    
        /**
         * 遍历打印链表
         * @brief 从头部到尾部打印所有元素,空链表提示"空链表"
         * @return void
         */
        void print() const;
    
        /**
         * 获取链表长度
         * @return size_t 链表节点个数(无符号整数)
         */
        size_t size() const;
    
        /**
         * 清空链表
         * @brief 释放所有节点内存,恢复为空链表
         * @return void
         */
        void clear();
    
        /**
         * 获取指定索引的元素
         * @brief 支持随机访问(需遍历,效率O(n))
         * @param idx 元素索引(从0开始)
         * @return T1& 元素的引用(支持修改)
         * @throw std::out_of_range 索引越界时抛出异常
         */
        T1& at(size_t idx);
    };
    
    #endif // MY_LINK_H

    MyLink.cpp

    cpp 复制代码
    #include "MyLink.cpp"
    #include <iostream>
    #include <string>
    
    // 示例:在MyLink.cpp中显式实例化int和string类型
    template class MyLink<int>;
    template class MyLink<std::string>;
    
    // ========== 类模板成员函数定义(必须在头文件中) ==========
    template <typename T1>
    MyLink<T1>::MyLink() : head(nullptr), len(0) {}
    
    template <typename T1>
    MyLink<T1>::~MyLink() {
        clear(); // 复用clear()释放所有节点
    }
    
    template <typename T1>
    void MyLink<T1>::pushBack(const T1& val) {
        Node* newNode = new Node(val); // 创建新节点
        if (head == nullptr) { // 空链表:新节点作为头节点
            head = newNode;
        } else { // 非空链表:遍历到尾部
            Node* cur = head;
            while (cur->next != nullptr) {
                cur = cur->next;
            }
            cur->next = newNode;
        }
        len++; // 长度+1
    }
    
    template <typename T1>
    void MyLink<T1>::pushFront(const T1& val) {
        // 新节点的next指向原头节点,再更新头节点
        head = new Node(val, head);
        len++;
    }
    
    template <typename T1>
    bool MyLink<T1>::remove(const T1& val) {
        if (head == nullptr) return false; // 空链表直接返回
    
        // 情况1:删除头节点
        if (head->data == val) {
            Node* temp = head;
            head = head->next;
            delete temp;
            len--;
            return true;
        }
    
        // 情况2:删除中间/尾部节点
        Node* cur = head;
        while (cur->next != nullptr && cur->next->data != val) {
            cur = cur->next;
        }
        if (cur->next == nullptr) return false; // 未找到元素
    
        // 找到匹配节点,释放内存
        Node* temp = cur->next;
        cur->next = cur->next->next;
        delete temp;
        len--;
        return true;
    }
    
    template <typename T1>
    void MyLink<T1>::print() const {
        if (head == nullptr) {
            std::cout << "链表为空" << std::endl;
            return;
        }
        Node* cur = head;
        std::cout << "链表元素: ";
        while (cur != nullptr) {
            std::cout << cur->data << " ";
            cur = cur->next;
        }
        std::cout << std::endl;
    }
    
    template <typename T1>
    size_t MyLink<T1>::size() const {
        return len;
    }
    
    template <typename T1>
    void MyLink<T1>::clear() {
        Node* cur = head;
        while (cur != nullptr) {
            Node* temp = cur;
            cur = cur->next;
            delete temp; // 逐个释放节点
        }
        head = nullptr; // 恢复空链表状态
        len = 0;
    }
    
    template <typename T1>
    T1& MyLink<T1>::at(size_t idx) {
        if (idx >= len) {
            throw std::out_of_range("MyLink: 索引越界");
        }
        Node* cur = head;
        for (size_t i = 0; i < idx; i++) {
            cur = cur->next;
        }
        return cur->data;
    }

    main.c

    cpp 复制代码
    #include "MyLink.cpp"
    
    int main() {
        // ========== 示例1:int类型链表 ==========
        MyLink<int> intLink;
        intLink.pushBack(10);   // 尾插10
        intLink.pushBack(20);   // 尾插20
        intLink.pushFront(5);   // 头插5
        intLink.print();        // 输出:链表元素: 5 10 20
        std::cout << "链表长度: " << intLink.size() << std::endl; // 输出:3
    
        intLink.remove(10);     // 删除10
        intLink.print();        // 输出:链表元素: 5 20
        std::cout << "索引0的元素: " << intLink.at(0) << std::endl; // 输出:5
    
        // ========== 示例2:string类型链表 ==========
        MyLink<std::string> strLink;
        strLink.pushBack("hello");
        strLink.pushBack("template");
        strLink.print();        // 输出:链表元素: hello template
    
        // ========== 清空链表 ==========
        intLink.clear();
        intLink.print();        // 输出:链表为空
    
        return 0;
    }
  • 类型适配性:模板支持任意类型,但需满足:

    • 支持==比较(remove()操作需要);

    • 支持std::cout打印(print()操作需要);

    • 自定义类型需重载==<<运算符,例如:

      cpp 复制代码
      struct Person {
          std::string name;
          int age;
          bool operator==(const Person& p) const {
              return name == p.name && age == p.age;
          }
      };
      std::ostream& operator<<(std::ostream& os, const Person& p) {
          os << p.name << "(" << p.age << ")";
          return os;
      }
      // 即可使用 MyLink<Person> personLink;
  • 内存安全 :析构函数调用clear()释放所有节点,避免内存泄漏;clear()中逐个删除节点并重置headlen,确保链表状态正确。

扩展方向

可基于此模板扩展双向链表、排序链表、迭代器支持等功能,核心模板语法不变,仅需补充节点结构(如双向链表加Node* prev)和对应成员函数。

相关推荐
听风吟丶2 小时前
微服务性能压测与容量规划实战:从高并发稳定性到精准资源配置
java·开发语言
小此方2 小时前
Re:从零开始学C++(一)基础精讲·上篇:命名空间、输入输出、缺省参数、函数重载
开发语言·c++
行云流水20002 小时前
编程竞赛语言选择:为什么优先学C++?聚焦竞赛属性的语法突破
开发语言·c++
仰泳的熊猫2 小时前
1132 Cut Integer
数据结构·c++·算法·pat考试
aini_lovee2 小时前
基于边缘图像分割算法详解与MATLAB实现
开发语言·算法·matlab
Mr_WangAndy2 小时前
C++数据结构与算法_数据结构与算法概念_时间复杂度
c++·c++数据结构与算法·时间复杂度分析
艾上编程2 小时前
第一章——办公自动化之Excel批量合并工具:Python助力高效办公
开发语言·python·excel
火山灿火山3 小时前
Qt常用控件(五) - 多元素控件
开发语言·qt
熬了夜的程序员3 小时前
【Rust学习之路】序
开发语言·后端·学习·rust