C++中list的简单实现

一、模拟实现

注意,这里的list是带头结点的双链表

1. 文档查看

Cpulsplus网站
在这里插入图片描述
cppreference网站
在这里插入图片描述

2. 实现部分

因为模拟实现,如果展开std命名空间就会出现名字冲突
因此可以用命名空间囊括起来

// List的节点类
    template<class T>
    struct ListNode
    {
        ListNode(const T& val = T());
        ListNode<T>* _pPre;
        ListNode<T>* _pNext;
        T _val;
    };


    //List的迭代器类
    template<class T, class Ref, class Ptr>
    class ListIterator
    {
        typedef ListNode<T>* PNode;
        typedef ListIterator<T, Ref, Ptr> Self;
    public:
        ListIterator(PNode pNode = nullptr);
        ListIterator(const Self& l);
        T& operator*();
        T* operator->();
        Self& operator++();
        Self operator++(int);
        Self& operator--();
        Self& operator--(int);
        bool operator!=(const Self& l);
        bool operator==(const Self& l);
    private:
        PNode _pNode;
    };

    //list类
    template<class T>
    class list
    {
        typedef ListNode<T> Node;
        typedef Node* PNode;
    public:
        typedef ListIterator<T, T&, T*> iterator;
        typedef ListIterator<T, const T&, const T&> const_iterator;
    public:
        ///
        // List的构造
        list();
        list(int n, const T& value = T());
        template <class Iterator>
        list(Iterator first, Iterator last);
        list(const list<T>& l);
        list<T>& operator=(const list<T> l);
        ~list();


        ///
        // List Iterator
        iterator begin();
        iterator end();
        const_iterator begin();
        const_iterator end();


        ///
        // List Capacity
        size_t size()const;
        bool empty()const;


        
        // List Access
        T& front();
        const T& front()const;
        T& back();
        const T& back()const;


        
        // List Modify
        void push_back(const T& val) { insert(end(), val); }
        void pop_back() { erase(--end()); }
        void push_front(const T& val) { insert(begin(), val); }
        void pop_front() { erase(begin()); }
        // 在pos位置前插入值为val的节点
        iterator insert(iterator pos, const T& val);
        // 删除pos位置的节点,返回该节点的下一个位置
        iterator erase(iterator pos);
        void clear();
        void swap(list<T>& l);
    private:
        void CreateHead();
        PNode _pHead;
    };
以下模拟实现需要自己查看文档,我根据自己所需要和方法传参、返回值来实现功能
这里就不展示文档了,不知道方法怎么实现就看上面的方法

二、开始模拟实现

顺序看需求定

1. 初始结构

在这里插入图片描述

1、这里把LisiNode分离出去当一个结构体,到后面插入/删除好申请和销毁空间
2、这里在list类里使用typedef ListNode<T> Node;(改一个名字,如果重复利用的话就比较方便)
3、实现了一个方法,用于创建头结点
4、实现默认构造器,自动生成头结点,让_size初始为0
5、结构体指针_pHead指向头结点
6、声明_size,记录结点个数(用于后面计算返回大小)

直到现在才发现,我们插入是依靠迭代器实现的

这时我们需要想到一个问题,如果使用指针,迭代器的加加减减怎么实现
例如:
list<int> ls;
list<int>::iterator lt = ls.begin();
while(lt != ls.end())
{
	lt++;
}
lt如果使用原生指针加加,怎么能加加就跳到下一个结点(毕竟链表是有val、next、prev三个成员属性的)
链表结点在这里也是一个结构体(类也行),加加结构体指针能到下一个结构体,那不现实

那就:
那迭代器分离出去,独自实现运算符重载,每次定义一个迭代器变量就算是实例化一个类对象
这样就能实现加加减减了,为啥这样?
因为如果是指针,想到运算符重载加加减减方法?指针是内置类型,无法运算符重载重载

如果在类里面定义一个结构体指针,调用类的运算符重载?
这样子是脱离类了,你的结构体指针加加怎么可能调用的是类里的运算符重载
为啥之前的vector迭代器可以?
(因为人家本来就脱离类的,连续空间对于指针的加加就是下一个位置)

2. 迭代器实现

a. 迭代器类初始结构

在这里插入图片描述

看着上面需要的方法实现:
1、迭代器也是管理一个指向结构体的指针,但我把指针包装成类,因为我需要加加到下一个结点
2、两个typedef是为了方便,第一个是指向结点的指针,第二个就是自己类重命名(为了方便后面返回迭代器)
3、构造函数,使用的是外部结构体地址(指针)给本迭代器赋值,让迭代器指向结点
3、拷贝构造,防止隐式类型转换
4、声明结构体指针(可以说整个类都是为了管理这个指针)

b. 迭代器最后规模

在这里插入图片描述

