C++中的list模拟实现解读

 更新时间:2025年09月18日 09:02:01   作者:一枝小雨  
文章详解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 != &lt)
            {
                    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算法更好地配合

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • C语言实现学生考勤系统

    C语言实现学生考勤系统

    这篇文章主要为大家详细介绍了C语言实现学生考勤系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • 浅析VC++中的头文件包含问题

    浅析VC++中的头文件包含问题

    类中尽量采用指针或引用方式调用其它类,这样就可以只声明class xxx了。并且这也符合资源最优利用,更利于使用多态
    2013-09-09
  • LintCode 堆化详解及实例代码

    LintCode 堆化详解及实例代码

    这篇文章主要介绍了LintCode 堆化详解及实例代码的相关资料,需要的朋友可以参考下
    2017-04-04
  • C++浅析虚函数使用方法

    C++浅析虚函数使用方法

    对C++了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。本文就将详细讲讲虚函数表的原理与使用,需要的可以参考一下
    2022-08-08
  • mysate中stat命令的实现方法

    mysate中stat命令的实现方法

    这篇文章主要介绍了mysate中stat命令的实现方法,stat作用:用来显示文件的详细信息,包括inode, atime, mtime, ctime,本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • 嵌入式项目使用C语言结构体位段特性实现断言宏校验数据范围有效性的方法

    嵌入式项目使用C语言结构体位段特性实现断言宏校验数据范围有效性的方法

    今天小编就为大家分享一篇关于嵌入式项目使用C语言结构体位段特性实现断言宏校验数据范围有效性的方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • C语言读写配置文件的方法

    C语言读写配置文件的方法

    这篇文章主要介绍了C语言读写配置文件的方法,包括C语言读写ini配置文件所涉及的文件读写技巧,以及完整的源文件及头文件实现方法,需要的朋友可以参考下
    2015-07-07
  • C++设计模式之迭代器模式(Iterator)

    C++设计模式之迭代器模式(Iterator)

    这篇文章主要为大家详细介绍了C++设计模式之迭代器模式Iterator,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • 分享C++面试中string类的一种正确写法

    分享C++面试中string类的一种正确写法

    C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源
    2013-11-11
  • C语言基础指针详解教程

    C语言基础指针详解教程

    此处对于指针做一些简要的介绍,作者实属初学,写博客也是作者学习的一个过程,难免文章中有内容理解不到位或者有不当之处,还请朋友们不吝指正,希望大家给予支持
    2021-11-11

最新评论