【C++语法堂】STL标准库学习_list容器

news2024/11/28 0:30:15

目录

STL标准库学习_list

【1】List的介绍及使用

【2】List常用的接口

【2.1】构造函数

【2.2】析构函数

【2.3】迭代器相关

【2.4】容量相关

【2.5】元素访问相关

【2.6】修改相关

【2.7】运行相关

【2.8】观察相关

【2.9】非成员函数重载

【3】list模拟实现

【3.1】list节点结构框架

【3.2】 list类结构框架

【3.2】 无参构造DHList()

【3.3】initializer_list构造函数DHList(initializer_list il)

【3.4】迭代器构造DHList(InputIterator first, InputIterator last)

【3.5】析构函数~DHList()

【3.6】拷贝构造DHList(const DHList& list)

【3.7】赋值拷贝DHList& operator=(DHList list)

【3.8】尾插PushBack(const T& val)

【3.9】头插void PushFront(const T& val)

【3.10】尾删void PopBack()

【3.11】头删void PopFront()

【3.12】指定位置插入iterator Insert(iterator pos, const T& val)

【3.13】指定位置删除iterator Erase(iterator pos)

【3.14】 判断空bool Empty()

【3.15】 返回大小size_t Size()

【3.16】交换void Swap(List& list)

【3.17】清空void Clear()

【3.18】 正向迭代器

【3.19】 list完整类

【3.20】vector和list的对比


STL标准库学习_list

【1】List的介绍及使用

  • list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  • list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  • list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  • 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  • 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
  • 详细的STL文档请参考:

https://cplusplus.com/reference/list/list/

【2】List常用的接口

【2.1】构造函数

// 默认构造
explicit list (const allocator_type& alloc = allocator_type());
​
// 构造的list中包含n个值为val的元
explicit list (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());
​
// 迭代器构造
template <class InputIterator>
list (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
​
// 拷贝构造
list (const list& x);
list& operator= (const list& x);

【2.2】析构函数

// 析构函数
~list();

【2.3】迭代器相关

// 正向迭代器
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
​
// 反向迭代器
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;
​
// C++11
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;
const_reverse_iterator crbegin() const noexcept;
const_reverse_iterator crend() const noexcept;

【2.4】容量相关

// 检查空
bool empty() const;
​
// 返回大小
size_type size() const;
​
// 返回最大元素数
size_type max_size() const;

【2.5】元素访问相关

// 返回list的第一个节点中值的引用
reference front();
const_reference front() const;
​
// 返回list的最后一个节点中值的引用
reference back();
const_reference back() const;

【2.6】修改相关

// 将新内容分配给容器
template <class InputIterator>
void assign (InputIterator first, InputIterator last);
void assign (size_type n, const value_type& val);
​
// 在list首元素前插入值为val的元素
void push_front (const value_type& val);
​
// 删除list中第一个元素
void pop_front();
​
// 在list尾部插入值为val的元素
void push_back (const value_type& val);
​
// 删除list中最后一个元素
void pop_back();
​
// 在list position 位置中插入值为val的元素
iterator insert (iterator position, const value_type& val);
void insert (iterator position, size_type n, const value_type& val);
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);
​
// 删除list position位置的元素
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
​
// 交换两个list中的元素
void swap (list& x);
​
// 清空list中的有效元素
void clear();
​
// 调整大小
void resize (size_type n, value_type val = value_type());
​
// c++11
template <class... Args>
void emplace_front (Args&&... args);
​
template <class... Args>
void emplace_back (Args&&... args);
​
template <class... Args>
iterator emplace (const_iterator position, Args&&... args);

【2.7】运行相关

// 转移(将链表转移到另一个链表中)
void splice (iterator position, list& x);
void splice (iterator position, list& x, iterator i);
void splice (iterator position, list& x, iterator first, iterator last);
// 给定一个值,自己查找到删除
void remove (const value_type& val);
// 满足某个条件删除值
template <class Predicate>
void remove_if (Predicate pred);
​
// 去重
void unique();
template <class BinaryPredicate>
void unique (BinaryPredicate binary_pred);
​
// 合并
void merge (list& x);
template <class Compare>
void merge (list& x, Compare comp);
​
// 排序
void sort();
template <class Compare>
void sort (Compare comp);
​
// 逆置
void reverse();

