C++中的list模拟实现解读
作者:一枝小雨
文章详解C++ list模拟实现,采用双向循环链表结构,通过迭代器封装与运算符重载实现容器功能,涵盖构造、拷贝、内存管理及异常安全等关键点,并讨论了测试与改进方向,如移动语义和STL兼容性
list 模拟实现详解
1. 基本框架与节点结构
list的底层实现通常采用带头双向循环链表,每个节点包含指向前后节点的指针和数据域。
namespace practice_list
{
    template<class T>
    struct __list_node
    {
        __list_node<T>* _next;    // 指向下一个节点的指针
        __list_node<T>* _prev;    // 指向前一个节点的指针
        T _data;                  // 存储的数据
        // 节点构造函数,使用默认参数初始化
        __list_node(const T& x = T())
            : _data(x)           // 初始化数据域
            , _next(nullptr)     // 初始化next指针
            , _prev(nullptr)     // 初始化prev指针
        {}
    };
}关键点说明:
- 使用模板类实现泛型编程,支持任何数据类型
 - 节点包含前后指针,实现双向链表结构
 - 构造函数使用默认参数,方便创建头节点和数据节点
 
2. 迭代器初步实现
迭代器是让链表能够像容器一样被遍历的关键,它封装了节点指针并重载了相关运算符。
2.1 简单迭代器实现
template<class T>
struct __list_iterator
{
    typedef __list_node<T> Node;
    Node* _node;  // 封装节点指针
    // 构造函数,通过节点指针初始化迭代器
    __list_iterator(Node* node)
        :_node(node)
    {}
    // 解引用操作符重载,返回节点数据的引用
    T& operator*()
    {
        return _node->_data;
    }
    // 前置++操作符重载,移动到下一个节点
    __list_iterator<T>& operator++()
    {
        _node = _node->_next;
        return *this;
    }
    // 不等于操作符重载,用于比较两个迭代器
    bool operator!=(const __list_iterator<T>& it)
    {
        return _node != it._node;
    }
};关键点说明:
- 迭代器本质上是对节点指针的封装
 - 通过重载运算符实现指针-like的行为
 operator*返回数据引用,使我们可以通过迭代器修改数据operator++让迭代器移动到下一个节点operator!=用于比较迭代器是否指向相同节点
3. 基础list类实现
3.1 类定义与迭代器类型声明
template<class T>
class list
{
    typedef __list_node<T> Node;
public:
    // 声明迭代器类型
    typedef __list_iterator<T> iterator;
    
    // 获取指向第一个元素的迭代器
    iterator begin()
    {
        return iterator(_head->_next);  // 匿名对象写法
    }
    
    // 获取尾后迭代器(指向头节点)
    iterator end()
    {
        return iterator(_head);  // 匿名对象写法
    }
    
    // 构造函数,初始化带头双向循环链表
    list()
    {
        _head = new Node;      // 创建头节点
        _head->_next = _head;  // 头节点的next指向自己
        _head->_prev = _head;  // 头节点的prev指向自己
    }
    
    // 尾插函数
    void push_back(const T& x)
    {
        Node* tail = _head->_prev;  // 找到当前尾节点
        Node* newnode = new Node(x); // 创建新节点
        
        // 链接新节点与前一个节点(尾节点)
        tail->_next = newnode;
        newnode->_prev = tail;
        
        // 链接新节点与后一个节点(头节点)
        newnode->_next = _head;
        _head->_prev = newnode;
    }
private:
    Node* _head;  // 头节点指针
};关键点说明:
- list类维护一个头节点指针
_head - begin()返回第一个有效元素的迭代器(头节点的下一个节点)
 - end()返回尾后迭代器(指向头节点本身)
 - 构造函数初始化一个空链表,头节点指向自己
 - push_back()在链表尾部插入新节点
 
