自学内容网 自学内容网

【C++】list容器及其模拟实现

目录

1. list的介绍及使用

1.1 list的介绍

1.2 list的使用

1.2.1 list的构造

1.2.2 list iterator的使用

1.2.3 list capacity

1.2.4 list element access

1.2.5 list modifiers

1.2.6 list的迭代器失效

2. list的模拟实现

2.1 模拟实现list

2.1.1list节点

2.1.2list常见功能接口

2.1.3list迭代器实现

2.1.4list构造与析构函数

2.1.5完整代码

2.2 list的反向迭代器

3. list与vector的对比


1. list的介绍及使用

1.1 list的介绍

list的文档介绍

在数据结构当中,我们学习过链表的一系列形式,带头、不带头、双向、单向、循环、不循环等形式,其中带头双向链表由于可以轻易找到头尾节点,某一节点前后节点,具有头结点,因此链表为空不需要做特殊处理等优势,作为链表最完美的形式。C++STL中list底层的结构就是采用带头双向循环链表(对list的理解需要建立在对数据结构有一定基础上,对于链表不了解的读者可以先移步学习链表。)

1.2 list的使用

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

1.2.1 list的构造

构造函数( (constructor)接口说明
list
list (size_type n, const value_type& val =
value_type())
构造的list中包含n个值为val的
元素
list()

构造空的list,有一个头结点

list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造
list

// list的构造
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;
}

1.2.2 list iterator的使用

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。当然由于链表的节点存储并不是一块连续的空间,因此list的迭代器并不是原生指针,而是通过封装实现的。

函数声明接口说明
begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin
+rend
返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置


// 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;
}

STL中范围统一都是左闭右开的区间,在list中由于头结点并不存储有效的数据,begin返回的是指向第一个有效数据的迭代器,end()返回最后一个元素的下一个位置,由于是循环链表,最后一个元素的下一个位置指向就是头结点。

list由于底层的链表存储空间不连续,因此list不在支持下标 +[]的随机访问,同样,比起之前string与vector,list的迭代器也发生了变化,list迭代器不再支持+或-某一常数来改变访问的指向。

这里我们需要在拓展补充一下迭代器的相关概念

基于功能上是正向还是反向遍历,迭代器可以分为正向迭代器iterator与反向迭代器reverse_iterator,以及支持const对象的迭代器。

基于底层结构的不同,实现出来的迭代器又有性质上的不同,可以分为单向、双向、随机迭代器(STL底层还抽象出一个最小单位的输入迭代器,我们使用的只有上诉三个迭代器,这个最小单位可以不考虑),不同迭代器支持的迭代操作不同,单向迭代器只支持单向移动,只能++,双向迭代器如我们本次学习list支持++、--两个方向迭代,而随机迭代器支持通过+、-某一个对象跳跃式移动到某一个位置。STL中所实现的一些组件,由于底层实现存在+、-、++、--等操作,对于有的迭代器是不支持的,如上段代码中sort底层实现式快排加堆排存在+、-等操作,因此传入的迭代器必须是随机迭代器。(不同的迭代器之间其实还存在者继承等复杂关系,本文不在深入探讨)

由上图不同迭代器支持的符号,我们可以得出使用范围上随机迭代器>双向迭代器>单向迭代器。所以在日后的使用中,我们需要根据文档查询对应支持的迭代器类型。

【注意】
1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

1.2.3 list capacity

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

1.2.4 list element access

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

1.2.5 list modifiers

函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

// list插入和删除
// push_back/pop_back/push_front/pop_front
void TestList3()
{
int array[] = { 1, 2, 3 };
list<int> L(array, array + sizeof(array) / sizeof(array[0]));

// 在list的尾部插入4,头部插入0
L.push_back(4);
L.push_front(0);
PrintList(L);

// 删除list尾部节点和头部节点
L.pop_back();
L.pop_front();
PrintList(L);
}

// insert /erase 
void TestList4()
{
int array1[] = { 1, 2, 3 };
list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));

// 获取链表中第二个节点
auto pos = ++L.begin();
cout << *pos << endl;

// 在pos前插入值为4的元素
L.insert(pos, 4);
PrintList(L);

// 在pos前插入5个值为5的元素
L.insert(pos, 5, 5);
PrintList(L);