【2.8】观察相关

allocator_type get_allocator() const;

【2.9】非成员函数重载

template <class T, class Alloc>
bool operator== (const list<T,Alloc>& lhs, const list<T,Alloc>& rhs);

template <class T, class Alloc>
bool operator<  (const list<T,Alloc>& lhs, const list<T,Alloc>& rhs);

template <class T, class Alloc>
bool operator<  (const list<T,Alloc>& lhs, const list<T,Alloc>& rhs);

template <class T, class Alloc>
bool operator<= (const list<T,Alloc>& lhs, const list<T,Alloc>& rhs);

template <class T, class Alloc>
bool operator>  (const list<T,Alloc>& lhs, const list<T,Alloc>& rhs);

template <class T, class Alloc>
bool operator>= (const list<T,Alloc>& lhs, const list<T,Alloc>& rhs);

template <class T, class Alloc>
void swap (list<T,Alloc>& x, list<T,Alloc>& y);

【3】list模拟实现

💡强调一下head是哨兵结点,本身不存储数据,作用只是指向头和尾!

【3.1】list节点结构框架

// 《带头双向循环链表 - 数据结构》
// 模板参数
template<class T>
// 结构体的默认成员是public.
struct ListNode
{
public:
    // 显示声明构造函数
    ListNode(const T& val = T())    // 匿名构造
        : _prev(nullptr)
        , _next(nullptr)
        , _data(val)
    {}
​
public:
    ListNode* _prev;        // 指向上一个节点.
    ListNode* _next;        // 指向下一个节点.
    T _data;                // 存储的数据
};

💡这里对于T()说明一下!这里是匿名构造,对于传来的参数类型不确定,可能是内置类型,也可能是类对象!所以不能想当然比如说默认赋值0!需要使用对象的构造函数.

【3.2】 list类结构框架

💡创建链表节点:链表节点本身指向自己。

// 《带头双向循环链表 - 类结构》
template<class T>
class List
{
private:
    typedef ListNode<T> Node;   
private:
    Node* _head;
    size_t _size;
};

【3.2】 无参构造DHList()

// 《带头双向循环链表 - 无参构造函数》
// 构造函数(无参构造函数)
DHList() {
    EmptyInitialize();
}
​
​
/ 《带头双向循环链表 - 创建节点初始化》
void EmptyInitialize()
{
    _head = new Node(T());
    _head->_prev = _head;
    _head->_next = _head;
    _size = 0;
}

【3.3】initializer_list构造函数DHList(initializer_list il)

// initializer_list构造函数
DHList(initializer_list<T> il) {
    EmptyInitialize();
    typename initializer_list<T>::iterator it = il.begin();
    while (it != il.end()) {
        PushBack(*it);
        ++it;
    }
}
​

【3.4】迭代器构造DHList(InputIterator first, InputIterator last)

// 构造函数(迭代器构造)
// 构造函数(迭代器构造)
template<class InputIterator>
DHList(InputIterator first, InputIterator last) {
    EmptyInitialize();
    while (first != last) {
        PushBack(*first);
        ++first;
    }
}
    
// 《带头双向循环链表 - 创建节点初始化》
void EmptyInitialize()
{
    _head = new Node(T());
    _head->_prev = _head;
    _head->_next = _head;
    _size = 0;
}

【3.5】析构函数~DHList()

// 《带头双向循环链表 - 析构函数》
~DHList() {
    Clear();
    delete _head;
    _head = nullptr;
}

【3.6】拷贝构造DHList(const DHList& list)

// 链表拷贝构造函数 - 传统写法
//DHList(const DHList<T>& list)
//{
//  // 创造节点,初始化节点
//  EmptyInitialize();
//  for (const T& e : list)
//  {
//      PushBack(e);
//  }
//}
​
// 链表拷贝构造函数 - 现代写法
DHList(const DHList<T>& list) {
    // 创造节点,初始化节点
    EmptyInitialize();
    DHList<T> temp = DHList(list.begin(), list.end());
    Swap(temp);
}

【3.7】赋值拷贝DHList& operator=(DHList list)

