C++ STL list容器深究

前言

不知不觉已经二月中旬了,但是这个疫情依然没有结束。这一个月我的感受十分强烈,原来自由是那么珍贵,以为生活在非战争年代,被憋在家里日子永远不会出现。但是这个疫情让我知道了,自由也是相对的,是有人牺牲了自己的自由才换来的我们自由。这个人就是许许多多工作在平凡岗位的工作者们。
正好在家没有什么事情,就把珍藏在B站上的侯捷老师STL课程拿出来看了看。看视频还是很好理解的,但是能够深入了解其中的原理还是有所困难。俗话说:检验自己最好的方法就是教会别人。所以就有这一篇博客,今天主要是关于list的原码,这个原码是网上找到的,我在文末也会将这个原码附在最后。

list有什么特点

在C语言中我们就学过数组,但是这个数组只能将我们已知的同种类的元素放到一起。但是这个数组存在很多问题,比如出现访问越界的时候就会报错,比如空间一次性分配不能再改变等等问题。渐渐的我们也会学到数据结构,这个里面就讲到了链表的优点。但是这个链表的优点很突出突出,但是缺点也同样致命。它适合与经常插入删除的数据结构。同样的list也具有这样的功能。
在这里插入图片描述
在这里插入图片描述
插入的过程只需要O(1)的时间复杂度,这是十分有好的。
而且过程也比较容易理解

q->next = p->next;
q->prior = p;
p->next->prior = q;
p->next = q;

list有些什么功能

前面我们知道了list有什么特点,现在我们就应该知道list有一些什么功能,也叫做有一些什么函数。对于一个容器最核心的就是如何向里面存入数据,那么就有了push_back(),push_front(),insert。存元素知道了,那么就应该是取元素了,但是这个取元素又分为出容器和读取元素,其中出容器有两个和如元素对对应的pop_back(),pop_front(),erase(),读取元素就有front(),back()
这上面这几个功能主要是用于程序员进行操作,但是容器和算法结合是就不是这样几个函数了。因为算法希望得到迭代器,而这个迭代器通过萃取器可以得到算法需要的内容。那么关于迭代器就有begin(),end(),还有两个就是细枝末节的函数了size(),empty()

list的结构

下面画一个图来表示list内部的结构。在这里插入图片描述
对于这里面的每一个部分我都会分开进行解释。首先是链表的结点Node:

结点Node

struct Node
        {
                Object data;
                struct Node *prev;
                struct Node *next;
                /*节点构造函数*/
                Node(const Object &d = Object(), Node *p = NULL, Node *n = NULL):data(d), prev(p), next(n){}
        };

这个里面的struct和C语言的struct是相同的,但是他又是和class类似的。这是C++为了和C语言兼容,刻意将struct定义为和结构体一样,但是和C语言结构体有不完全相同,里面的所有元素默认为是public,而class里面的元素默认为private。

迭代器iterator

在STL中迭代器的作用十分重要,他是将算法和容器连接起来的一个中间件。这一个中间件可以在不同的容器中表现形式个不相同,有的容器当中迭代器是指针,有的又是类的对象。所以需要一个萃取器实现不同的输入得到需要的输出。
在迭代器中需要重载几个符号:*,++,--,==,!=这里面比较重要的是++和–,因为存在前缀和后缀两种情况。在C++中不允许出现i++++这种情况所以我们需要对后缀进行一个特殊处理。

const_iterator & operator++ ()
{
     current = current->next;
     return *this;
}       

const_iterator operator++(int)
{
     const_iterator old = *this;
     ++(*this);
	 return old;
}

上面两个代码中,前面那个是前缀++,后面那个是后缀++。看一下这个后缀++和前缀++返回值的地方。前缀++返回的是一个引用,说明可以继续往下执行,是一个左值。但是后缀++返回的是一个值,是一个右值,右值是不允许进行操作的。这个时候系统会报错。
大家可能会疑问我们对于操作符的重载应该达到什么样的效果,大家首相想一想为什么我们要进行操作符的重载。当然是一般的操作符不能实现特殊的功能,那么最一般的数据类型是int。所以我们就是要向着int类型的操作符进行模拟,这就是我们操作符重载的目的了。
下面我就开始针对每一个函数进行单独讲解。

list函数

构造函数

List()	{	init();		}
void init() {
	theSize = 0;
	head = new Node;
	tail = new Node;
	head->next = tail;
	tail->prev = head;
}

在这里里面我们创造两个结点,分别作为前部哨兵和后部哨兵。就如下图在这里插入图片描述

析构函数

~List() {
	clear();
	delete head;
	delete tail;
}
void clear() {
	while( !empty())
		pop_front();
}