3.2 测试函数
void test_list1()
{
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    list<int>::iterator it = lt.begin();
    while (it != lt.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}4. 完整迭代器实现
更完整的迭代器实现需要考虑const迭代器、前后移动等功能。
4.1 进阶迭代器设计
// 使用三个模板参数支持普通迭代器和const迭代器
// T: 数据类型, Ref: 引用类型, Ptr: 指针类型
template<class T, class Ref, class Ptr>
struct __list_iterator
{
    typedef __list_node<T> Node;
    typedef __list_iterator<T, Ref, Ptr> Self;  // 自身类型别名
    Node* _node;
    __list_iterator(Node* node)
        :_node(node)
    {}
    // 解引用操作符,返回Ref类型(T&或const T&)
    Ref operator*()
    {
        return _node->_data;
    }
    // 前置++
    Self& operator++()
    {
        _node = _node->_next;
        return *this;
    }
    // 后置++(int参数用于区分前置和后置)
    Self operator++(int)
    {
        Self ret = _node;  // 保存当前状态
        ++(*this);         // 调用前置++
        return ret;        // 返回之前保存的状态
    }
    // 前置--
    Self& operator--()
    {
        _node = _node->_prev;
        return *this;
    }
    // 后置--
    Self operator--(int)
    {
        Self ret = _node;
        --(*this);
        return ret;
    }
    // 不等于操作符
    bool operator!=(const Self& it)
    {
        return _node != it._node;
    }
    // 等于操作符
    bool operator==(const Self& it)
    {
        return !(_node != it._node);
    }
    // 箭头操作符,用于访问成员
    Ptr operator->()
    {
        return &(_node->_data);
    }
};关键点说明:
- 使用三个模板参数实现普通迭代器和const迭代器的统一实现
 - 添加了前后移动操作(++和--的前置和后置版本)
 - 实现了箭头操作符,用于直接访问成员变量
 - 使用Self类型别名简化代码编写
 
5. 完整list类实现
5.1 类型定义与构造函数
template<class T>
class list
{
    typedef __list_node<T> Node;
private:
    Node* _head;  // 头节点指针
    
public:
    // 普通迭代器类型
    typedef __list_iterator<T, T&, T*> iterator;
    // const迭代器类型
    typedef __list_iterator<T, const T&, const T*> const_iterator;
    // 构造函数
    list()
    {
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;
    }
    
    // 析构函数
    ~list(){
        clear();     // 清空所有节点
        delete _head;        // 删除头节点
        _head = nullptr;
    }
    
    // 获取起始迭代器
    iterator begin()
    {
        return iterator(_head->_next);
    }
    
    // 获取尾后迭代器
    iterator end()
    {
        return iterator(_head);
    }
    
    // 获取const起始迭代器
    const_iterator begin() const
    {
        return const_iterator(_head->_next);
    }
    
    // 获取const尾后迭代器
    const_iterator end() const
    {
        return const_iterator(_head);
    }
    
    5.2 拷贝构造与赋值重载
    // 拷贝构造函数
    list(const list<T>& lt)
    {
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;
        
        // 遍历源链表,插入数据
        const_iterator it = lt.begin();
        while (it != lt.end())
        {
            push_back(*it);
            ++it;
        }
        
        // 使用范围for,更简洁
        /*for (auto e : lt)
        {
                push_back(*it);
        }*/
    }
    
    /* 赋值重载 */
    /*list<T>& operator=(const list<T>& lt)
    {
            if (this != <)
            {
                    clear();
                    for (auto e : lt)
                    {
                            push_back(e);
                    }
            }
            else
                    return *this;
    }*/
    // 赋值运算符(简洁写法)
    list<T>& operator=(list<T> lt)  // 传参调用拷贝构造
    {
        swap(_head, lt._head);  // 交换头指针
        return *this;           // 返回当前对象
    }5.3 元素访问与修改函数
    // 尾插
    void push_back(const T& x)
    {
        /*
        Node* tail = _head->_prev;
        Node* newnode = new Node(x);
        // 链接newnode与newnode前一个节点tail
        tail->_next = newnode;
        newnode->_prev = tail;
        // 链接newnode与newnod后一个节点"头节点"
        newnode->_next = _head;
        _head->_prev = newnode;
        */
        
        insert(end(), x);  // 在end()前插入
    }
    
    // 尾删
    void pop_back()
    {
        /*Node* tail = _head->_prev;
        Node* newtail = tail->_prev;
        delete[] tail;
        _head->_prev = newtail;
        newtail->_next = _head;*/
        erase(--end());
        // erase(iterator(_head->_prev)); 这种写法也可以
    }
    
    // 头插
    void push_front(const T& x)
    {
        insert(begin(), x);  // 在begin()前插入
    }
    
    // 头删
    void pop_front()
    {
        erase(begin());  // 删除第一个元素
    }
    
    // 在指定位置前插入
    void insert(iterator pos, const T& x)
    {
        Node* cur = pos._node;    // 当前节点
        Node* prev = cur->_prev;  // 前一个节点
        Node* newnode = new Node(x);  // 新节点
        
        // 链接新节点与前一个节点
        prev->_next = newnode;
        newnode->_prev = prev;
        
        // 链接新节点与当前节点
        newnode->_next = cur;
        cur->_prev = newnode;
    }
    
    // 删除指定位置元素
    void erase(iterator pos)
    {
        assert(pos != end());  // 不能删除头节点
        
        Node* cur = pos._node;
        Node* next = cur->_next;
        Node* prev = cur->_prev;
        
        delete cur;  // 释放节点内存
        
        // 重新链接前后节点
        next->_prev = prev;
        prev->_next = next;
    }
    
    // 清空链表(保留头节点)
    void clear()
    {
        iterator it = begin();
        while (it != end())
        {
        erase(it++);  // 使用后置++避免迭代器失效
        }
    }
};6. 测试代码与特殊用例
6.1 基本功能测试
void test_list1()
{
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    list<int>::iterator it = lt.begin();
    while (it != lt.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}6.2 结构体访问测试
struct Date
{
    int _year = 0;
    int _month = 1;
    int _day = 1;
};
void test_list2()
{
    list<Date> lt;
    lt.push_back(Date());
    lt.push_back(Date());
    list<Date>::iterator it = lt.begin();
    while (it != lt.end())
    {
        cout << it->_year << "-" << it->_month << "-" << it->_day << endl;
        ++it;
    }
    cout << endl;
}关键点说明:
it->等价于it.operator->(),返回Date*- 编译器特殊处理了
it->member,使其等价于(it.operator->())->member - 这种语法糖提高了代码的可读性
 
6.3 const迭代器测试
void print_list(const list<int>& lt)
{
    list<int>::const_iterator it = lt.begin();
    while (it != lt.end())
    {
        // *it = 1;  // 错误:不能修改const引用指向的值
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}6.4 拷贝构造与赋值测试
void test_list4()
{
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    list<int> lt2(lt);  // 测试拷贝构造函数
    print_list(lt2);
}
void test_list5()
{
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    list<int> lt2;
    lt2.push_back(10);
    lt2.push_back(20);
    lt2.push_back(30);
    lt2.push_back(40);
    
    lt = lt2;  // 测试赋值运算符
    print_list(lt);
}7. 补充说明与注意事项
7.1 迭代器设计的关键点
- 封装指针行为:迭代器封装了节点指针,使其能够像指针一样使用
 - 模板技巧:通过模板参数区分普通迭代器和const迭代器
 - 前后置运算符:正确实现++和--的前置和后置版本
 - 箭头运算符:特殊处理
->操作符以直接访问成员 
7.2 链表操作的关键点
- 边界处理:始终维护双向循环链表的完整性
 - 异常安全:在修改链表结构前分配资源,确保异常安全
 - 迭代器失效:插入和删除操作可能导致迭代器失效,需要特别注意
 - 内存管理:确保所有动态分配的节点都被正确释放
 
7.3 可行的改进点
- 异常处理:可以添加异常处理机制增强鲁棒性
 - 性能优化:可以考虑添加移动语义支持(C++11)
 - 更多容器操作:实现如resize(), merge(), sort()等常用操作
 - 迭代器萃取:添加迭代器特性支持,与STL算法更好地配合
 
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
