链表详解及C++实现

一、什么是链表?

链表(Linked List)是一种线性数据结构,它通过指针将一组离散的内存节点串联起来,形成有序的序列。与数组的连续内存存储不同,链表的节点在内存中可以不连续,节点间的逻辑关系完全依靠指针维系。这种特性让链表在插入、删除操作上具有天然优势,无需像数组那样移动大量元素。

1.1 链表的核心组成

链表的基本单位是节点(Node),每个节点通常包含两部分:

  • 数据域:存储节点的数据(如int、float、自定义类型等);

  • 指针域:存储下一个(或上一个)节点的内存地址,通过指针实现节点间的关联。

1.2 常见链表类型

  1. 单链表 :每个节点仅包含一个指针域,指向下一个节点,尾节点指针域为nullptr(空指针),只能从表头遍历到表尾;

  2. 双向链表 :每个节点包含两个指针域,分别指向前一个节点后一个节点,可双向遍历,插入/删除时需维护两个指针;

  3. 循环链表:尾节点的指针域不指向nullptr,而是指向表头节点(单循环链表)或表头的前驱节点(双循环链表),可实现首尾相连的遍历。

1.3 链表与数组的对比

特性 链表(指针实现) 数组
内存存储 离散存储,节点间通过指针关联 连续存储,元素地址连续
访问效率 O(n),需从表头遍历查找元素 O(1),支持随机访问(通过下标)
插入/删除效率 O(1)(已知插入/删除位置时),仅需修改指针 O(n),需移动插入/删除位置后的所有元素
空间灵活性 动态扩容,无需预先分配固定空间 静态扩容(或动态数组的拷贝扩容),空间利用率可能较低

二、指针链表的C++实现核心思路

C++中实现链表的核心是自定义节点结构体/类指针操作 。以最常用的单链表双向链表为例,核心思路如下:

2.1 节点定义

使用模板类定义节点,确保链表可适配多种数据类型。单链表节点包含数据域和一个后继指针;双向链表节点额外增加一个前驱指针。

2.2 链表类设计

封装链表的核心操作,对外提供简洁的接口,隐藏指针操作的细节。核心操作包括:

  • 初始化(创建空链表);

  • 节点插入(表头、表尾、指定位置);

  • 节点删除(表头、表尾、指定值/位置);

  • 链表遍历(正向、反向,双向链表支持);

  • 元素查找;

  • 获取链表长度、判断是否为空;

  • 销毁链表(释放内存,避免内存泄漏)。

三、完整C++实现代码

3.1 单链表实现

单链表是最基础的链表类型,仅支持正向遍历,插入/删除操作只需维护后继指针。

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

// 单链表节点模板类
template <typename T>
class SingleListNode {
public:
    T data;                   // 数据域
    SingleListNode<T>* next; // 后继指针(指向 next 节点)

    // 构造函数
    SingleListNode(T val) : data(val), next(nullptr) {}
};

// 单链表类(指针实现)
template <typename T>
class SingleLinkedList {
private:
    SingleListNode<T>* head; // 头指针(指向链表第一个节点)
    int length;               // 链表长度(避免每次遍历统计)

public:
    // 构造函数:初始化空链表
    SingleLinkedList() : head(nullptr), length(0) {}

    // 析构函数:销毁链表,释放所有节点内存
    ~SingleLinkedList() {
        SingleListNode<T>* current = head;
        while (current != nullptr) {
            SingleListNode<T>* temp = current; // 保存当前节点
            current = current->next;           // 移动到下一个节点
            delete temp;                       // 释放当前节点内存
        }
        head = nullptr; // 头指针置空
        length = 0;
    }

    // 1. 判空:判断链表是否为空
    bool isEmpty() const {
        return length == 0;
    }

    // 2. 获取链表长度
    int getLength() const {
        return length;
    }

    // 3. 表头插入节点
    void insertAtHead(T val) {
        // 创建新节点
        SingleListNode<T>* newNode = new SingleListNode<T>(val);
        // 新节点的 next 指向原头节点
        newNode->next = head;
        // 头指针指向新节点(新节点成为新表头)
        head = newNode;
        length++; // 长度+1
    }