这个函数相对比较简单,调用clear函数循环遍历将每个元素删除,最后将两个哨兵结点删除就可以了。

empty()&size()

bool empty()const
{
	return size() == 0;
}
int size()const
{
	return theSize;
}

这两个函数比较好理解都是直接返回相应的值

front()

Object & front()
{
	/*采用了迭代器begin()*/
	return *begin();
}
//将该函数声明为const类型
const Object &front()const
{
	return *begin();
}

这两个重载函数就是返回list头元素的引用

back()

Object &back()
{
	/*end()指向最后一个对象的下一个地址,因此需要--*/
	return *--end();
}
const Object &back()const
{
	return *--end();
}

这函数需要注意的地方在于由于STL的前闭后开规则导致end指向的是最后一个元素的下一个元素。所以返回的时候需要–,这里end()函数返回的是一个引用所以可以--,而且返回值是一个副本,不会影响本身的指针指向。

push_back()

void push_back(const Object &x) {
	insert(end(), x);
}
iterator insert(iterator itr, const Object &x)
{
	Node *p = itr.current;
	theSize ++;
	return iterator(p->prev=p->prev->next= new Node(x,p->prev, p));
}

这个需要注意的地方在于这个insert()函数插入的位置是在当前位置的下一个位置。需要注意的是这一个语句return iterator(p->prev=p->prev->next= new Node(x,p->prev, p));这一句实现和插入,我们来进行分部操作。
首先因为赋值运算符=是右结合的,所以从右向左进行运算。new Node(x,p->prev, p));这个是调用Node的构造函数,如果不清楚翻看前面的内容。
在这里插入图片描述
这个就是这个构造函数所生成的结构。然后再将左右两个结点指向他即可。注意这个这个地方为什么采用前插法?

push_front()

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

这里的insert依然调用前面的push_back()的。现在我们来回答前面的问题:因为我们在插入的过程中,是在end和head之前,这样我们就可以实现代码风格的统一性。而且因为我们在前面放置了哨兵这样我们还避免了对 head指针的移动。

pop_front()

void pop_front() {
	erase(begin());
}
iterator erase(iterator itr) {
	Node *p = itr.current;
	iterator retVal(p->next);
	p->prev->next = p->next;
	p->next->prev = p->prev;
	delete p;
	theSize --;
	return retVal;
}

这个就是删除头节点元素,下面我们来进行模拟。
在这里插入图片描述

pop_back()

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

pop_back()相比于front来说区别在于删除的元素并不是end(),而是end()的前一个元素。

存储图示

在这里插入图片描述
如图所示,红色结点就是list内部存储多增加两个哨兵结点,黄色结点是实际存储数据的数据结点。所以在后面的源代码中可以看到begin()end()两个函数并不是返回headrail,而是返回的head->nexttail->prior

源代码

#ifndef __MYLIST_H_H_
#define __MYLIST_H_H_


#include<iostream>


namespace myspace
{


    template<class Object>
    class List
    {
    private:
        /*封装对象,形成链表节点*/
        struct Node
        {
                Object data;
                struct Node *prev;
                struct Node *next;
                /*节点构造函数*/
                Node(const Object &d = Object(), Node *p = NULL, Node *n = NULL):data(d), prev(p), next(n){}
        };


public:
        /*创建一个常量迭代器类,这是容器设计的关键*/
        class const_iterator
        {
        public:
                const_iterator():current(NULL)
                {}


                /*获取当前迭代器的值*/
                const Object & operator*()const
                {
                        return retrieve();
                }
                /*重载前向++操作符*/
                const_iterator & operator++ ()
                {
                        current = current->next;
                        return *this;
                }
                /*重载后向++操作符,因为是一个局部对象不能返回引用*/
                //C++不允许出现i++++
                const_iterator operator++(int)
                {
                        //暂时保存当前的的地址
                        const_iterator old = *this;
                        ++(*this);


                        return old;
                }
                /*判断迭代器是否相同,实质上就是判断指向的节点是否相同*/
                bool operator==(const const_iterator &rhs) const
                {
                        return current == rhs.current;
                }
                        
                /*调用==操作符*/
                bool operator!=(const const_iterator &rhs)const
                {
                        return (!(*this == rhs));
                }
                /*迭代器实质就是一个节点指针*/
                Node *current;
        protected:
                


                /*获得链表中的内容*/
                Object & retrieve() const
                {
                        return current->data;
                }


                /*基于指针参数的迭代器构造函数,保证只有List使用*/
                const_iterator(Node *p):current (p)
                {}