管理一个结构体指针的类,迭代器需要支持加加减减,可以根据运算符重载走向下一个结点
在这里得理解,为什么管理一个指针需要独立出来一个类
1、如果只是单纯的结构体指针放进list类里,那么它的加加减减就会使用的是指针的加加减减
(因为结点不是连续的,因此会非法访问)
2、为啥不会调用list类里重载的加加减减?因为这时指针算是独立在类外面了,并且定义的对象才能用重载的加加减减
简单来说就是单纯走了指针加加减减的道路,不是对象,不能调用类里运算符重载的加加减减
里面的方法很常见,通常不理解就是:
1、对类和结点之间指针的不融洽,比如:
	a. 构造函数参数是外面传进来的地址,类管理就需要类里面的属性接收地址
	b. 拷贝构造是为了下面的(中间变量)对象,比如后置加加需要返回的是没更新前的值
	c. 返回的T*和T&,T*是我需要迭代器(指针)里的结点的地址,T&是需要结点,这里重载的是解引用和指向
2、无法从管理指针转换成管理类
	a. 加加减减的运算符重载,需要的是指针,意思是需要的是迭代器
	(这时迭代器是类,所以返回的是对象,毕竟可以抛弃底层把迭代器看成指针)
	b. 不等于与等于运算符重载,就是为了比较指针指向的结点是否相同

c. 测试自定义类型Date的->指向功能

这是完成了下面的插入才好做,不然没结点咋看
在这里插入图片描述
在这里插入图片描述
好玩吧!!!

这里玩的比较奇怪,但如果不理解可能也觉得可以,但如果带入->运算符重载呢
是迭代器类对象,这里走了->运算符重载,如operator->()
这里看应该是:(对象->)得出来的是一个地址,但为什么地址紧接着Date里面的属性就能引用了呢?
如同:地址属性(地址紧接属性)

其实这里编译器简化了一个指向,本来是:
迭代器.operator->()->_day
如果以正常思维就是
迭代器->->_day
优化了之后就好看还好理解,我不用理解底层就能直接找结构体里面的值:
迭代器->_day

3. begin()、end()

在这里插入图片描述
在这里插入图片描述

因为迭代器没有实现const版本,因此这里begin、end先只实现普通版本
这里最重要的思维就是走了隐式类型转换,主要过程:
1、typedef,把类类型改名成迭代器(记住,这个迭代器类是为了管理一个结构体指针)
2、begin是头结点的下一个结点,end就是头结点
3、return的是指针(地址),但返回类型是iterator(迭代器),迭代器里的构造函数是传结点地址
(结点指针与迭代器类类型不匹配,但是节点指针可以实例化成迭代器,因此在返回的时候转换成迭代器了)
想不明白吗?
看string s1 = "abcdef";
编译器不优化的时候,先把后面字符串转换成string对象,再走拷贝构造给s1,上面的原理也一样,只不过形式有点改变
也因为begin、end返回时是匿名对象,然后被外面迭代器对象接收

4. insert()、push_back()、push_front()

在这里插入图片描述

insert是在该迭代器的前一个位置插入结点(这里是迭代器对象的指针指向的结点之前插入)
这里有细节:
1、如果迭代器里的_pNode是私有成员属性的话,在insert里面就无法访问
(因此可以是公有成员属性,当然也可以用方法来获取,但迭代器不用那么繁琐)
2、insert可以在链表任意插入结点,这里只需要申请一个新节点就好了
3、可以先使用指针来指向各个结点,这样子连接起来方便
4、因为insert可以在任何地方插入,因此头插尾插都可以复用insert方法
(begin是第一个结点的迭代器,在前插入就是头插,end是头结,在它前面插入就是尾插)
5、返回pos位置的迭代器用于更新迭代器

5. erase()、pop_back()、pop_front()

在这里插入图片描述

erase是删除迭代器所指向的结点,主要过程思维是:
1、断言不让迭代器对象指向的结点是头结点
2、用结构体指针(结点指针)指向迭代器对象指向的结点
(还是得知道,迭代器对象是管理结构体指针的)
3、定义两个指针分别指向需要删除的结点的前一个结点和后一个节点
4、让前后两个节点连接起来
5、释放需要删除的结点,并把指针置空(规范化)
6、记录结点个数的_size减1
7、返回后一个结点的指针,因为走了隐式类型转换,所以在外面是接收迭代器对象
(迭代器类是以结点地址(指针)实例化的,因此结点地址是可以隐式类型转换的)

pop_back()和pop_from()是复用Insert,主要删除头结点的前一个结点和后一个节点

6. swap()

在这里插入图片描述

这里也主要是交换两个对象内容,指向头结点的指针内容交换和大小交换
这里链表是不需要变动的,只要两个对象管理的结点指针指向交换就好了

7.size()、empty()、clear()