// 在pos前插入[v.begin(), v.end)区间中的元素
vector<int> v{ 7, 8, 9 };
L.insert(pos, v.begin(), v.end());
PrintList(L);

// 删除pos位置上的元素
L.erase(pos);
PrintList(L);

// 删除list中[begin, end)区间中的元素,即删除list中的所有元素
L.erase(L.begin(), L.end());
PrintList(L);
}

// resize/swap/clear
void TestList5()
{
// 用数组来构造list
int array1[] = { 1, 2, 3 };
list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
PrintList(l1);

// 交换l1和l2中的元素
list<int> l2;
l1.swap(l2);
PrintList(l1);
PrintList(l2);

// 将l2中的元素清空
l2.clear();
cout << l2.size() << endl;
}

list中还有一些操作,需要用到时大家可参阅list的文档说明。

1.2.6 list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器(该迭代器变为野指针),其他迭代器不会受到影响。

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);erase会返回当前删除节点的下一节点
}
}

2. list的模拟实现

2.1 模拟实现list

要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,通过上面的学习,这些内容已基本
掌握,现在我们来模拟实现list。

2.1.1list节点

//链表的节点,由于存储的数据不定,我们这里实现成模板
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;

list_node(const T& data = T())
:_data(data)
,_next(nullptr)
,_prev(nullptr)
{}
};

2.1.2list常见功能接口

void clear()
{
auto it = begin();//挨个释放list中节点资源
while (it != end())
{
it = erase(it);//复用删除功能
}
}

void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}

void push_back(const T& x)
{
/*Node* newnode = new Node(x);
Node* tail = _head->_prev;

tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;

++_size;*/

insert(end(), x);//尾插直接复用插入代码
}

void push_front(const T& x)
{
insert(begin(), x);//头插直接复用插入代码
}

iterator insert(iterator pos, const T& x)
{
            //插入一个新节点
Node* cur = pos._node;//当前位置节点
Node* prev = cur->_prev;//前驱节点
Node* newnode = new Node(x);

// prev newnode cur
newnode->_next = cur;//新节点next指向当前位置节点
cur->_prev = newnode;//当前节点前驱指针指向新节点
newnode->_prev = prev;//新前驱节点的前驱指针指向前驱节点
prev->_next = newnode;//前驱节点next指向新节点

++_size;//更新list中节点个数

return newnode;//返回新节点指针,返回值在根据指针构造出对应迭代器
}

void pop_back()
{
erase(--end());//尾删直接复用对应删除代码
}

void pop_front()
{
erase(begin());//前删直接复用对应删除代码
}

iterator erase(iterator pos)
{
assert(pos != end());//不能删除头结点

Node* prev = pos._node->_prev;//将删除节点前驱跟后驱节点的指向相连
Node* next = pos._node->_next;

prev->_next = next;
next->_prev = prev;
delete pos._node;//释放删除节点的资源

--_size;

return next;//返回删除节点后驱节点指针,返回值在根据指针构造出对应迭代器
                        //这一步返回操作可以更新外部指向删除节点的迭代器的值
                        //防止迭代器失效的问题
}

size_t size() const
{
return _size;
}

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

   

2.1.3list迭代器实现

由于list的底层是链表,存储空间并不连续,因此,我们需要利用模板进行封装。首先需要说明的是,由于const_iterator指向的对象不能修改,因此如果我们通过重载的函数返回引用或者指针,那么针对const_iterator跟iterator,我们就需要写两份非常相似的代码,非常冗余,对此库里面增加Ref与Ptr两个模板参数用来控制返回值中的引用与指针,传入的参数是const对像,函数返回的引用与指针就是const修饰的,如果是普通对象,就是普通的指针和引用,我们就不再需要写两份非常相似的代码了。

因为list的迭代器,我们无法通过原生指针实现,为了能过还原对应的指针操作,我们需要在迭代器类中有一个成员变量是节点指针,同时由于迭代器的相关操作经常使用,我们这里使用struct定义类。

template<class T, class Ref, class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;//通过模板参数控制iterator与const_iterator 
                                                //Ptr指针,Ref引用
                                               //不同返回值
Node* _node;

list_iterator(Node* node)
:_node(node)
{}

Ref operator*()//模拟解引用指针,我们需要将迭代器指向节点的数据返回
{
return _node->_data;
}

Ptr operator->()//模拟访问节点内部指针,我们需要将迭代器指向节点的内部成员指针返回
{
return &_node->_data;
}