    // 4. 表尾插入节点
    void insertAtTail(T val) {
        SingleListNode<T>* newNode = new SingleListNode<T>(val);
        // 空链表特殊处理:新节点直接作为头节点
        if (isEmpty()) {
            head = newNode;
        } else {
            // 遍历到尾节点(next 为 nullptr 的节点)
            SingleListNode<T>* current = head;
            while (current->next != nullptr) {
                current = current->next;
            }
            // 尾节点的 next 指向新节点
            current->next = newNode;
        }
        length++;
    }

    // 5. 指定位置插入节点(pos:插入到第 pos 个节点之后,pos 从 1 开始)
    bool insertAtPos(int pos, T val) {
        // 位置合法性检查:pos 需在 0 ~ length 之间(pos=0 等价于表头插入)
        if (pos < 0 || pos > length) {
            cout << "插入位置非法!" << endl;
            return false;
        }
        // pos=0 直接调用表头插入
        if (pos == 0) {
            insertAtHead(val);
            return true;
        }
        // 遍历到第 pos 个节点
        SingleListNode<T>* current = head;
        for (int i = 1; i < pos; i++) {
            current = current->next;
        }
        // 创建新节点,调整指针
        SingleListNode<T>* newNode = new SingleListNode<T>(val);
        newNode->next = current->next; // 新节点 next 指向原 pos+1 节点
        current->next = newNode;       // pos 节点 next 指向新节点
        length++;
        return true;
    }

    // 6. 删除表头节点
    bool deleteAtHead() {
        if (isEmpty()) {
            cout << "链表为空,无法删除!" << endl;
            return false;
        }
        SingleListNode<T>* temp = head; // 保存原头节点
        head = head->next;              // 头指针指向原头节点的 next
        delete temp;                    // 释放原头节点内存
        length--;
        return true;
    }

    // 7. 删除表尾节点
    bool deleteAtTail() {
        if (isEmpty()) {
            cout << "链表为空,无法删除!" << endl;
            return false;
        }
        // 只有一个节点的情况:直接删除头节点
        if (length == 1) {
            delete head;
            head = nullptr;
        } else {
            // 遍历到倒数第二个节点(尾节点的前驱)
            SingleListNode<T>* current = head;
            while (current->next->next != nullptr) {
                current = current->next;
            }
            delete current->next;       // 释放尾节点
            current->next = nullptr;    // 倒数第二个节点 next 置空
        }
        length--;
        return true;
    }

    // 8. 删除指定值的节点(删除第一个匹配值的节点)
    bool deleteByVal(T val) {
        if (isEmpty()) {
            cout << "链表为空,无法删除!" << endl;
            return false;
        }
        // 头节点就是目标节点的情况
        if (head->data == val) {
            return deleteAtHead();
        }
        // 遍历查找目标节点的前驱
        SingleListNode<T>* current = head;
        while (current->next != nullptr && current->next->data != val) {
            current = current->next;
        }
        // 未找到目标节点
        if (current->next == nullptr) {
            cout << "未找到值为 " << val << " 的节点!" << endl;
            return false;
        }
        // 删除目标节点
        SingleListNode<T>* temp = current->next;
        current->next = temp->next; // 前驱节点 next 指向目标节点的 next
        delete temp;
        length--;
        return true;
    }

    // 9. 查找指定值的节点(返回是否存在)
    bool search(T val) const {
        SingleListNode<T>* current = head;
        while (current != nullptr) {
            if (current->data == val) {
                return true; // 找到
            }
            current = current->next;
        }
        return false; // 未找到
    }

    // 10. 正向遍历链表并打印
    void traverse() const {
        if (isEmpty()) {
            cout << "链表为空!" << endl;
            return;
        }
        cout << "单链表遍历:";
        SingleListNode<T>* current = head;
        while (current != nullptr) {
            cout << current->data << " ";
            current = current->next;
        }
        cout << endl;
    }
};

3.2 双向链表实现

双向链表支持正向和反向遍历,插入/删除时需同时维护前驱指针和后继指针,灵活性更高。

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