在这里插入图片描述

1、size()可以利用之前的属性_size返回,里面记录着大小,这是用空间换时间的思维
2、empty()直接判断_size是否是0来返回真假
(这两个const修饰是为了const对象和普通对象都能使用)
3、clear()是除了头结点之外的结点都删除,需要遍历,可以自己手搓,也可以复用erase来删除,_size来判断是否还有结点删除

8. front()、back()

在这里插入图片描述

这里主要是不理解返回的T&或const T&
可以看实例化时的list<int>,这时的T就是int
这时可以思考,怎么返回,如果返回pre或next就会发现,指针不是int,那就说明不是结构体指针
如果解引用,那也是结构体ListNode,并不是int,这时可以往结构体里面看,存在属性val是T类型
所以这里返回的是结构体里面存的值
这里const对象和普通对象一样的返回,会隐式类型转换

9. 完善const迭代器

以上都是没有实现const迭代器的,如果是const对象,那么就会出现const对象的结点被修改的问题
根据上面需要完善的list类就大概知道,const迭代器是在原有的迭代器类上修改的
因此从几个不同方向改动:

a. 迭代器类

在这里插入图片描述
在这里插入图片描述

这里把类模版难度加深了一下,后面两个是传引用和指针,这时就比较灵活
1、假设传的是普通引用和普通指针,那么下面解引用和指向返回类型也是普通类型
2、假设传的是const引用和const指针,那么下面两个函数就返回const类型
(这类型是结构体存储的数据类型,因为迭代器解引用和指向返回都是需要结构体所存储的数据或地址)
这时就可以实现普通迭代器和const迭代器
(const迭代器和普通迭代器的区别就是能不能改变结构体存储的内容)

b. list类的改动

在这里插入图片描述

改动上,之前typedef的迭代器加上了T&和T*的指定,这样可以和const迭代器区分
1、加多了typedef迭代器类成const_iterator
2、加多两个const对象对应的返回const_iterator的方法begin、end
(这里方法后面用const修饰是为了重写方法和对应const对象)
不然不小心使用了(假设是const对象ls)list<int>iterator lt = ls.begin();
这里ls是const对象,假设调用了非const函数,这里就权限放大了,编译器不允许
假设有了const版本的begin、end,如果没有const_iterator对应,那在迭代器类里也权限放大了,妥妥报错
(毕竟上面解引用运算符重载返回的是内容的引用,如果没有const修饰,那么外部就能修改了)

10. 完善构造函数

a. n个value构造

在这里插入图片描述

使用n个value构造:
这里参数列表里使用的T()可以算一个跟随类型自动生成初始值,如指针就nullptr,int就是0,还有其他默认值
1、先建立头结点(这里把_size放到CreateHead里面置0了)
2、根据n来循环的push_back(value);反正都是同一个值,可以直接乱插入
(当然也可以insert或push_front)

b. 迭代区间构造

在这里插入图片描述

根据迭代区间初始化对象:
1、因为可能是不同类型的迭代器,不能用list的迭代器定死这个构造函数,因此是函数模版
2、CreateHead();生成头结点
3、while循环从first到last区间取值,结束条件是first==last,循环条件相反
(这里别人的迭代器解引用、加加减减是人家迭代器实现的,可以走)

c. 拷贝构造

在这里插入图片描述

拷贝构造的函数都是类似这种结构的,也只能走引用(应该构造那个有讲)
这里直接使用迭代器循环遍历,把值直接push_back();进要初始化的对象里
(这里也可以使用范围for)

d. 列表初始化构造

在这里插入图片描述

用列表初始化,例如:list<int> ls = { 1,2,3,4,5 };
这个隐式类型转换也是一个对象,有自己的迭代器
因此可以在创建头结点之后直接用范围for然后push_back();
(这里只能push_back();,因为遍历这个列表也是从头遍历,从后面插入数据就会顺序相同)

e. 赋值重载

在这里插入图片描述

赋值重载这里走现代方式,s1 = s2;
走赋值重载,可以直接用一个中间对象(中间变量)生成一份和s2相同的
然后使用swap互换两个的指向内容,之后可以直接返回*this(因为this是当前对象的指针,用着没感觉而已)
为啥不用释放这个中间对象l,因为它的生命周期就只在这个方法内,出了作用域,生命周期结束自动调用析构函数

f. 析构

在这里插入图片描述

析构这里调用了clear清除结点(因为里面也是erase删除)
如果自己手搓循环变量释放也是可以的,注意防止野指针就好
清除完结点之后释放头结点,让_pHead置空
(这里在clear或erase已经让_size变动了,不清零也行)
如果是自定义类型,delete释放的时候会调用它的析构函数

总结

建议实现前先使用一下容器库的list里面的方法,理解使用方法就好实现

