
 
文章目录
前言
在C++编程中,数据结构是理解和应用算法的基础。列表list作为线性数据结构的一种,它提供了有序的元素集合,支持高效的插入和删除操作。掌握list的基本使用和模拟实现,不仅有助于加深对数据结构的理解,也是提高编程能力的关键。
 本文将深入探讨list的基本概念、常见操作以及模拟实现方法。我们将从list的定义开始,逐步介绍如何使用标准库中的list类,并进一步探讨如何手动模拟实现一个简单的list数据结构。通过理论与实践的结合,帮助读者更好地理解和运用listt数据结构。
一.list的基本概念和使用
在C++中,list 是一种双向链表容器,属于标准模板库(STL)的一部分。
基本概念
-  定义: list是一个序列容器,它允许非连续内存分配,并通过双向链表来管理元素。这意味着list支持常数时间内的任意位置插入和删除操作,但随机访问元素的时间复杂度较高(因为要顺序查找)。
-  结构: list由一系列节点组成,每个节点包含数据部分(_val)和指向前一个(_prev)及后一个(_next)节点的指针。这种结构使得list在插入和删除操作时不需要移动大量数据,只需调整指针即可。
-  迭代器: list提供了双向迭代器,支持向前和向后遍历元素。但需要注意的是,list的迭代器不是随机访问迭代器(因为内存地址不是连续的),因此不支持通过加减整数来直接访问某个位置的元素,只能(++/–)。
基本操作使用
-  创建和初始化: -  使用默认构造函数创建一个空 list对象:std::list<int> mylist;
-  使用初始化列表创建 list:std::list<int> mylist={1,2,3,4,5};
 
-  
-  添加元素: -  在末尾添加元素: //mylist:1,2,3,4,5 //调用尾插函数push_back() mylist.push_back(6);
-  在开头添加元素: //mylist:1,2,3,4,5,6 //调用头插函数push_front() mylist.push_front(0);
-  在指定位置插入元素: //mylist:0,1,2,3,4,5,6 //获取begin位置的迭代器 std::list<int>::iterator it=mylist.begin(); //迭代器it++,指向下一个位置的迭代器 it++; //在迭代器it位置前,也就是元素1前插入10 //调用插入函数insert() it=mylist.insert(it.10);
 
-  
-  删除元素: -  删除指定位置的元素: //mylist:0,10,1,2,3,4,5,6 //删除第二个元素10 std::list<int>::iterator it=mylist.begin(); it++; //调用删除函数erase() it=erase(it);
-  删除第一个元素: //mylist:0,1,2,3,4,5,6 //调用头删函数pop_front mylist.pop_front();
-  删除最后一个元素: //mylist:1,2,3,4,5,6 //调用尾删函数pop_back mylist.pop_back();
-  清空整个 list://调用清空函数clear() mylist.clear();
 
-  
-  访问元素: - 由于list不是随机访问容器,因此不能通过索引直接访问元素。但可以使用迭代器遍历list来访问元素:std::list<int>::iterator it=mylist.begin(); while(it!=mylist.end()){ cout<<*it<<" " } cout<<endl;
 
- 由于
-  大小和容量: -  获取 list中元素的数量://调用size()函数 size_t sz=mylist.size();
-  判断 list是否为空://调用empty()函数 bool ret=mylist.empty();
  注意:list和string,vector这两个类不同的是,list的大小也就是存储的元素个数就是容量,不再区分,size和capacity 
-  
-  排序和查找: -  对 list进行排序://list类中有自己的sort()函数,也可以使用算法库函数里面的 mylist.sort();
-  查找元素(返回迭代器): //list类中没有find()函数,所以只能有算法库函数里面的 //查找值为4的迭代器位置 auto it=mylist.find(mylist.begin(),mylist.end(),4);
 