                /*友元类,可以调用迭代器的私有成员*/
                friend class List<Object>;
        };
        /*一般迭代器,直接继承const_iterator*/
        class iterator : public const_iterator
                {
                public:
                        iterator():const_iterator()
                        {}


                        /*得到对象的值*/
                        Object &operator*()
                        {
                                return *(iterator::current);
                        }


                        /*基于const的重载*/
                        const Object& operator*()const
                        {
                                return const_iterator::operator*();
                        }
                        /*前向++操作符*/
                        iterator &operator++()
                       {
                                const_iterator::current = const_iterator::current->next;
                                return *this;
                        }
                        /*后向++操作符*/
                        iterator operator++(int)
                        {
                                iterator *old = *this;
                                ++(*this);
                                return old;
                        }


                protected:
                        /*基于节点的迭代器构造函数*/
                        iterator(Node *p):const_iterator(p)
                        {}


                        friend class List<Object>;
                };
        public:
                List()
                {
                        init();
                }
                ~List()
                {
                        clear();
                        delete head;
                        delete tail;
                }


                List(const List &rhs)//拷贝构造函数
                {
                       /*创建哨兵节点*/
                        init();
                        /*复制数据*/
                        *this = rhs;
                }


                const List & operator=(const List &rhs)//拷贝赋值函数
                {
                        if(this == &rhs)
                                return *this;
                        /*清除原有的信息*/
                        clear();
                        /*添加新的对象*/
                        for(const_iterator itr = rhs.begin(); itr != rhs.end(); ++ itr)
                                push_back(*itr);
                        return *this;
                }


                /*得到迭代器,实质上就是得到节点指针*/
                iterator begin()
                {
                        /*iterator()是构造函数*/
                        return iterator(head->next);
                }


                const_iterator begin()const
                {
                        return const_iterator(head->next);
                }


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


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


                int size()const
                {
                        return theSize;
                }


                bool empty()const
                {
                        return size() == 0;
                }


                void clear()
                {
                        while( !empty())
                                pop_front();
                }


                /*得到第一个元素*/
                Object & front()
                {
                        /*采用了迭代器begin()*/
                        return *begin();
                }
                //将该函数声明为const类型
                const Object &front()const
                {
                        return *begin();
               }


                Object &back()
                {
                        /*end()指向最后一个对象的下一个地址,因此需要--*/
                        return *--end();
                }
                const Object &back()const
                {
                        return *--end();
                }
                /***********************************************
                *从头插入新的节点,这时候的begin已经不再是begin
                *因此插入操作会导致迭代器出错
                ***********************************************/
                void push_front(const Object &x)
                {
                        insert(begin(), x);
                }
                /*从后插入新的节点,这时候会将end后移*/
                void push_back(const Object &x)
                {
                        insert(end(), x);
                }


                /*从头弹出一个对象*/
                void pop_front()
                {
                        erase(begin());
                }
                void pop_back()
                {
                       erase(--end());
                }


                /*插入对象,参数是迭代器和数据*/
                iterator insert(iterator itr, const Object &x)
                {
                        /*得到当前迭代器的指针*/
                        Node *p = itr.current;
                        theSize ++;
                        return iterator(p->prev=p->prev->next= new Node(x,p->prev, p));
                }


                /*删除迭代器处的对象,因此删除也会导致迭代器破坏*/
                //是删除的itr的下一个元素
                iterator erase(iterator itr)
                {
						Node *p = itr.current;
                        iterator retVal(p->next);
                        p->prev->next = p->next;
                        p->next->prev = p->prev;
                        delete p;
                        theSize --;
                        return retVal;
                }


                /*删除迭代器指向的对象*/
                iterator erase(iterator start, iterator end)
                {
                        /*for中不使用++itr的原因是erase之后
                         *就是下一个迭代器,因此不需要++操作*/
                        iterator itr;
                        for(itr = start; itr != end; )
                        {
                                /*该操作会导致迭代器更新到下一个*/
                                itr = erase(itr);
                        }
                        return itr;
                }


        private:
                /*链表中的数据成员*/
                int theSize;
                Node *head;
                Node *tail;
                /*初始化函数*/
                void init()
                {
                        theSize = 0;
                        head = new Node;
                        tail = new Node;
                        head->next = tail;
                        tail->prev = head;
                }
        };
}


#endif

后记

虽然自己完整写出这部分代码还是有一定的难度,但是通过阅读别人的代码,了解容器内部的构造也能够实现技术的提升。大家一起共勉!中国加油!武汉加油!

发布了12 篇原创文章 · 获赞 12 · 访问量 2466

猜你喜欢

转载自blog.csdn.net/deng821776892/article/details/104331232
今日推荐