SLT—List详解

1.list概述

相较于 vector 的连续线性空间,list 就显得复杂很多,它的好处是每次插入或删除一个数据,就配置或释放一个元素空间。因此,list 对于空间的运用有绝对的精准,一点也不浪费。对于任何位置的元素进行插入或者元素移除,list 永远是常数时间(O(N))。

list 和 vector 是两个最常被使用的容器,什么时机下最适合使用哪一种容器,必须视元素的多寡、元素的构造复杂度、 元素存取行为的特征而定。

下图是 list 示意图 : 环状链表的尾端加上一个空白节点,符合"前闭后开"区间。

2.list的使用

list 中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。

(1) list的构造

cpp 复制代码
void TestList1()
{
    list<int> l1;                         // 构造空的l1
    list<int> l2(4, 100);                 // l2中放4个值为100的元素
    list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
    list<int> l4(l3);                    // 用l3拷贝构造l4

    // 以数组为迭代器区间构造l5
    int array[] = { 16,2,77,29 };
    list<int> l5(array, array + sizeof(array) / sizeof(int));

    // 列表格式初始化C++11
    list<int> l6{ 1,2,3,4,5 };

    // 用迭代器方式打印l5中的元素
    list<int>::iterator it = l5.begin();
    while (it != l5.end())
    {
        cout << *it << " ";
        ++it;
    }       
    cout << endl;

    // C++11范围for的方式遍历
    for (auto& e : l5)
        cout << e << " ";

    cout << endl;
}

(2) list iterator

暂时将迭代器理解成一个指针,该指针指向 list 中的某个节点。

【注意】

  1. begin 与 end 为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end) 与 rend(begin) 为反向迭代器,对迭代器执行++操作,迭代器向前移动
cpp 复制代码
// list迭代器的使用
// 注意:遍历链表只能用迭代器和范围for
void PrintList(const list<int>& l)
{
    // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
    for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
    {
        cout << *it << " ";
        // *it = 10; 编译不通过
    }

    cout << endl;
}

void TestList2()
{
    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    // 使用正向迭代器正向list中的元素
    // list<int>::iterator it = l.begin();   // C++98中语法
    auto it = l.begin();                     // C++11之后推荐写法
    while (it != l.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 使用反向迭代器逆向打印list中的元素
    // list<int>::reverse_iterator rit = l.rbegin();
    auto rit = l.rbegin();
    while (rit != l.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
}

(3) list capacity

(4) list element access(元素访问)

(5) list modifiers(修改器)

cpp 复制代码
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
	list<int> ilist; 
	cout << "size=" << ilist.size() << endl;// size=0
	ilist.push_back(0); 
	ilist.push_back(1); 
	ilist.push_back(2); 
	ilist.push_back(3); 
	ilist.push_back(4); 
	cout << "size=" << ilist.size() << endl;//size=5

	list<int>::iterator ite; 
	for (ite = ilist.begin(); ite != ilist.end(); ++ite)
	{
		cout << *ite << ' ';//0 1 2 3 4
	}
	cout << endl;


	ite = find(ilist.begin(), ilist.end(), 3);
	if (ite != ilist.end())
	{
		ilist.insert(ite, 99);
	}
	cout << "size=" << ilist.size() << endl;// size=6
	cout << *ite << endl;// 3
	
	for (ite = ilist.begin(); ite != ilist.end(); ++ite)//0 1 2 99 3 4
		cout << *ite << ' ';
	cout << endl;
	ite = find(ilist.begin(), ilist.end(), 1); 
	if (ite != ilist.end())
		cout << *(ilist.erase(ite)) << endl;//2

	for (ite = ilist.begin(); ite != ilist.end(); ++ite)//0 2 99 3 4
		cout << *ite << ' ';
	cout << endl;
}

(6) list迭代器失效

前面说过,此处可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在 list 中进行插入时是不会导致 list 的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。这在 vector 是不成立的,因为 vector 的常茹操作可能造成记忆体重新配置,导致原有的迭代器全部失效。

cpp 复制代码
void TestListIterator1()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		// erase()函数执行后,it所指向的节点已被删除,
		// 因此it无效,在下一次使用it时,必须先给其赋值
			l.erase(it);
		++it;
	}
}
// 改正
void TestListIterator()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		l.erase(it++); // it = l.erase(it);
	}
}