// 运算符重载:[] - 传统写法
/*DHList<T>& operator=(const DHList<T>& list)
{
    if (this != &list)
    {
        this->Clear();
        for (const auto& v : list)
        {
            PushBack(v);
        }
​
        return *this;
    }
}*/
​
// 运算符重载:[] - 现代写法
DHList<T>& operator=(DHList<T> list) {
    Swap(list);
    return *this;
}
​

【3.8】尾插PushBack(const T& val)

 

// 《链表尾插》
void PushBack(const T& val)
{
    // 创建一个新的节点.
    Node* newNode = new Node(val);
    // 找到尾节点.
    Node* tail = _head->_prev;

    // 链接
    // 尾节点->下一个节点 指向新的节点
    tailNode->_nextNode = newNode;
    // 头节点->上一个节点 指向新的节点
    _head->_prevNode = newNode;
    // 新节点->下一个节点 指向头节点
    newNode->_nextNode = _head;
    // 新节点->上一个节点 指向尾节点(更改完成tailNode就不是最后一个节点了)
    newNode->_prevNode = tailNode;
}

【3.9】头插void PushFront(const T& val)

// 头插数据
void PushFront(const T& val) {
    // 创建新的节点
    Node* newNode = new Node(val);
    // 找到链表头节点的下一个节点
    Node* nextNode = _head->_nextNode;

    // 头节点的下一个节点->上一个节点 指向新节点
    nextNode->_prevNode = newNode;
    // 头节点->下一个节点 指向新节点
    _head->_nextNode = newNode;
    // 新节点->下一个节点 指向原头节点的下一个节点
    newNode->_nextNode = nextNode;
    // 新节点->上一个节点 指向头节点
    newNode->_prevNode = _head;
}

【3.10】尾删void PopBack()

// 尾删数据
void PopBack() {
    // 判断链表不为空链表
    assert(!Empty());

    // 找到链表最后一个节点
    Node* pTail = _head->_prevNode;
    // 找到链表最有一个节点的上一个节点
    Node* pFirst = pTail->_prevNode;
    // 头节点->上一个节点值 指向链表最后一个节点的上一个节点
    _head->_prevNode = pFirst;
    // 链表最后一个节点->上一个节点的下一个节点 指向头节点
    pFirst->_nextNode = _head;
    // 释放
    delete pTail;
    pTail = nullptr;
}

【3.11】头删void PopFront()

// 头删数据
void PopFront() {
    // 判断链表不为空链表
    assert(!Empty());
    
    // 找到链表第一个节点 
    Node* pFirst = _head->_nextNode;
    // 找到链表第二个节点
    Node* pSecond = pFirst->_nextNode;
    // 头节点->下一个节点 指向第二个节点
    _head->_nextNode = pSecond;
    // 第二个节点->上一个节点 指向头节点
    pSecond->_prevNode = _head;
    // 释放
    delete pFirst;
    pFirst = nullptr;
}

【3.12】指定位置插入iterator Insert(iterator pos, const T& val)

由于C++特别喜欢复用,我们也发现实现一个Insert()就可以实现以上的功能。

// 《带头双向循环链表 - 尾插》
void PushBack(const T& val)
{
    Insert(end(), val);
}

// 《带头双向循环链表 - 头插》
void PushFront(const T& val)
{
    Insert(begin(), val);
}

// 《带头双向循环链表 - 指定位置插入》
iterator Insert(iterator pos, const T& val) {
    assert(pos != nullptr);
    // 创建新的节点
    Node* newNode = new Node(val);
    // 传递的pos位置是迭代器,先从迭代器中拿到节点.
    Node* pCurNode = pos._node;
    Node* pPosPrevNode = pCurNode->_prevNode;
    // pos上一个节点->下一个节点 指向新的节点
    pPosPrevNode->_nextNode = newNode;
    // pos上一个节点 指向新节点
    pCurNode->_prevNode = newNode;
    // 新节点上一个节点指向 原pos上一个节点
    newNode->_prevNode = pPosPrevNode;
    // 新节点下一个节点指向 原pos节点
    newNode->_nextNode = pCurNode;

    ++_size;
    return iterator(newNode);
}

【3.13】指定位置删除iterator Erase(iterator pos)