Self& operator++()//模拟迭代器++,向后移动
{
_node = _node->_next;//迭代器内部的节点指针后移
return *this;//返回新节点的引用,在根据引用,构造一个新迭代器返回
}

Self& operator--()
{
_node = _node->_prev;
return *this;
}

Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;

return tmp;
}

Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;

return tmp;
}

bool operator!=(const Self& s) const
{
return _node != s._node;
}

bool operator==(const Self& s) const
{
return _node == s._node;
}
};


/*template<class T>//如果不通过函模板参数控制,我们就需要因为const_iterator多写一份非常相 
                       //似的代码
struct list_const_iterator
{
typedef list_node<T> Node;
typedef list_const_iterator<T> Self;
Node* _node;

list_const_iterator(Node* node)
:_node(node)
{}

const T& operator*()
{
return _node->_data;
}

const T* operator->()
{
return &_node->_data;
}

Self& operator++()
{
_node = _node->_next;
return *this;
}

Self& operator--()
{
_node = _node->_prev;
return *this;
}

Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;

return tmp;
}

Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;

return tmp;
}

bool operator!=(const Self& s) const
{
return _node != s._node;
}

bool operator==(const Self& s) const
{
return _node == s._node;
}
};*/

2.1.4list构造与析构函数

与之前string和vector实现不同,空list中构造出来是有一个头结点,头节点的前驱和后驱指针都指向自己。因此,我们这里要单独实现一个空初始化函数。

void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}

list()
{
empty_init();
}

list(initializer_list<T> il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}

// lt2(lt1)
list(const list<T>& lt)
{
empty_init();

for (auto& e : lt)
{
push_back(e);
}
}

// lt1 = lt3 现代写法的赋值重载
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}

~list()
{
clear();
delete _head;
_head = nullptr;
}

2.1.5完整代码

#pragma once
#include<assert.h>

namespace zlr
{
template<class T>
struct list_node//链表的节点
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;

list_node(const T& data = T())
:_data(data)
,_next(nullptr)
,_prev(nullptr)
{}
};

template<class T, class Ref, class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;//通过模板参数控制iterator与const_iterator 
                                                //不同返回值
Node* _node;

list_iterator(Node* node)
:_node(node)
{}

Ref operator*()
{
return _node->_data;
}

Ptr operator->()
{
return &_node->_data;
}

Self& operator++()
{
_node = _node->_next;
return *this;
}

Self& operator--()
{
_node = _node->_prev;
return *this;
}

Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;

return tmp;
}

Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;

return tmp;
}

bool operator!=(const Self& s) const
{
return _node != s._node;
}

bool operator==(const Self& s) const
{
return _node == s._node;
}
};


/*template<class T>//如果不通过函模板参数控制,我们就需要因为const_iterator多写一份非常相 
                       //似的代码
struct list_const_iterator
{
typedef list_node<T> Node;
typedef list_const_iterator<T> Self;
Node* _node;

list_const_iterator(Node* node)
:_node(node)
{}

const T& operator*()
{
return _node->_data;
}

const T* operator->()
{
return &_node->_data;
}

Self& operator++()
{
_node = _node->_next;
return *this;
}

Self& operator--()
{
_node = _node->_prev;
return *this;
}

Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;

return tmp;
}

Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;

return tmp;
}

bool operator!=(const Self& s) const
{
return _node != s._node;
}

bool operator==(const Self& s) const
{
return _node == s._node;
}
};*/

template<class T>
class list
{
typedef list_node<T> Node;
public:
/*typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;*/

typedef list_iterator<T, T&, T*> iterator;//将类型重新封装,便于使用,统一风格
typedef list_iterator<T, const T&, const T*> const_iterator;

iterator begin()
{

/*iterator it(_head->_next);
return it;*/

//return iterator(_head->_next);
return _head->_next;
}

iterator end()
{
return _head;
}

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

const_iterator end() const
{
return _head;
}

void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}

list()
{
empty_init();
}

list(initializer_list<T> il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}

// lt2(lt1)
list(const list<T>& lt)
{
empty_init();

for (auto& e : lt)
{
push_back(e);
}
}

// lt1 = lt3
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}

~list()
{
clear();
delete _head;
_head = nullptr;
}

void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}

void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}

