C++【STL】之list模拟实现

简介: C++ STL list类模拟实现,常用接口详细讲解,干货满满!

list模拟实现

上一篇讲解了list的使用,这一篇接着介绍list的模拟实现,这里依然是讲解常用接口的模拟实现,话不多说,正文开始!

1. 成员变量和节点

list类中的成员变量就是一个哨兵位的头结点,后续在上进行链接操作,而节点的实现需要一个节点类来进行封装

private:
    node* _head; //哨兵位头结点
template<class T>
struct list_node
{
   
    list_node<T>* _next; //指向前一个节点
    list_node<T>* _prev; //指向后一个节点
    T _data; //节点内数据

    list_node(const T& x = T())
        :_next(nullptr)
        , _prev(nullptr)
        , _data(x)
    {
   }
};

2. 迭代器

迭代器要么就是原生指针,要么就是自定义类型对原生指针的封装,模拟指针的行为

显然list中的迭代器是第二种,list的迭代器也是被封装成了一个类,类中的成员为节点类指针,指向单个节点

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* n) //begin()和end()中需要构造来返回节点指针
        :_node(n)
    {
   }

    Ref operator*()
    {
   
        return _node->_data;
    }

    Ptr operator->()
    {
   
        return &_node->_data;
    }
    //前置++
    self& operator++() 
    {
   
        _node = _node->_next;
        return *this;
    }
    //后置++
    self operator++(int) //局部变量tmp出了作用域会销毁,不能使用引用返回
    {
   
        self tmp(*this);
        _node = _node->_next;

        return tmp;
    }
    //前置--
    self& operator--()
    {
   
        _node = _node->_prev;
        return *this;
    }
    //后置--
    self operator--(int)
    {
   
        self tmp(*this);
        _node = _node->prev;
        return tmp;
    }

    bool operator!=(const self& s)
    {
   
        return _node != s._node;
    }

    bool operator==(const self& s)
    {
   
        return _node == s._node;
    }
};

==注意==

  • list双向链表是链式结构,迭代器需要自定义类型对原生指针的封装,模拟指针的行为
  • begin()end()中需要构造来拿到节点的指针,所以需要提供构造函数
  • class Refclass Ptr模板的提供是为了T*const T*不用复用代码来直接传参
  • typedef _list_iterator<T, Ref, Ptr> self这里的self是其类型别名,*this指针就是self类型,包含了_node成员变量,_node 是迭代器中的节点指针,包含在迭代器对象中

2.1 移动原理

list的空间不是连续的,使用的是双向迭代器,只支持++、--来实现前后节点间的移动,不支持随机移动

那么是如何实现不连续空间之间的移动的呢?

首先构造迭代器对象,当使用++操作时,会去调用迭代器类中实现的operator++()重载方法,核心操作就是将迭代器指向当前节点的下一个节点,即_node = _node -> next,使用--操作原理也是如此

//前置++
self& operator++() 
{
   
    _node = _node->_next;
    return *this;
}
//后置++
self operator++(int) //局部变量tmp出了作用域会销毁,不能使用引用返回
{
   
    self tmp(*this);
    _node = _node->_next;

    return tmp;
}
//前置--
self& operator--()
{
   
    _node = _node->_prev;
    return *this;
}
//后置--
self operator--(int)
{
   
    self tmp(*this);
    _node = _node->prev;
    return tmp;
}

2.2 多参数模板

迭代器分为普通迭代器和const迭代器,不同的对象需要调用不同的迭代器类型,这里使用多模板参数很好的解决了两种迭代器在实现时的冗余问题

  • T:普通节点类型
  • Ref:引用节点类型(可以是const)
  • Ptr:指针节点类型(可以是const)
template<class T, class Ref, class Ptr>
struct _list_iterator
{
   
    typedef list_node<T> node;
    typedef _list_iterator<T, Ref, Ptr> self;
    node* _node; //成员变量
    //...
};

用不同的迭代器类型时,迭代器类中的模板参数变为对应类型,这就是所谓的泛型思想之一

3. 默认成员函数

3.1 构造和析构

构造函数需要创建头结点,这里要先提供一个空初始化的方法empty_init()

void empty_init()
{
   
    //初始化头结点
    _head = new node;
    _head->_next = _head;
    _head->_prev = _head;
}

==默认构造==

list()
{
   
    empty_init();
}

==迭代器区间构造==

template<class Iterator>
list(Iterator first, Iterator last)
{
   
    empty_init();
    while (first != last)
    {
   
        push_back(*first);
        ++first;
    }
}