同样根据C++的特性实现Erase(),实现以上所有的功能。

// 《带头双向循环链表 - 尾删》
void PopBack()
{
    Erase(--end());
}

// 《带头双向循环链表 - 头删》
void PopFront()
{
    Erase(begin());
}

// 《带头双向循环链表 - 指定位置删除》
iterator Erase(iterator pos) {
    assert(pos != nullptr);
    assert(!Empty());

    // 获取pos下一个节点
    Node* prevNode = pos._node->_prevNode;
    // 获取pos上一个节点
    Node* nextNode = pos._node->_nextNode;

    // pos上一个节点->下一个节点 指向pos下一个节点
    prevNode->_nextNode = nextNode;
    // pos下一个节点->上一个节点 指向pos上一个节点
    nextNode->_prevNode = prevNode;
    // 释放pos位置节点
    delete pos._node;

    --_size;
    return iterator(nextNode);
}

【3.14】 判断空bool Empty()

// 判断为空
bool Empty() {
    return _head->_nextNode == _head;
}

【3.15】 返回大小size_t Size()

// 返回大小
size_t Size() {
    return _size;
}

【3.16】交换void Swap(List& list)

// 《链表内部交换》
void Swap(List<T>& list)
{
    std::swap(_head, list._head);
    std::swap(_size, list._size);
}

【3.17】清空void Clear()

// 《带头双向循环链表 - 清理链表保留头节点》
void Clear()
{
    // 直接使用内部的迭代器
    iterator itBegin = begin();
    while (itBegin != end())
    {
        itBegin = Erase(itBegin);
    }
}

【3.18】 正向迭代器

        iterator 原生指针,数组结构正好支持迭代器的行为。

        iterator不能和顺序表一样,iterator 原生指针(Node*),不能满足迭代器行为。我们可以用类封装+运算符重载支持。

// 链表struct结构:迭代器
// typedef __list_iterator<T, T&, T*> iterator;
// typedef __list_iterator<T, const T&, const T*> const_iterator;
template<class T, class Ref, class Ptr>
struct __ListIterator {
    typedef DListNode<T> Node;
    typedef __ListIterator<T, Ref, Ptr> Self;
    Node* _node;
    
    // 构造函数
    __ListIterator(Node* pNode) 
        : _node(pNode) 
    {};

    // 运算符重载:*
    Ref operator*() { 
        return _node->_data; 
    }

    // 运算符重载:->
    Ptr operator->() { 
        return &_node->_data; 
    }


    // 运算符重载:++前置
    Self& operator++() {
        _node = _node->_nextNode;
        return *this;
    }

    // 运算符重载:++后置
    Self operator++(int)
    {
        Self temp(*this);
        _node = _node->_next;
        return temp;
    }

    // 运算符重载:--前置
    Self& operator--() {
        _node = _node->_prevNode;
        return *this;
    }

    // 运算符重载:--后置
    Self operator--(int)
    {
        Self temp(*this);
        _node = _node->_prev;
        return temp;
    }

    // 运算符重载:!=
    bool operator!=(const Self& it) { 
        return _node != it._node; 
    }

    // 运算符重载:==
    bool operator==(const Self& it) { 
        return _node == it._node; 
    }
};


// 《迭代器》
typedef __ListIterator<T,T&,T*> iterator;
// typedef __ListConstIterator<T> const_iterator;
typedef __ListIterator<T, const T&,const T*> const_iterator;
iterator begin()
{
    return iterator(_head->_next);
}
iterator end()
{
    return iterator(_head);
}

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

【3.19】 list完整类

#include <iostream>
#include <cassert>
using namespace std;

namespace ShaXiang_DList1 {
    /* 带头双向循环链表 - 数据结构*/
    template<class T>
    struct DListNode {
        // 构造函数(带默认构造函数)
        DListNode(const T& val = T()) 
            : _prevNode(nullptr)
            , _nextNode(nullptr)
            , _data(val) 
        {};
    
        DListNode* _prevNode;    // 指向上一个节点地址
        DListNode* _nextNode;    // 指向下一个节点地址
        T _data;                // 存储数据值
    };