-  
二.模拟实现list
上面我们了解完了如何使用list,接下来,我们将自己模拟实现一个属于自己的list,模拟实现可以帮助我们更好地了解list的底层结构,让我们更好的使用和理解list这个数据结构,而list作为一个双向循环链表,实现一个基本的list需要分别设计三个不同的类,节点类(_list_node),用来封装节点结构;链表类(list),用来封装链表的成员函数和链表结构;迭代器类(_list_iterator),用来封装迭代器。
注意:list类和vector类,string类不同的是,list类需要对迭代器单独使用一个类进行封装,主要原因是因为它们的底层数据结构特性和访问方式的不同
-  list的底层数据结构: - list是基于链表实现的,链表的节点在内存中的存储位置是不连续的。
- 为了在链表中实现顺序访问(即,通过迭代器逐一访问链表中的元素),需要设计一个迭代器类来封装链表节点的指针(可以理解为,迭代器类中的成员变量表示的是一个节点的指针),并通过运算符重载(如++、--、*等)来模拟指针的行为。
- 迭代器类内部维护一个指向当前节点的指针,并通过这些运算符重载来移动到下一个或上一个节点,或访问当前节点的数据。
 
-  vector和string的底层数据结构: - vector和- string是基于数组实现的,它们的元素在内存中是连续存储的。
- 由于数组元素的连续性,可以直接使用原生指针(即数组元素的地址)作为迭代器,而无需额外的封装。
- 原生指针自然支持顺序访问(通过指针加减来访问相邻元素),因此vector和string的迭代器本质上就是原生指针。
 
模拟实现的整体框架:
namespace Mylist{
    template<class T>
    struct _list_node{
        //.....
    };
    
    //迭代器类封装
    template<class T,class Ref,class Ptr>
    class _list_iterator{
        //.....
    };
    
    //链表类封装
    template<class T>
    class list{
        //.....
    };
}
1.节点类(_list_node)模拟实现
-  代码实现: template<class T> struct _list_node{ //成员变量 _list_node<T>* _prev; _list_node<T>* _next; T _val; //构造函数 _list_node(const T& val=T()) :_prev(nullptr) ,_next(nullptr) ,_val(val) {} };
-  实现原理: - 定义三个成员变量表示一个节点的结构:指向前一个节点的指针_prev,指向后一个节点的指针_next,存储的数据_val
- 构造函数初始化时,两个指针都设置为空指针,参数val=T()表示创建的匿名对象(在上一篇vector的模拟实现中有用到,不理解的可以看上一篇文章),自定义类型不如string类型调用对应的构造函数,内置类型也可以调用构造函数初始化。
 
- 定义三个成员变量表示一个节点的结构:指向前一个节点的指针

2.迭代器类(_list_iterator)模拟实现
基本框架
-  代码实现: 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) {} //....其他成员函数 };
-  实现原理: - _list_iteartor是一个模板类,模版参数有三个,这里参数T比较好理解,表示的是存储的数据类型,而后面两个比较难理解,这里先不做解释,在后面讲到相关函数时在详细解释。
- 用typedef对两个类类型名重定义(方便实用),注意,模板类和普通的类有一点不同,普通类,类名就是类型;而模版类,类名加上模版参数才是类型。
- 成员变量是一个节点的指针,构造函数将参数节点指针初始化为迭代器(其实本质上还是指针,因为迭代器就是一种指针类型)。
 
成员函数
1.operator++函数:
-  代码实现: //前置++ self& operator++(){ _node=_node->_next; return *this; } //后置++ self operator++(int){ self tmp(*this); _node=_node->_next; return tmp; }
-  实现原理: - 因为list的迭代器不能直接加减常数,所以只能++或–,而++又分为前置++和后置++
- 前置++,迭代器本身需要+1,并且返回的是+1后的迭代器,直接让当前节点指针指向下一个节点即可,返回类型是self&(_list_iterator<T,Ref,Ptr>&)
- 后置++,拷贝构造一个新的迭代器用来保存+1前的迭代器,对原本的迭代器+1,返回保存的迭代器,返回类型时self(_list_iterator<T,Ref,Ptr>),因为保存的迭代器是一个局部变量,函数调用结束会销毁,所以不能用引用类型的返回。
- 后置++参数中的int是用来和前置++构成函数重载,进行区分
 