// 双向链表节点模板类
template <typename T>
class DoubleListNode {
public:
    T data;                     // 数据域
    DoubleListNode<T>* prev;    // 前驱指针(指向 previous 节点)
    DoubleListNode<T>* next;    // 后继指针(指向 next 节点)

    // 构造函数
    DoubleListNode(T val) : data(val), prev(nullptr), next(nullptr) {}
};

// 双向链表类(指针实现)
template <typename T>
class DoubleLinkedList {
private:
    DoubleListNode<T>* head; // 头指针
    DoubleListNode<T>* tail; // 尾指针(直接指向尾节点,优化表尾操作效率)
    int length;               // 链表长度

public:
    // 构造函数:初始化空链表
    DoubleLinkedList() : head(nullptr), tail(nullptr), length(0) {}

    // 析构函数:销毁链表
    ~DoubleLinkedList() {
        DoubleListNode<T>* current = head;
        while (current != nullptr) {
            DoubleListNode<T>* temp = current;
            current = current->next;
            delete temp;
        }
        head = tail = nullptr;
        length = 0;
    }

    // 1. 判空
    bool isEmpty() const {
        return length == 0;
    }

    // 2. 获取长度
    int getLength() const {
        return length;
    }

    // 3. 表头插入
    void insertAtHead(T val) {
        DoubleListNode<T>* newNode = new DoubleListNode<T>(val);
        if (isEmpty()) {
            // 空链表:头、尾指针都指向新节点
            head = newNode;
            tail = newNode;
        } else {
            newNode->next = head; // 新节点 next 指向原头节点
            head->prev = newNode; // 原头节点 prev 指向新节点
            head = newNode;       // 头指针更新为新节点
        }
        length++;
    }

    // 4. 表尾插入(利用尾指针,O(1) 效率)
    void insertAtTail(T val) {
        DoubleListNode<T>* newNode = new DoubleListNode<T>(val);
        if (isEmpty()) {
            head = newNode;
            tail = newNode;
        } else {
            newNode->prev = tail; // 新节点 prev 指向原尾节点
            tail->next = newNode; // 原尾节点 next 指向新节点
            tail = newNode;       // 尾指针更新为新节点
        }
        length++;
    }

    // 5. 指定位置插入(pos:插入到第 pos 个节点之后,pos 从 1 开始)
    bool insertAtPos(int pos, T val) {
        if (pos < 0 || pos > length) {
            cout << "插入位置非法!" << endl;
            return false;
        }
        if (pos == 0) {
            insertAtHead(val);
            return true;
        }
        if (pos == length) {
            insertAtTail(val);
            return true;
        }
        // 遍历到第 pos 个节点(双向链表可优化为从表头或表尾遍历,此处简化为表头遍历)
        DoubleListNode<T>* current = head;
        for (int i = 1; i < pos; i++) {
            current = current->next;
        }
        // 调整前驱和后继指针
        DoubleListNode<T>* newNode = new DoubleListNode<T>(val);
        newNode->prev = current;          // 新节点 prev 指向 pos 节点
        newNode->next = current->next;    // 新节点 next 指向 pos+1 节点
        current->next->prev = newNode;    // pos+1 节点 prev 指向新节点
        current->next = newNode;          // pos 节点 next 指向新节点
        length++;
        return true;
    }

    // 6. 删除表头
    bool deleteAtHead() {
        if (isEmpty()) {
            cout << "链表为空,无法删除!" << endl;
            return false;
        }
        DoubleListNode<T>* temp = head;
        if (length == 1) {
            // 只有一个节点:头、尾指针都置空
            head = tail = nullptr;
        } else {
            head = head->next;       // 头指针指向原头节点的 next
            head->prev = nullptr;    // 新头节点 prev 置空
        }
        delete temp;
        length--;
        return true;
    }

    // 7. 删除表尾(O(1) 效率)
    bool deleteAtTail() {
        if (isEmpty()) {
            cout << "链表为空,无法删除!" << endl;
            return false;
        }
        DoubleListNode<T>* temp = tail;
        if (length == 1) {
            head = tail = nullptr;
        } else {
            tail = tail->prev;       // 尾指针指向原尾节点的 prev
            tail->next = nullptr;    // 新尾节点 next 置空
        }
        delete temp;
        length--;
        return true;
    }