    /* 带头双向循环链表 - 迭代器类 */
    template<class T, class Ref, class Ptr>
    struct __ListIterator {
        typedef DListNode<T> Node;
        typedef __ListIterator<T, Ref, Ptr> Self;
        Node* _node;
        
        // 构造函数
        __ListIterator(Node* pNode) 
            : _node(pNode) 
        {};

        // 运算符重载:*
        Ref operator*() { 
            return _node->_data; 
        }

        // 运算符重载:->
        Ptr operator->() { 
            return &_node->_data; 
        }


        // 运算符重载:++前置
        Self& operator++() {
            _node = _node->_nextNode;
            return *this;
        }

        // 运算符重载:++后置
        Self operator++(int)
        {
            Self temp(*this);
            _node = _node->_next;
            return temp;
        }

        // 运算符重载:--前置
        Self& operator--() {
            _node = _node->_prevNode;
            return *this;
        }

        // 运算符重载:--后置
        Self operator--(int)
        {
            Self temp(*this);
            _node = _node->_prev;
            return temp;
        }

        // 运算符重载:!=
        bool operator!=(const Self& it) { 
            return _node != it._node; 
        }

        // 运算符重载:==
        bool operator==(const Self& it) { 
            return _node == it._node; 
        }
    };


    /* 带头双向循环链表 - 类*/
    template<class T>
    class DList {
    // 公有取别名
    public:
        typedef DListNode<T> Node;
        typedef __ListIterator<T, T&, T*> iterator;
        typedef __ListIterator<T, const T&, const T*> const_iterator;

    // 私有成员变量
    private:
        Node* _head;
        size_t _size;

    // 公开成员函数
    public:
        // 迭代器
        iterator begin() { 
            return iterator(_head->_nextNode);
        }
        iterator end() { 
            return iterator(_head);
        }

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

    // 公有成员函数
    public:
        // 构造函数(无参构造函数)
        DList() {
            EmptyInitialize();
        }

        // 构造函数(迭代器构造)
        template<class InputIterator>
        DList(InputIterator first, InputIterator last) {
            EmptyInitialize();
            while (first != last) {
                PushBack(*first);
                ++first;
            }
        }

        // 析构函数
        ~DList() {
            Clear();
            delete _head;
            _head = nullptr;
        }

        // 链表拷贝构造函数 - 传统写法
        //DList(const DList<T>& list)
        //{
        //    // 创造节点,初始化节点
        //    EmptyInitialize();
        //    for (const T& e : list)
        //    {
        //        PushBack(e);
        //    }
        //}

        // 链表拷贝构造函数 - 现代写法
        DList(const DList<T>& list) {
            // 创造节点,初始化节点
            EmptyInitialize();
            DList<T> temp = DList(list.begin(), list.end());
            Swap(temp);
        }

    // 公有成员函数
    public:
        // 初始化
        void EmptyInitialize(){
            // 创建节点->节点指向自己.
            _head = new Node(T());
            _head->_nextNode = _head;    // 初始化指向自己
            _head->_prevNode = _head;    // 初始化指向自己
            _head->_data = 0;            // 数据初始化为0
            _size = 0;                    // 头节点存放数据个数初始化为0
        }

        // 尾插数据
        void PushBack(const T& val) {
            /*
            // 创建新的节点
            Node* newNode = new Node(val);
            // 找到链表的尾节点
            Node* tailNode = _head->_prevNode;

            // 尾节点->下一个节点 指向新的节点
            tailNode->_nextNode = newNode;
            // 头节点->上一个节点 指向新的节点
            _head->_prevNode = newNode;
            // 新节点->下一个节点 指向头节点
            newNode->_nextNode = _head;
            // 新节点->上一个节点 指向尾节点(更改完成tailNode就不是最后一个节点了)
            newNode->_prevNode = tailNode;
            */

            Insert(end(), val);
            ++_size;
        }

        // 头插数据
        void PushFront(const T& val) {
            /*
            // 创建新的节点
            Node* newNode = new Node(val);
            // 找到链表头节点的下一个节点
            Node* nextNode = _head->_nextNode;

            // 头节点的下一个节点->上一个节点 指向新节点
            nextNode->_prevNode = newNode;
            // 头节点->下一个节点 指向新节点
            _head->_nextNode = newNode;
            // 新节点->下一个节点 指向原头节点的下一个节点
            newNode->_nextNode = nextNode;
            // 新节点->上一个节点 指向头节点
            newNode->_prevNode = _head;
            */

            Insert(begin(), val);
            ++_size;
        }