这里上面的实现可能会有小问题,但我一边实现一边总结,我找不到
(CreateHead那里new Node那里不能放值,容易出错,我已经把-1删了,因为不一定是整型那些内置类型)
这里可以说是一步一步走了,缺什么补什么,不缺的放后边实现,这样子可以减少分支
以上就是这里list的实现了,简单实现一下就行了,这里重要的是理解迭代器,迭代器不一定是指针,这里变成管理指针的类了

加油吧!少年
在这里插入图片描述

  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STL(Standard Template Library)是C++的一个标准库,其包含了许多常用的数据结构和算法。其list是一种常用的数据结构,它是一个双向链表,可以实现快速的插入和删除操作。 下面是C++STL list简单实现: ```cpp template <typename T> class List { private: struct Node { T data; Node* prev; Node* next; Node(const T& d = T(), Node* p = nullptr, Node* n = nullptr) : data(d), prev(p), next(n) {} }; Node* head; Node* tail; int size; public: List() : head(nullptr), tail(nullptr), size(0) {} List(const List& other) : head(nullptr), tail(nullptr), size(0) { for (const auto& item : other) { push_back(item); } } ~List() { clear(); } List& operator=(const List& other) { if (this != &other) { List tmp(other); swap(tmp); } return *this; } void push_back(const T& item) { Node* new_node = new Node(item, tail, nullptr); if (tail) { tail->next = new_node; } else { head = new_node; } tail = new_node; ++size; } void push_front(const T& item) { Node* new_node = new Node(item, nullptr, head); if (head) { head->prev = new_node; } else { tail = new_node; } head = new_node; ++size; } void pop_back() { if (tail) { Node* tmp = tail; tail = tail->prev; if (tail) { tail->next = nullptr; } else { head = nullptr; } delete tmp; --size; } } void pop_front() { if (head) { Node* tmp = head; head = head->next; if (head) { head->prev = nullptr; } else { tail = nullptr; } delete tmp; --size; } } T& front() { return head->data; } T& back() { return tail->data; } bool empty() const { return size == 0; } int size() const { return size; } void clear() { while (!empty()) { pop_back(); } } class iterator { private: Node* ptr; public: iterator(Node* p = nullptr) : ptr(p) {} iterator(const iterator& other) : ptr(other.ptr) {} iterator& operator++() { ptr = ptr->next; return *this; } iterator operator++(int) { iterator tmp(*this); ptr = ptr->next; return tmp; } iterator& operator--() { ptr = ptr->prev; return *this; } iterator operator--(int) { iterator tmp(*this); ptr = ptr->prev; return tmp; } T& operator*() const { return ptr->data; } T* operator->() const { return &(ptr->data); } bool operator==(const iterator& other) const { return ptr == other.ptr; } bool operator!=(const iterator& other) const { return ptr != other.ptr; } }; iterator begin() const { return iterator(head); } iterator end() const { return iterator(nullptr); } iterator rbegin() const { return iterator(tail); } iterator rend() const { return iterator(nullptr); } void swap(List& other) { std::swap(head, other.head); std::swap(tail, other.tail); std::swap(size, other.size); } }; ``` 上述代码List定义了一个私有结构体Node,它表示双向链表的每个节点。Node包含了数据成员data、指向前一个节点的指针prev和指向后一个节点的指针next。List还包含了头指针head、尾指针tail和大小size等数据成员。 List类提供了许多操作,包括push_back、push_front、pop_back、pop_front、front、back、empty、size、clear等。其,push_back和push_front分别表示在双向链表的末尾和开头插入一个元素,pop_back和pop_front分别表示删除双向链表的末尾和开头的元素,front和back分别表示获取双向链表的第一个和最后一个元素,empty表示判断双向链表是否为空,size表示获取双向链表元素的个数,clear表示清空双向链表的所有元素。 List类还定义了一个迭代器iterator,它可以用于遍历双向链表的所有元素。迭代器包含了一个指向Node的指针ptr,它可以指向双向链表的任意一个节点。迭代器提供了许多操作,包括++、--、*、->、==、!=等。其,++和--分别表示迭代器向前和向后移动一个位置,*表示获取迭代器所指向节点的数据成员data,->表示获取迭代器所指向节点的数据成员data的指针,==和!=分别表示判断两个迭代器是否相等和不相等。 List还提供了一些其他操作,比如拷贝构造函数、析构函数、赋值运算符、begin、end、rbegin、rend和swap等。其,拷贝构造函数用于复制另一个List对象,析构函数用于释放所有节点的内存,赋值运算符用于将一个List对象赋值给另一个List对象,begin和end分别返回指向双向链表第一个元素和最后一个元素的迭代器,rbegin和rend分别返回指向双向链表最后一个元素和第一个元素的迭代器,swap用于交换两个List对象的所有数据成员。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值