    // 8. 删除指定值的节点
    bool deleteByVal(T val) {
        if (isEmpty()) {
            cout << "链表为空,无法删除!" << endl;
            return false;
        }
        // 头节点是目标节点
        if (head->data == val) {
            return deleteAtHead();
        }
        // 尾节点是目标节点
        if (tail->data == val) {
            return deleteAtTail();
        }
        // 遍历查找目标节点
        DoubleListNode<T>* current = head->next;
        while (current != tail && current->data != val) {
            current = current->next;
        }
        // 未找到
        if (current == tail && current->data != val) {
            cout << "未找到值为 " << val << " 的节点!" << endl;
            return false;
        }
        // 调整指针
        current->prev->next = current->next; // 前驱节点 next 指向目标节点的 next
        current->next->prev = current->prev; // 后继节点 prev 指向目标节点的 prev
        delete current;
        length--;
        return true;
    }

    // 9. 查找指定值
    bool search(T val) const {
        DoubleListNode<T>* current = head;
        while (current != nullptr) {
            if (current->data == val) {
                return true;
            }
            current = current->next;
        }
        return false;
    }

    // 10. 正向遍历
    void traverseForward() const {
        if (isEmpty()) {
            cout << "链表为空!" << endl;
            return;
        }
        cout << "双向链表正向遍历:";
        DoubleListNode<T>* current = head;
        while (current != nullptr) {
            cout << current->data << " ";
            current = current->next;
        }
        cout << endl;
    }

    // 11. 反向遍历(双向链表特有)
    void traverseBackward() const {
        if (isEmpty()) {
            cout << "链表为空!" << endl;
            return;
        }
        cout << "双向链表反向遍历:";
        DoubleListNode<T>* current = tail;
        while (current != nullptr) {
            cout << current->data << " ";
            current = current->prev;
        }
        cout << endl;
    }
};

3.3 测试代码(主函数)

cpp 复制代码
int main() {
    // -------------------------- 单链表测试 --------------------------
    cout << "===== 单链表测试 =====" << endl;
    SingleLinkedList<int> singleList;

    // 插入测试
    singleList.insertAtHead(3);
    singleList.insertAtHead(1);
    singleList.insertAtTail(5);
    singleList.insertAtPos(2, 4); // 在第2个节点(值为3)后插入4
    singleList.traverse(); // 预期:1 3 4 5

    // 长度和判空测试
    cout << "单链表长度:" << singleList.getLength() << endl; // 预期:4
    cout << "单链表是否为空:" << (singleList.isEmpty() ? "是" : "否") << endl; // 预期:否

    // 查找测试
    int searchVal = 4;
    cout << "是否存在值 " << searchVal << ":" << (singleList.search(searchVal) ? "是" : "否") << endl; // 预期:是

    // 删除测试
    singleList.deleteByVal(3);
    singleList.traverse(); // 预期:1 4 5
    singleList.deleteAtHead();
    singleList.traverse(); // 预期:4 5
    singleList.deleteAtTail();
    singleList.traverse(); // 预期:4

    // -------------------------- 双向链表测试 --------------------------
    cout << "\n===== 双向链表测试 =====" << endl;
    DoubleLinkedList<int> doubleList;

    // 插入测试
    doubleList.insertAtHead(2);
    doubleList.insertAtTail(6);
    doubleList.insertAtPos(1, 4); // 在第1个节点(值为2)后插入4
    doubleList.traverseForward();  // 预期:2 4 6
    doubleList.traverseBackward(); // 预期:6 4 2

    // 长度测试
    cout << "双向链表长度:" << doubleList.getLength() << endl; // 预期:3

    // 删除测试
    doubleList.deleteByVal(4);
    doubleList.traverseForward();  // 预期:2 6
    doubleList.deleteAtHead();
    doubleList.traverseForward();  // 预期:6
    doubleList.deleteAtTail();
    doubleList.traverseForward();  // 预期:链表为空

    return 0;
}

四、代码说明与核心要点