        // 尾删数据
        void PopBack() {
            /*
            // 判断链表不为空链表
            assert(!Empty());

            // 找到链表最后一个节点
            Node* pTail = _head->_prevNode;
            // 找到链表最有一个节点的上一个节点
            Node* pFirst = pTail->_prevNode;
            // 头节点->上一个节点值 指向链表最后一个节点的上一个节点
            _head->_prevNode = pFirst;
            // 链表最后一个节点->上一个节点的下一个节点 指向头节点
            pFirst->_nextNode = _head;
            // 释放
            delete pTail;
            pTail = nullptr;
            */

            Erase(--end());
            --_size;
        }
        
        // 头删数据
        void PopFront() {
            /*
            // 判断链表不为空链表
            assert(!Empty());
            
            // 找到链表第一个节点 
            Node* pFirst = _head->_nextNode;
            // 找到链表第二个节点
            Node* pSecond = pFirst->_nextNode;
            // 头节点->下一个节点 指向第二个节点
            _head->_nextNode = pSecond;
            // 第二个节点->上一个节点 指向头节点
            pSecond->_prevNode = _head;
            // 释放
            delete pFirst;
            pFirst = nullptr;
            */

            Erase(begin());
            --_size;
        }

        // 指定位置插入数据
        iterator Insert(iterator pos, const T& val) {
            assert(pos != nullptr);
            // 创建新的节点
            Node* newNode = new Node(val);
            // 传递的pos位置是迭代器,先从迭代器中拿到节点.
            Node* pCurNode = pos._node;
            Node* pPosPrevNode = pCurNode->_prevNode;
            // pos上一个节点->下一个节点 指向新的节点
            pPosPrevNode->_nextNode = newNode;
            // pos上一个节点 指向新节点
            pCurNode->_prevNode = newNode;
            // 新节点上一个节点指向 原pos上一个节点
            newNode->_prevNode = pPosPrevNode;
            // 新节点下一个节点指向 原pos节点
            newNode->_nextNode = pCurNode;

            ++_size;
            return iterator(newNode);
        }

        // 指定位置删除数据
        iterator Erase(iterator pos) {
            assert(pos != nullptr);
            assert(!Empty());

            // 获取pos下一个节点
            Node* prevNode = pos._node->_prevNode;
            // 获取pos上一个节点
            Node* nextNode = pos._node->_nextNode;

            // pos上一个节点->下一个节点 指向pos下一个节点
            prevNode->_nextNode = nextNode;
            // pos下一个节点->上一个节点 指向pos上一个节点
            nextNode->_prevNode = prevNode;
            // 释放pos位置节点
            delete pos._node;

            --_size;
            return iterator(nextNode);
        }

        // 判断为空
        bool Empty() {
            return _head->_nextNode == _head;
        }

        // 返回大小
        size_t Size() {
            return _size;
        }

        // 清空
        void Clear() {
            iterator it = begin();
            while (it != end()) {
                it = Erase(it);
            }
        }

        // 交换
        void Swap(DList<T>& list)
        {
            std::swap(_head, list._head);
            std::swap(_size, list._size);
        }

        // 打印测试
        void Print() {
            Node* beginNode = _head->_nextNode;
            while (beginNode != _head) {
                cout << beginNode->_data << " ";
                beginNode = beginNode->_nextNode;
            }
            cout << endl;
        }
    
    // 公有成员函数
    public:
        // 运算符重载:[] - 传统写法
        /*DList<T>& operator=(const DList<T>& list)
        {
            if (this != &list)
            {
                this->Clear();
                for (const auto& v : list)
                {
                    PushBack(v);
                }
        
                return *this;
            }
        }*/

        // 运算符重载:[] - 现代写法
        DList<T>& operator=(DList<T> list) {
            Swap(list);
            return *this;
        }

    };
}

【3.20】vector和list的对比

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

vector

list

底 层 结 构

动态顺序表,一段连续空间