2.operator--函数:
-  代码实现: //前置-- self& operator--(){ _node=_node->_prev; return *this; } //后置-- self operator--(int){ self tmp(*this); _node=_node->_prev; return tmp; }
-  实现原理: - –又分为前置–和后置–
- 前置–,迭代器本身需要-1,并且返回的是-1后的迭代器,直接让当前节点指针指向前一个节点即可,返回类型是self&(_list_iterator<T,Ref,Ptr>&)
- 后置–,拷贝构造一个新的迭代器用来保存-1前的迭代器,对原本的迭代器-1,返回保存的迭代器,返回类型时self(_list_iterator<T,Ref,Ptr>),因为保存的迭代器是一个局部变量,函数调用结束会销毁,所以不能用引用类型的返回。
- 后置–参数中的int是用来和前置–构成函数重载,进行区分
 
3.operator!=函数:
-  代码实现: bool operator!=(const selt& it){ return _node!=it._node; }
-  实现原理: - 左参数迭代器和右参数迭代器判断是否不等,左参数迭代器是this,右参数迭代器是it
- 直接返回两个迭代器是否是同一个节点的指针
 
4.operator*函数:
-  代码实现: T& operator*(){ return _node->_val; } const T& operator*(){ return _node->_val; } //使用模版参数合二为一 Ref operator*(){ return _node->_val; }
-  实现原理: - 首先迭代器是一个指向节点的指针,对其解引用*,表示获取节点中的数据域(_val)存储的数据
- 返回节点中的数据域(_val),返回类型是T&
- 这里有一个注意点,如果是对const类型的迭代器解引用*,直接将返回类型写成T&就会导致const类型的无法调用该函数,写成const T&才可以调用,所以为了解决这个问题,引入了第二个模版参数Ref,用来自动替换这两个不同的类型。
 
- 首先迭代器是一个指向节点的指针,对其解引用*,表示获取节点中的数据域(
5.operator->函数:
-  代码实现: T* operator->(){ return &_node->_val; } const T* operator->(){ return &_node->_val; } //使用模版参数合二为一 Ptr operator->(){ return &-node->_val; }
-  实现原理: - ->运算符重载和- *不同的是,- operator*,返回的是节点中存储的数据_val的地址,返回类型是T*类型
- 和operator*一样,如果有const类型的迭代器使用->,返回类型直接写成T*会导致const类型的无法调用该函数,写成const T*类型才可以调用,为了解决这个问题,引入了第三个模版参数Ptr,用来自动替换这两个不同的类型。
 
3.链表类(list)模拟实现
基本框架
- 代码实现:
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;
    
    //...成员函数
    
    private:
    //成员变量
    node* _head; 
    size_t _size;
};
- 实现原理: 
  - 将节点类类型重定义,设置成私有,迭代器类类型重定义,设置成公有
- 成员变量声明一个哨兵节点,声明一个大小用来记录链表的节点个数
 
默认成员函数
1.构造函数代码实现:
void empty_init(){
    _head=new node;
    _head->_next=_head;
    _head->_prev=_head;
    _size=0;
}
//构造函数
list(){
    empty_init();
}
- 实现原理: 
  - 用一个函数将初始化的内容分开存放,这样方便拷贝构造函数直接调用
- 定义一个哨兵节点,该节点的前节点指针和后节点指针都指向该节点,将大小初始化为0
 

2.拷贝构造函数代码实现:
list(const list<T>& lt){
    empty_init();
    for(auto& e:lt){
        push_back(e);
    }
}
- 实现原理: 
  - 先用函数empty_init()函数初始化一个空链表,也就是被拷贝的链表
- 用范围for循环,将被拷贝的链表中的节点数据依次插入到拷贝的链表中完成拷贝
 
- 先用函数
3.赋值运算符重载:
swap(list<T>& lt){
    std::swap(_head,lt._head);
    std::swap(_size,lt._size);
}
list<T>& operator=(list<T> lt){
    swap(lt);
    return *this;
}
- 实现原理: 
  - 用一个自己实现的交换函数,交换两个链表的哨兵节点和大小,就能达到赋值的效果
- 最后将被赋值的链表返回,返回类型是链表类类型list<T>,返回要引用返回
 