3.list的模拟实现

(1) list的节点(ListNode)

**list 本身和 list 的节点是不同的结构,需要分开设计,以下是根据 STL 设计的 list 的节点(**ListNode )结构:

cpp 复制代码
template <class T>
struct ListNode // struct默认成员是公有的
{
	ListNode(const T& val = T())
		:_prev(nullptr)
		,_next(nullptr)
		,_data(val)
	{}

	ListNode<T>* _prev;
	ListNode<T>* _next;
	T _data;
};

(2) list的迭代器(ListIterator)

list 不能再像 vector 一样以普通指针作为迭代器,因为其节点不保证在存储空间里连续存在。list迭代器必须有能力指向list节点,并有能力进行正确的递增、递减、取值、成员存取等操作。所谓"list 正确的递增、递减、取值、成员存取"操作是指,递增时指向下一个节点,递减时指向下一个节点,取值时取的是节点的数据值,成员取用时取的是节点的成员。

由于STL list是一个双向链表,迭代器必须具备迁移,后移的能力,所以 list 提供的是 Birdirectional Iterator(双向)
List 的迭代器

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

1. 原生态指针,比如:vector

2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

(1)指针可以解引用,迭代器的类中必须重载 operator*()

(2)指针可以通过->访问其所指空间成员,迭代器类中必须重载 oprator->()

(3)指针可以++向后移动,迭代器类中必须重载 operator++() 与 operator++(int)

至于 operator--() / operator--(int) 释放需要重载,根据具体的结构来抉择,双向链表可以向前 移动,所以需要重载,如果是forward_list(单向)就不需要重载--

(4)迭代器需要进行是否相等的比较,因此还需要重载 operator==() 与 operator!=()

cpp 复制代码
//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)
        :_pNode(pNode)
    {}

    ListIterator(const Self& l)
        :_pNode(l._pNode)
    {}

    T& operator*()
    {//取得是节点的数值
        return (*_pNode)._data;
    }
    T* operator->()
    {
        return &(operator*());
    }

    Self& operator++()
    {
        _pNode = (*_pNode)._next;
        return *this;
    }
    Self operator++(int)//前置++
    {
        Self tmp = *this;
        _pNode = (*_pNode)._next;
        return tmp;//返回++之前的
    }
    Self& operator--()
    {
        _pNode = (*_pNode)._prev;
        return *this;
    }
    Self operator--(int)
    {
        Self tmp = *this;
        _pNode = _pNode._prev;
        return tmp;//返回++之前的
    }
    bool operator!=(const Self& l)
    {
        return !(_pNode == l._pNode);
    }
    bool operator==(const Self& l)
    {
        return _pNode == l._pNode;
    }

    PNode _pNode;//迭代器的内部要有一个普通指针,指向list的节点
};

(3) list的数据结构

STL list 不仅是一个双向链表,而且还是一个环状双向链表。所以它只需要一个指针,就可以完整表现整个链表。

cpp 复制代码
//list类
template<class T>
class list // class 默认是私有的
{
    typedef ListNode<T> Node;
public:
    typedef Node* PNode;
    
    //实现...

private:
    PNode _pHead;//只需要一个指针,完成整个双向环状链表
    size_t _size;//STL中没有,自己实现为了方便加的

};

(4) list的构造与内存管理

如果将指针刻意置于尾端的一个空白节点,_pHead便能符合STL对于"前闭后开"去区间的要求,成为 list 迭代器。

cpp 复制代码
    // List的构造
    list()
    {
        CreateHead();//产生一个空链表(一个指向自己的空节点)
    }
    list(int n, const T& value = T())
    {
        CreateHead();
        while (n--)
        {
            push_back(value);
        }
    }
    template <class Iterator>
    list(Iterator first, Iterator last)
    {
        CreateHead();
        while (first != last)
        {
            push_back(*first);
            first++;
        }
    }
    //深拷贝
    list(const list<T>& l)
    {
        CreateHead();
        list<T> tmp(l.begin(), l.end());
        swap(tmp);
    }
    list<T>& operator=(list<T> l)
    {
        swap(l);
        return *this;
    }
    ~list()
    {
        clear();
        delete[] _pHead;
        _pHead = nullptr;
    }