在创建 list 对象时,多使用迭代器区间进行构造,创建新对象可以直接调用尾插进行创建

==析构函数==

调用clear()方法释放节点,然后再释放头结点即可

~list()
{
   
    clear();
    delete _head;
    _head = nullptr;
}

3.2 拷贝和赋值

==拷贝构造==

void swap(list<T>& tmp)
{
   
    std::swap(_head, tmp._head);
}

list(const list<T>& lt)
{
   
    empty_init();
    list<T> tmp(lt.begin(), lt.end());
    swap(tmp);
}

拷贝构造必须使用引用传参,否则会导致无穷递归问题

==赋值重载==

list<T>& operator=(list<T> lt)
{
   
    swap(lt);
    return *this;
}

4. 容量操作

4.1 empty方法

判空只需要判断当前 begin()end() 的位置是否相同即可

bool empty() const
{
    
    return begin() == end(); 
}

4.2 size方法

统计大小只需要将list遍历一遍,统计节点个数即可

size_t size() const
{
   
    size_t cnt = 0;
    const_iterator it = begin();
    while (it != end())
    {
   
        ++cnt;
        ++it;
    }
    return cnt;
}

5. 数据访问

访问首尾数据只需要通过对应指针来返回对应值即可

T& front()
{
   
    return begin()._node->_data;
}

const T& front() const
{
   
    return begin()._node->_data;
}

T& back()
{
   
    return end()._node->_data;
}

const T& back() const
{
   
    return end()._node->_data;
}

6. 数据修改

6.1 insert方法

操作步骤:

  • pos位置前进行插入
  • 记录当前pos位置的节点和pos位置的上一个节点
  • 建立预插入节点和两位置的链接关系
void insert(iterator pos, const T& x)
{
   
    node* cur = pos._node; //pos位置节点
    node* prev = cur->_prev; //pos位置上一个节点

    node* new_node = new node(x);
    //建立链接关系
    prev->_next = new_node;
    new_node->_prev = prev;
    new_node->_next = cur;
    cur->_prev = new_node;
}

6.2 erase方法

操作步骤:

  • 首先判断list是否为空
  • 记录当前节点的上一个节点和下一个节点的位置
  • 将记录的两个节点建立链接关系,
  • 最后删除当前节点,返回已删除节点下一个节点
iterator erase(iterator pos)
{
   
    assert(pos != end());

    node* prev = pos._node->_prev; //pos位置上一个节点
    node* next = pos._node->_next; //pos位置下一个节点

    prev->_next = next;
    next->_prev = prev;
    delete pos._node;

    return iterator(next); //返回位置防止迭代器失效
}

注意:list的删除操作会存在迭代器失效的问题,这里记录返回节点的位置来解决此问题

6.3 头尾插入删除

这里头尾的插入删除操作可以直接复用insert()和erase()

void push_back(const T& x)
{
   
    insert(end(), x);
}

void push_front(const T& x)
{
   
    insert(begin(), x);
}

void pop_back()
{
   
    erase(--end());
}

void pop_front()
{
   
    erase(begin());
}

6.4 swap方法

list中的交换方法是直接交换两个对象的哨兵位头结点,效率很高

void swap(list<T>& tmp)
{
   
    std::swap(_head, tmp._head);
}

6.5 clear方法

遍历链表删除除了头结点外的所有节点

void clear()
{
   
    iterator it = begin();
    while (it != end())
    {
   
        //it = erase(it);
        erase(it++);
    }
}

7. 完整代码

#pragma once
#include <iostream>
#include<list>
#include <cassert>
using namespace std;

namespace sakura
{
   
    template<class T>
    struct list_node
    {
   
        list_node<T>* _next;
        list_node<T>* _prev;
        T _data;

        list_node(const T& x = T())
            :_next(nullptr)
            , _prev(nullptr)
            , _data(x)
        {
   }
    };