void push_back(const T& x)
{
/*Node* newnode = new Node(x);
Node* tail = _head->_prev;

tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;

++_size;*/

insert(end(), x);//直接复用代码
}

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

iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);

// prev newnode cur
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;

++_size;

return newnode;
}

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

void pop_front()
{
erase(begin());
}

iterator erase(iterator pos)
{
assert(pos != end());

Node* prev = pos._node->_prev;
Node* next = pos._node->_next;

prev->_next = next;
next->_prev = prev;
delete pos._node;

--_size;

return next;
}

size_t size() const
{
return _size;
}

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

private:
Node* _head;
size_t _size;
};

struct AA
{
int _a1 = 1;
int _a2 = 1;
};

// 按需实例化
// T* const ptr1
// const T* ptr2
template<class Container>
void print_container(const Container& con)
{
// const iterator -> 迭代器本身不能修改
// const_iterator -> 指向内容不能修改
typename Container::const_iterator it = con.begin();
//auto it = con.begin();
while (it != con.end())
{
//*it += 10;

cout << *it << " ";
++it;
}
cout << endl;

for (auto e : con)
{
cout << e << " ";
}
cout << endl;
}

void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);

list<int>::iterator it = lt.begin();
while (it != lt.end())
{
*it += 10;

cout << *it << " ";
++it;
}
cout << endl;

for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
print_container(lt);

list<AA> lta;
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
list<AA>::iterator ita = lta.begin();
while (ita != lta.end())
{
//cout << (*ita)._a1 << ":" << (*ita)._a2 << endl;
// 特殊处理,本来应该是两个->才合理,为了可读性,省略了一个->
cout << ita->_a1 << ":" << ita->_a2 << endl;
cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;

++ita;
}
cout << endl;
}

void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);

// insert以后迭代器不失效
list<int>::iterator it = lt.begin();
lt.insert(it, 10);
*it += 100;

print_container(lt);

// erase以后迭代器失效
// 删除所有的偶数
it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
it = lt.erase(it);
}
else
{
++it;
}
}

print_container(lt);
}

void test_list3()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);

list<int> lt2(lt1);

print_container(lt1);
print_container(lt2);

list<int> lt3;
lt3.push_back(10);
lt3.push_back(20);
lt3.push_back(30);
lt3.push_back(40);

lt1 = lt3;
print_container(lt1);
print_container(lt3);
}

void func(const list<int>& lt)
{
print_container(lt);
}

void test_list4()
{
// 直接构造
list<int> lt0({ 1,2,3,4,5,6 });
// 隐式类型转换
list<int> lt1 = { 1,2,3,4,5,6,7,8 };
const list<int>& lt3 = { 1,2,3,4,5,6,7,8 };

func(lt0);
func({ 1,2,3,4,5,6 });

print_container(lt1);

//auto il = { 10, 20, 30 };
/*initializer_list<int> il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
cout << sizeof(il) << endl;*/
}
}

2.2 list的反向迭代器

通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,
因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对
正向迭代器的接口进行包装即可。

template<class Iterator>
class ReverseListIterator
{
// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态
成员变量
// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
public:
typedef typename Iterator::Ref Ref;
typedef typename Iterator::Ptr Ptr;
typedef ReverseListIterator<Iterator> Self;
public:
//
// 构造
ReverseListIterator(Iterator it) : _it(it) {}
//
// 具有指针类似行为
Ref operator*() {
Iterator temp(_it);
--temp;
return *temp;
}
Ptr operator->() { return &(operator*()); }
//
// 迭代器支持移动
Self& operator++() {
--_it;
return *this;
}
Self operator++(int) {
Self temp(*this);
--_it;
return temp;
}
Self& operator--() {
++_it;
return *this;
}
Self operator--(int)
{
Self temp(*this);
++_it;
return temp;
}
//
// 迭代器支持比较
bool operator!=(const Self& l)const { return _it != l._it; }
bool operator==(const Self& l)const { return _it != l._it; }
Iterator _it;
};

3. list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及
应用场景不同,其主要不同如下:(vector与list的主要区别还是基于底层顺序表与链表的区别,对区别感兴趣的读者可以移步看笔者【初阶数据结构】顺序表与链表的比较(附题)这篇文章)

vectorlist



动态顺序表,一段连续空间带头结点的双向循环链表


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




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




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


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




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


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


原文地址:https://blog.csdn.net/2401_82610555/article/details/143868570

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!