带头结点的双向循环链表

随 机 访 问

支持随机访问,访问某个元素效率O(1)

不支持随机访问,访问某个元素 效率O(N)

插 入 和 删 除

任意位置插入和删除效率低,需要搬移元素,时间复杂 度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低

任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1)

空 间 利 用 率

底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高

底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低

迭 代 器

原生态指针

对原生态指针(节点指针)进行封装

迭 代 器 失 效

在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效

插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响

使 用 场 景

需要高效存储,支持随机访问,不关心插入删除效率

大量插入和删除操作,不关心随 机访问

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/664800.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

io.netty学习(六)字节缓冲区 ByteBuf(上)

目录 前言 ByteBuf类 ByteBuffer 实现原理 ByteBuffer 写入模式 ByteBuffer 读取模式 ByteBuffer 写入模式切换为读取模式 clear() 与 compact() 方法 ByteBuffer 使用案例 总结 前言 网络数据传输的基本单位是字节&#xff0c;缓冲区就是存储字节的容器。在存取字节…

双目结构光 实现高度测量

这里使用了两个大恒金星相机&#xff0c;一个投影仪。 相机镜头以及投影仪的架设&#xff1a; 相机镜头以及投影仪的架设&#xff1a; 注意相对位置的摆放&#xff0c;投影仪的光源照亮范围要超过相机的视野。 相机与光源调整好位置后&#xff0c;调整成像效果。两个镜头的光…

传教士与野人过河问题(numpy、pandas)

努力是为了不平庸~ 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。 目录 一、问题描述 二、问题解释 1.算法分析 2.程序执行流程 3.编写程序对问题进行求解 三、问题思路 1. 算法分析&#xff1a; 2. 实验执…

flink 实时数仓构建与开发[记录一些坑]

记-flink 实时数仓搭建、开发、维护笔记 业务场景描述数仓架构数仓分层odsdimdwddws 数仓建模注意项数仓建模开发规范命名规范 问题与原因分析1、debezium 采集pg 表&#xff0c;数据类型问题2、业务库出现大批量刷表数据&#xff0c;debezium采集connector 可能会挂3、业务库出…

MySQL面试题--索引概念以及底层

目录 概述 索引的底层数据结构 二叉树 B树 B树 B树与B树对比: 面试回答 大纲 回答 概述 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构(有序)。 在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff08;B树&#xff0…

chatgpt赋能python:Python扩展开发:从入门到精通

Python扩展开发&#xff1a;从入门到精通 Python是一门高效、可扩展、易学易用的编程语言。Python的优秀性能在科学计算、数据处理、web开发等领域表现突出。然而&#xff0c;Python在特定的应用场景中&#xff0c;如图像处理和机器学习等领域&#xff0c;需要更高效的代码执行…

8.4 IP地址与端口号

目录 IP地址 IP地址及编址方式 IP 地址及其表示方法 点分十进制记法举例 IP 地址采用 2 级结构 分类的 IP 地址 分类的 IP 地址 多归属主机 各类 IP 地址的指派范围 ​编辑 一般不使用的特殊的 IP 地址 ​编辑 分类的 IP 地址的优点和缺点 划分子网 无分类编址 CIDR 无…

【C/C++数据结构与算法】C语言万年历

目录 项目分析 项目效果 头文件及全局变量 获取天数 打印月份、年份日历 main函数 项目分析 实现查询某一个年份、月份&#xff0c;以日历的格式打印为了观赏性利用符号对打印的每一个日期进行分格特殊节日日期能够在日历中标注出来万年历的起始日期是公元1年&#xff0…

【随笔闲谈】软件工程导论

目录 一、软件工程概述 二、启动阶段 三、计划阶段 四、实施阶段 五、收尾阶段 一、软件工程概述 软件危机&#xff1a;在计算机软件的开发和维护过程中遇到的一系列严重问题。 软件危机的产生与自身的特点有关&#xff0c;还与软件开发、管理的方法不正确有关。 软件危…

chatgpt赋能python:Python打开目录:快速浏览目录中的所有文件