4.析构函数:
void clear(){
    auto it=begin();
    while(it!=end()){
        it=erase(it);
    }
    _size=0;
}
~list(){
    clear();
    delete _head;
    _head=nullptr;
}
- 实现原理: 
  - 用一个自己实现的清空函数,通过迭代器来依次删除链表中的每个节点,再讲大小置为0
- 析构函数调用清空函数后,在通过delete函数,释放哨兵节点,最后置为空指针
 
迭代器成员函数
1.begin()函数代码实现:
//普通对象类型的迭代器
iterator begin(){
    return _head->_next;
}
//const对象类型的迭代器
const_iterator begin()const {
    return _head->_next;
}
-  实现原理: 直接返回哨兵节点的下一个节点即可 
2.end()函数代码实现:
//普通对象类型的迭代器
iterator end(){
    return _head;
}
//const对象类型的迭代器
const_iterator end()const {
    return _head;
}
-  实现原理: 直接返回哨兵节点即可 

插入成员函数
1.insert()函数代码实现:
 iterator insert(iterator pos const T& x){
     node* newnode=new node(x);
     node* cur=pos._node;
     node* prev=cut->_prev;
     
     prev->_next=newnode;
     newnode->_next=cur;
     cur->_prev=newnode;
     newnode->_prev=prev;
     _size++;
     return pos
    
 }
- 实现原理: 
  - 先创建需要插入的新节点newnode,然后找pos迭代器指向的当前节点cur,再找到pos当前指向的节点的前节点prev
- 将新节点插入到节点cur和prev中间即可
 

2.push_back()尾插函数代码实现:
void push_back(const T& x){
    //相当于在哨兵节点前插入
    insert(end(),x);
}
3.push_front()头插函数代码实现:
void push_front(const T& x){
    //相当于在第一个节点前插入,也就是哨兵节点的后一个节点
    insert(begin(),x);
}
删除成员函数
1.erase()删除函数代码实现:
iteraotr erase(iterator pos){
    assert(pos!=end());
    node* cur=pos._node;
    node* prev=cur->_prev;
    node* next=cur->_next;
    
    prev->_next=next;
    next->_prev=prev;
    return next;
}
- 实现原理: 
  - 首先断言判断pos是否是指向哨兵节点的迭代器,因为不能将哨兵节点删除
- 找到当前迭代器pos指向的节点,再找到前节点prev,后节点next
- 将pos指向的节点删除,返回next指向的节点,也就是删除节点的下一个节点
 

2**pop_back()尾删函数代码实现:**
void pop_back(){
    //相当于删除最后一个节点,也就是哨兵节点的前一个节点
    erase(--end);
}
3.pop_front()头删函数代码实现:
void pop_front(){
    //相当于删除第一个节点,也就是哨兵节点的后一个节点
    erase(begin());
}
三.模拟实现完整代码
list.h文件
 