    //迭代器要么就是原生指针
    //迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为
    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* n) //begin()和end()中需要构造来返回节点指针
            :_node(n)
        {
   }

        Ref operator*()
        {
   
            return _node->_data;
        }

        Ptr operator->()
        {
   
            return &_node->_data;
        }
        //前置++
        self& operator++()
        {
   
            _node = _node->_next;
            return *this;
        }
        //后置++
        self operator++(int) //局部变量tmp出了作用域会销毁,不能使用引用返回
        {
   
            self tmp(*this);
            _node = _node->_next;

            return tmp;
        }
        //前置--
        self& operator--()
        {
   
            _node = _node->_prev;
            return *this;
        }
        //后置--
        self operator--(int)
        {
   
            self tmp(*this);
            _node = _node->prev;
            return tmp;
        }

        bool operator!=(const self& s)
        {
   
            return _node != s._node;
        }

        bool operator==(const self& s)
        {
   
            return _node == s._node;
        }
    };

    template<class T>
    class list
    {
   
        typedef list_node<T> node;
    public:
        typedef _list_iterator<T, T&, T*> iterator;
        typedef _list_iterator<T, const T&, const T*> const_iterator;

        iterator begin()
        {
   
            return iterator(_head->_next);
        }

        const_iterator begin() const
        {
   
            return const_iterator(_head->_next);
        }

        iterator end()
        {
   
            return iterator(_head);
        }

        const_iterator end() const
        {
   
            return const_iterator(_head);
        }

        void empty_init()
        {
   
            //初始化头结点
            _head = new node;
            _head->_next = _head;
            _head->_prev = _head;
        }

        list()
        {
   
            empty_init();
        }

        template<class Iterator>
        list(Iterator first, Iterator last)
        {
   
            empty_init();
            while (first != last)
            {
   
                push_back(*first);
                ++first;
            }
        }

        void swap(list<T>& tmp)
        {
   
            std::swap(_head, tmp._head);
        }

        list(const list<T>& lt)
        {
   
            empty_init();
            list<T> tmp(lt.begin(), lt.end());
            swap(tmp);
        }

        list<T>& operator=(list<T> lt)
        {
   
            swap(lt);
            return *this;
        }

        ~list()
        {
   
            clear();
            delete _head;
            _head = nullptr;
        }

        void clear()
        {
   
            iterator it = begin();
            while (it != end())
            {
   
                //it = erase(it);
                erase(it++);
            }
        }

        T& front()
        {
   
            return begin()._node->_data;
        }

        const T& front() const
        {
   
            return begin()._node->_data;
        }

        T& back()
        {
   
            return end()._node->_data;
        }

        const T& back() const
        {
   
            return end()._node->_data;
        }

        bool empty() const
        {
    
            return begin() == end(); 
        }

        size_t size() const
        {
   
            size_t cnt = 0;
            const_iterator it = begin();
            while (it != end())
            {
   
                ++cnt;
                ++it;
            }
            return cnt;
        }

        void push_back(const T& x)
        {
   
            //node* tail = _head->_prev;
            //node* new_node = new node(x);

            //tail->_next = new_node;
            //new_node->_prev = tail;
            //new_node->_next = _head;
            //_head->_prev = new_node;

            insert(end(), x);
        }

        void push_front(const T& x)
        {
   
            insert(begin(), x);
        }

        void pop_back()
        {
   
            erase(--end());
        }

        void pop_front()
        {
   
            erase(begin());
        }

        void insert(iterator pos, const T& x)
        {
   
            node* cur = pos._node; //pos位置节点
            node* prev = cur->_prev; //pos位置上一个节点

            node* new_node = new node(x);
            //建立链接关系
            prev->_next = new_node;
            new_node->_prev = prev;
            new_node->_next = cur;
            cur->_prev = new_node;
        }

        iterator erase(iterator pos)
        {
   
            assert(pos != end());

            node* prev = pos._node->_prev; //pos位置上一个节点
            node* next = pos._node->_next; //pos位置下一个节点

            prev->_next = next;
            next->_prev = prev;
            delete pos._node;

            return iterator(next); //返回位置防止迭代器失效
        }

    private:
        node* _head;
    };

    void print_list(const list<int>& lt)
    {
   
        list<int>::const_iterator it = lt.begin();
        while (it != lt.end())
        {
   
            cout << *it << " ";
            ++it;
        }
        cout << endl;
    }
}

C++【STL】之list模拟实现,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!

目录
相关文章
|
15天前
|
C++ 容器
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
47 21
|
2月前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
37 1
|
2月前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
61 7
|
2月前
|
存储 编译器 C++
C++ initializer_list&&类型推导
在 C++ 中,`initializer_list` 提供了一种方便的方式来初始化容器和传递参数,而右值引用则是实现高效资源管理和移动语义的关键特性。尽管在实际应用中 `initializer_list&&` 并不常见,但理解其类型推导和使用方式有助于深入掌握现代 C++ 的高级特性。
26 4
|
3月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
127 4
|
3月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
121 5
|
3月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
87 2
|
3月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
95 0
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
63 0
|
30天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
67 19