private:
    void CreateHead()
    {
        _pHead = new Node[1];//配置一个节点空间,头尾指向自己
        _pHead->_next = _pHead;
        _pHead->_prev = _pHead;
    }

当我们以 push_back( )将新元素插入于list尾端时,此时函数内部调用 insert( ) :

cpp 复制代码
void push_back(const T& val){ insert(end(), val);}

insert( ) 是一个重载函数,有多种形式,其中最简单的一种如下,首先配置并构造一个节点,然后在尾端进行适当的指针操作,将新节点插入进去:

cpp 复制代码
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
    PNode tmp = new Node[1];
    tmp->_data = val;
    PNode pcur = pos._pNode->_prev;
    pcur->_next = tmp;
    tmp->_prev = pcur;
    pos._pNode->_prev = tmp;
    tmp->_next = pos._pNode;
    ++_size;
    return tmp;
}

插入完成之后,新节点将位于插入点所指向之节点的前方---这是STL对于"插入操作"的标准规范。

(5) list的元素操作

list 提供的元素操作有很多,这里只实现部分操作。

cpp 复制代码
// 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)
{
    PNode tmp = new Node[1];
    tmp->_data = val;
    PNode pcur = pos._pNode->_prev;
    pcur->_next = tmp;
    tmp->_prev = pcur;
    pos._pNode->_prev = tmp;
    tmp->_next = pos._pNode;
    ++_size;
    return tmp;
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
    PNode pcur = pos._pNode->_prev;
    PNode pnext = pos._pNode->_next;
    pcur->_next = pnext;
    pnext->_prev = pcur;
    delete[] pos._pNode;
    --_size;
    return pnext;
}
//清除整个链表
void clear()
{
    auto cur = begin();
    while (cur != end())
    {
        cur = erase(cur);
    }
}

(6) 模拟实现.h

cpp 复制代码
#include<iostream>
#include<list>
#include<algorithm>
#include<assert.h>
using namespace std;

namespace zyt
{
    //List的节点类
    template <class T>
    struct ListNode // struct默认成员是公有的
    {
        ListNode(const T& val = T())
            :_prev(nullptr)
            , _next(nullptr)
            , _data(val)
        {}

        ListNode<T>* _prev;
        ListNode<T>* _next;
        T _data;
    };

    //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)
            :_pNode(pNode)
        {}

        ListIterator(const Self& l)
            :_pNode(l._pNode)
        {}

        T& operator*()
        {
            return (*_pNode)._data;
        }
        T* operator->()
        {
            return &(operator*());
        }

        Self& operator++()
        {
            _pNode = (*_pNode)._next;
            return *this;
        }
        Self operator++(int)//前置++
        {
            Self tmp = *this;
            _pNode = (*_pNode)._next;
            return tmp;//返回++之前的
        }
        Self& operator--()
        {
            _pNode = (*_pNode)._prev;
            return *this;
        }
        Self operator--(int)
        {
            Self tmp = *this;
            _pNode = _pNode._prev;
            return tmp;//返回++之前的
        }
        bool operator!=(const Self& l)
        {
            return !(_pNode == l._pNode);
        }
        bool operator==(const Self& l)
        {
            return _pNode == l._pNode;
        }