4.1 指针操作的核心细节

  • 空指针处理:链表操作中必须避免对 nullptr 解引用(如访问 current->data 时需确保 current != nullptr),否则会导致程序崩溃;

  • 指针指向更新顺序:插入/删除节点时,需先更新新节点或前驱节点的指针,再更新原节点的指针,避免链表"断裂"(如单链表表头插入需先让新节点 next 指向原头节点,再更新 head);

  • 内存释放:删除节点后必须手动释放内存(delete),否则会造成内存泄漏,析构函数中需遍历整个链表释放所有节点。

4.2 模板类的优势

使用模板类 template <typename T> 让链表可适配 int、float、string 等多种数据类型,无需为每种数据类型单独编写链表代码,极大提高了代码复用性。例如,创建存储字符串的链表只需 SingleLinkedList<string> strList;

4.3 双向链表的优化点

双向链表通过增加尾指针(tail),将表尾插入、表尾删除操作的效率从 O(n) 提升到 O(1);同时支持反向遍历,适合需要双向访问元素的场景(如LRU缓存淘汰算法)。但相比单链表,双向链表每个节点多一个指针域,占用更多内存,插入/删除操作需维护两个指针,逻辑更复杂。

五、编译与运行

5.1 编译命令

将代码保存为 linked_list.cpp,使用 g++ 编译:

bash 复制代码
g++ linked_list.cpp -o linked_list

5.2 运行结果

text 复制代码
===== 单链表测试 =====
单链表遍历:1 3 4 5 
单链表长度:4
单链表是否为空:否
是否存在值 4:是
单链表遍历:1 4 5 
单链表遍历:4 5 
单链表遍历:4 

===== 双向链表测试 =====
双向链表正向遍历:2 4 6 
双向链表反向遍历:6 4 2 
双向链表长度:3
双向链表正向遍历:2 6 
双向链表正向遍历:6 
双向链表为空!

六、常见问题与注意事项

  1. 链表断裂:指针更新顺序错误会导致链表断裂(如单链表插入时先更新 head 再设置 newNode->next),需牢记"先关联新节点,再断开旧关联";

  2. 内存泄漏:忘记 delete 删除的节点,或析构函数未遍历释放所有节点,会导致内存泄漏,长期运行可能导致程序崩溃;

  3. 边界条件处理:空链表、只有一个节点的链表、插入/删除位置为表头/表尾等边界情况,需单独处理,否则易出现逻辑错误;

  4. 遍历终止条件 :单链表遍历终止条件为 current != nullptr,双向链表反向遍历终止条件为 current != nullptr,避免越界访问;

  5. 与STL容器对比 :C++ STL 提供了 std::list(双向链表)和 std::forward_list(单链表),封装了完整的链表操作,实际开发中可直接使用,但手动实现链表有助于理解指针和数据结构原理。

七、总结

指针链表是C++中指针应用的经典场景,核心在于通过指针串联离散节点,实现高效的插入/删除操作。本文详细讲解了单链表和双向链表的概念、设计思路,并用模板类实现了完整的链表操作,包括插入、删除、遍历、查找等核心功能,同时提供了测试代码和运行结果。

相关推荐
源代码•宸1 分钟前
Golang基础语法(go语言指针、go语言方法、go语言接口、go语言断言)
开发语言·经验分享·后端·golang·接口·指针·方法
名字不相符3 分钟前
NSSCTF2026年1月8日每日一练之[第五空间 2021]WebFTP
学习·萌新
四谎真好看5 分钟前
JavaWeb 学习笔记(Day02)之Vue
笔记·学习·vue·学习笔记·javaweb
:mnong8 分钟前
辅助学习神经网络
人工智能·神经网络·学习
进阶小白猿11 分钟前
Java技术八股学习Day14
java·数据库·学习
ChoSeitaku14 分钟前
16.C++入门:list|手撕list|反向迭代器|与vector对比
c++·windows·list
li星野17 分钟前
Windows下QT开发一个OpenCV程序
学习
Qhumaing20 分钟前
C++学习:【PTA】数据结构 7-1 实验6-1(图-邻接矩阵)
c++·学习·算法
No0d1es20 分钟前
2025年12月 GESP CCF编程能力等级认证C++一级真题
开发语言·c++·青少年编程·gesp·ccf
2301_7737303127 分钟前
系统编程—在线商城信息查询系统
c++·html