#include<iostream>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace Mylist
{
    //节点类封装
    template<class T>
    struct list_node
    {
        //成员变量,前节点指针,后节点指针,存储的数据val
        list_node<T>* _next;
        list_node<T>* _prev;
        T _val;
        //构造函数  创造节点
        list_node(const T& val=T())
        :_next(nullptr)
        ,_prev(nullptr)
        ,_val(val)
        {}
    };
    //迭代器类封装
    template<class T,class Ref,class Ptr>
    struct _list_iterator
    { 
        typedef _list_iterator<T,Ref,Ptr> self;
        typedef list_node<T> node;
        //成员变量 一个节点的指针
        node* _node;
        //构造函数 将传入的节点指针变为迭代器
        _list_iterator(node* node)
        :_node(node)
        {}
        Ref operator*(){
            return _node->_val;
        }
        Ptr operator->(){
            return &_node->_val;
        }
        bool operator!=(const self& it){
            return _node!=it._node;
        }
        self& operator++(){
            _node=_node->_next;
            return *this;
        }
        self operator++(int){
            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;
        }
    };
    //链表类封装
    template<class T>
    class list
    {
        typedef list_node<T> node;
        void empty_init(){
             _head=new node;
            _head->_next=_head;
            _head->_prev=_head;
            _size=0;
        }
    public:
        //重定义迭代器类型
        typedef _list_iterator<T,T&,T*> iterator;
        typedef _list_iterator<T,const T&,const T*> const_iterator; 
        iterator begin(){
            //隐式类型转换,相当于调用_list_iterator()构造函数,创建迭代器类型的对象返回
            return _head->_next;          
        }
        iterator end(){
            return _head;
        }
        const_iterator begin()const {
            return _head->_next;
        }
        const_iterator end()const {
            return _head;
        }
        
        //构造函数 创建带哨兵节点的链表
        list(){
            cout<<"list()"<<endl;
            empty_init();
        }
        //拷贝构造函数
        list(const list<T>& lt){
            empty_init();
            for(auto& e:lt){
                push_back(e);
            }
        }
        void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}
        //赋值运算符重载
        list<T>& operator=(list<T> lt){
            swap(lt);
            return *this;
        }
        void clear(){
            auto it=begin();
            while(it!=end()){
            it=erase(it);
            }
            _size=0;
        }
        //析构函数
        ~list(){
            cout<<"~list()"<<endl;
            clear();
            delete _head;
            _head=nullptr;
        }
        //插入
        iterator 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;
            _size++;
            return pos;
        }
        //尾插
        void push_back(const T& x){
           /* node* newnode=new node(x);
            node*prev=_head->_prev;
            prev->_next=newnode;
            newnode->_next=_head;
            newnode->_prev=prev;
            _head->_prev=newnode;
            _size++;*/
            insert(end(),x);
        }
        //头插
        void push_front(const T& x){
            insert(begin(),x);
        }
        //删除
        iterator erase(iterator pos){
            assert(pos!=end());
            node* cur=pos._node;
            node* prev=cur->_prev;
            node* next=cur->_next;
            prev->_next=next;
            next->_prev=prev;
            _size--;
            return next;
        }
        //尾删
        void pop_back(){
            //这里end()要先减后用,所以有前置--
            erase(--end());
        }
        //头删
        void pop_front(){
            erase(begin());
        }
        size_t size()const {
            return _size;
        }
    private:
        //成员变量 哨兵节点
        node* _head;
        size_t _size;
    };
    //测试用例类
    class A{
        public:
        A(int a1=0,int a2=0)
        :_a1(a1)
        ,_a2(a2)
        {}
        int _a1;
        int _a2;
    };
}
test.cpp文件
 
#include"list.h"
void test1()
{
    Mylist::list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_front(5);
    lt.push_front(6);
    lt.push_front(7);
    lt.push_front(8);
    cout<<lt.size()<<endl;
    Mylist::list<int>::iterator it=lt.begin();
    while(it!=lt.end()){
        cout<<*it<<" ";
        it++;
    }
    cout<<endl;
    lt.pop_back();
    lt.pop_front();
    for(auto e:lt){
        cout<<e<<" ";
    }
    cout<<endl;
    cout<<lt.size()<<endl;
    lt.clear();
    cout<<lt.size()<<endl;
}
void test2(){
    Mylist::list<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    Mylist::list<int> v2(v1);
    Mylist::list<int> v3;
    v3=v1;
    for(auto e:v2){
        cout<<e<<" ";
    }
    cout<<endl;
    for(auto e:v3){
        cout<<e<<" ";
    }
    cout<<endl;
}
void test3(){
    Mylist::list<Mylist::A> lt;
    lt.push_back(Mylist::A(1,1));
    lt.push_back(Mylist::A(2,2));
    lt.push_back(Mylist::A(3,3));
    lt.push_back(Mylist::A(4,4));
    Mylist::list<Mylist::A>::iterator it=lt.begin();
    while(it!=lt.end()){
        cout<<it->_a1<<","<<it->_a2<<" ";
        it++;
    }
    cout<<endl;
    it=lt.begin();
    while(it!=lt.end()){
        cout<<(*it)._a1<<","<<(*it)._a2<<" ";
        it++:
    }
    cout<<endl;
}
int main()
{
    //test1();
    //test2();
    test3();
    return 0;
}
以上就是关于list的基本使用和模拟实现的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
 