Python打开目录&#xff1a;快速浏览目录中的所有文件 Python是一种流行的编程语言&#xff0c;其可扩展性和易学性使其成为一种受欢迎的语言。Python的强大功能之一就是能够操作文件和目录。在本文中&#xff0c;我们将讨论如何使用Python在Windows、Mac和Linux上打开目录并列…

Opencv-C++笔记 (11) : opencv-图像二值化与LUB查找表

文章目录 一、概述二、THRESH_BINARY和THRESH_BINARY_INV三、THRESH_TRUNC四、THRESH_TOZERO和THRESH_TOZERO_INV五、THRESH_OTSU和THRESH_TRIANGLE六、LUT查找表 一、概述 我们在上一节程序中生成了一张只有黑色和白色的图像&#xff0c;这种“非黑即白”的图像像素的灰度值无…

MFC 非线程创建模态化窗口 实现工具栏拓展

1 实现基本工具栏 1.1 在Dlg.h文件中声明变量和定义资源ID #define ID_BUTTONS 501CToolBar m_toolbar; //工具栏 CImageList m_imageList; //工具栏图片 CImageList m_hotImageList; //工具栏热点图片 CReBar m_Rebar; //可以在位图上显示子窗口口 用来显示背景 CString…

【DeepLearning】Ubuntu中深度学习环境配置完整流程

Ubuntu中深度学习环境配置完整流程 1 显卡驱动2 cuda3 cuDNN4 torch5 torchvision 1 显卡驱动 支持 cuda 的所有显卡型号: Link 查询显卡型号 lspci -nn | grep VGA即 Vendor ID:Device ID 为 10de:21c4&#xff0c;在浏览器或者 Link 中搜索。 填写显卡信息: Link 选择要下载…

Jenkins-pipeline自动化构建Java应用

本实验操作需要&#xff1a;Jenkins&#xff0c;git代码仓库&#xff08;如gitlab&#xff0c;gitee等都可以&#xff09;&#xff0c;maven&#xff0c;docker&#xff0c;docker镜像仓库&#xff08;habor&#xff0c;nexus或者阿里云ACR等&#xff09;以及k8s环境。 前期准…

nginx特点以及安装

目录 1.特点 2.nginx和apache的区别 3.nginx应用场景 4.安装nginx 5. 更新nginx版本 6.总结 1.特点 高性能 轻量级web服务软件 稳定性高 系统自选消耗低 对http并发链接处理能力高 #处理并发连接能力 1.cup个数 2.本地服务器最大文件打开数 2.nginx和apache的区别 ng…

chatgpt赋能python:打包Python应用程序成deb包

打包Python应用程序成deb包 随着Python编程语言的不断发展&#xff0c;越来越多的开发者使用Python编写应用程序。然而&#xff0c;将Python程序打包并制作成deb包以进行安装可能仍然是一个难点。本文将介绍如何使用Debian打包工具&#xff0c;将Python应用程序制作成deb包。 …

chatgpt赋能python:Python扩展库介绍

Python扩展库介绍 Python是一种广泛使用的编程语言&#xff0c;它的易用性和可扩展性是许多开发者选择它的原因之一。这个语言有着丰富的扩展库&#xff0c;让开发者能够更加高效地编写代码。在这篇SEO文章中&#xff0c;我们将介绍几个与Python相关的扩展库。 NumPy NumPy是…

RPC远程调用

简介 PRC是一种调用方式而不是一种协议 在本地调用方式时由于方法在同一个内存空间&#xff0c;所以程序中可以直接调用该方法&#xff0c;但是浏览器端和服务端程序是不在一个内存空间的&#xff0c;需要使用网络来访问&#xff0c;就需要使用TCP或者UDP协议&#xff0c;由于…

使用frp工具实现内网穿透以及配置多个ssh和web服务

frp简介 FRP 项目地址 https://github.com/fatedier/frp/blob/master/README_zh.md frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http 和 https 应用协议提供了额外的能力,且尝试性支持了点对点穿透。 环境准备 ssh连接 1. 需要一台可以直接访问…

简要介绍 | 交叉熵损失:原理和研究现状

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对交叉熵损失进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 注2&#xff1a;"简要介绍"系列的所有创作均使用了AIGC工具辅助 交叉熵损失&#xff1a;原理、研究现状与未来展望 Under…