        PNode _pNode;
    };


    //list类
    template<class T>
    class list // class 默认是私有的
    {
        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()
        {
            CreateHead();//产生一个空链表(一个指向自己的空节点)
        }
        list(int n, const T& value = T())
        {
            CreateHead();
            while (n--)
            {
                push_back(value);
            }
        }
        template <class Iterator>
        list(Iterator first, Iterator last)
        {
            CreateHead();
            while (first != last)
            {
                push_back(*first);
                first++;
            }
        }
        //深拷贝
        list(const list<T>& l)
        {
            CreateHead();
            list<T> tmp(l.begin(), l.end());
            swap(tmp);
        }
        list<T>& operator=(list<T> l)
        {
            swap(l);
            return *this;
        }
        ~list()
        {
            clear();
            delete[] _pHead;
            _pHead = nullptr;
        }


        ///
        // List Iterator
        iterator begin()
        {
            return ((*_pHead)._next);
        }
        iterator end()
        {
            return _pHead;
        }
        const_iterator begin() const
        {
            return ((*_pHead)._next);
        }
        const_iterator end() const
        {
            return _pHead;

        }

        ///
        // List Capacity
        size_t size()const
        {
            return _size;
        }
        bool empty()const
        {
            return _pHead->_next == _pHead;
        }

        
        // List Access
        T& front()
        {
            return *begin();
        }
        const T& front()const
        {
            return *begin();
        }
        T& back()
        {
            return *end();
        }
        const T& back()const
        {
            return *end();
        }

        
        // 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)
        {
            PNode tmp = new Node[1];
            tmp->_data = val;
            PNode pcur = pos._pNode->_prev;
            pcur->_next = tmp;
            tmp->_prev = pcur;
            pos._pNode->_prev = tmp;
            tmp->_next = pos._pNode;
            ++_size;
            return tmp;
        }
        // 删除pos位置的节点,返回该节点的下一个位置
        iterator erase(iterator pos)
        {
            PNode pcur = pos._pNode->_prev;
            PNode pnext = pos._pNode->_next;
            pcur->_next = pnext;
            pnext->_prev = pcur;
            delete[] pos._pNode;
            --_size;
            return pnext;
        }
        //清除整个链表
        void clear()
        {
            auto cur = begin();
            while (cur != end())
            {
                cur = erase(cur);
            }
        }
        void swap(list<T>& l)
        {
            std::swap(_pHead, l._pHead);
            std::swap(_size, l._size);
        }
    private:
        void CreateHead()
        {
            _pHead = new Node[1];//配置一个节点空间,头尾指向自己
            _pHead->_next = _pHead;
            _pHead->_prev = _pHead;
        }
    private:
        PNode _pHead;
        size_t _size;

    };
}

(7) 测试.cpp

cpp 复制代码
// 打印各类容器(类)
template<class T>
void PrintList(const zyt::list<T>& l)
{
    auto it = l.begin();
    while (it != l.end())
    {
        cout << *it << " ";
        ++it;
    }

    cout << endl;
}

void TestList3()
{
    int array[] = { 1, 2, 3, 4, 5 };
    //zyt::list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    zyt::list<int> l;
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    l.push_back(4);
    l.push_back(5);
    PrintList(l);

    auto pos = l.begin();
    l.insert(l.begin(), 0);
    PrintList(l);

    ++pos;
    l.insert(pos, 2);
    PrintList(l);

    l.erase(l.begin());
    l.erase(pos);
    PrintList(l);

    // pos指向的节点已经被删除,pos迭代器失效
    cout << *pos << endl;

    auto it = l.begin();
    while (it != l.end())
    {
        it = l.erase(it);
    }
    cout << l.size() << endl;
}


// PushBack()/PopBack()/PushFront()/PopFront()
void TestList2()
{
    // 测试PushBack与PopBack
    zyt::list<int> l;
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    PrintList(l);

    l.pop_back();
    l.pop_back();
    PrintList(l);

    l.pop_back();
    cout << l.size() << endl;

    // 测试PushFront与PopFront
    l.push_front(1);
    l.push_front(2);
    l.push_front(3);
    PrintList(l);

    l.pop_front();
    l.pop_front();
    PrintList(l);

    l.pop_front();
    cout << l.size() << endl;
}

// 测试List的构造
void TestList1()
{
    zyt::list<int> l1;
    zyt::list<int> l2(10, 5);
    PrintList(l2);

    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    zyt::list<int> l3(array, array + sizeof(array) / sizeof(array[0]));
    PrintList(l3);

    zyt::list<int> l4(l3);
    PrintList(l4);

    l1 = l4;
    PrintList(l1);
}
int main()
{
    TestList1();
    return 0;
}

4.list与vector的区别

vector 与 list 都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

相关推荐
唐诺4 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨5 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
菜鸡中的奋斗鸡→挣扎鸡5 小时前
滑动窗口 + 算法复习
数据结构·算法
红龙创客5 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin5 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
axxy20007 小时前
leetcode之hot100---240搜索二维矩阵II(C++)
数据结构·算法
yuanbenshidiaos7 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室7 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0018 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我588 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物