文章目录
- 一、list的介绍
- 二、list的使用
-
- 1.构造函数
- 2.容量操作
- 3.元素访问
- 4.修改操作
- 5.其他接口
- 6.排序的性能分析
- 三、list迭代器的实现
-
- 1.迭代器的分类
- 2.list迭代器的失效问题
- 3.list迭代器的模拟实现
-
- 3.1普通迭代器
- 3.2const 迭代器
- 3.3完整版迭代器
- 3.4迭代器总结
- 四、模拟实现完整代码
-
- 1.list.h
- 2.test.cpp
- 五、vector和list的区别
一、list的介绍
list和sting、vector一样,我们可以使用cplusplus文档进行查询:list的文档介绍
【总结】
1.list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代
2.list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素
3.list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效
4.与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好
5.与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
二、list的使用
1.构造函数
构造函数 | 接口说明 |
---|---|
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list() | 构造空的list |
list (const list& x) | 拷贝构造函数 |
list (InputIterator fifirst, InputIterator last) | 用[fifirst, last)区间中的元素构造list |
2.容量操作
函数声明 | 接口说明 |
---|---|
empty | 检测list是否为空,是返回true,否则返回false |
size | 返回list中有效节点的个数 |
3.元素访问
函数声明 | 接口说明 |
---|---|
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
4.修改操作
函数声明 | 接口说明 |
---|---|
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中的有效元素 |
void list_test3()
{
listint> lt;
// 尾插
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
listint>::iterator it = lt.begin();
while (it != lt.end())
{
cout *it " ";
++it;
}
cout endl;
// 头插
lt.push_front(10);
lt.push_front(20);
lt.push_front(30);
lt.push_front(40);
for (auto e : lt)
{
cout e " ";
}
cout endl;
// 在pos之前插入数据
auto pos = find(lt.begin(), lt.end(), 3);
if (pos != lt.end())
{
lt.insert(pos, 30);
}
cout *pos endl;
(*pos)++;
for (auto e : lt)
{
cout e " ";
}
cout endl;
// 删除pos位置数据
lt.erase(pos);
for (auto e : lt)
{
cout e " ";
}
cout endl;
}
5.其他接口
函数声明 | 接口说明 |
---|---|
splice | 将list1的元素转移到list2中 |
remove | 移除list中指定元素 |
remove_if | 有条件的移除元素 |
unique | 去重 |
merge | 合并两个链表 |
sort | 链表排序 |
reverse | 链表逆置 |
void list_test4()
{
listint> lt;
lt.push_back(10);
lt.push_back(2);
lt.push_back(5);
lt.push_back(3);
lt.push_back(4);
lt.push_back(4);
lt.push_back(6);
lt.push_back(4);
lt.push_back(0);
for (auto e : lt)
{
cout e " ";
}
cout endl;
lt.remove(3);
for (auto e : lt)
{
cout e " ";
}
cout endl;
lt.remove(30);
for (auto e : lt)
{
cout e " ";
}
cout endl;
lt.sort();
for (auto e : lt)
{
cout e " ";
}
cout endl;
// 先排序,才能实现去重
lt.unique();
for (auto e : lt)
{
cout e " ";
}
cout endl;
}
【注意】
1.由于list的物理结构是非连续的–前一个节点和后一个节点的地址大小是不确定的,所以list不支持随机访问,也没有了重载[]操作,因为重载的话效率很低O(N)
2.list不支持reserve操作,因为list的节点使用时开辟,使用完销毁,不能预留空间
3.链表排序只能使用list提供的sort接口,而不能使用algorithm提供的sort接口,因为链表物理地址不连续,迭代器为双向迭代器,不支持±操作,而算法库中的sort函数需要支持±的随机迭代器
4.链表去重之前必须保证链表有序,否则去重不完全
5.两个有序链表合并之后仍然有序
尽管list提供了这些额外的函数接口,他们有一定的作用,但是实际上这些特殊接口使用的频率很低,保存sort接口(链表排序的效率很低)
6.排序的性能分析
链表排序只能使用list提供的sort接口,而不能使用algorithm提供的sort接口,但是list提供的sort函数排序的效率非常低,我们通过下面的两组数据测试来直观的感受
一、vector排序与list排序对比
// N个数据需要排序,vector+ 算法sort list+ sort
void test_op()
{
srand(time(0));
const int N = 100000;
vectorint> v;
v.reserve(N);
listint> lt;
for (int i = 0; i N; ++i)
{
auto e = rand();
v.push_back(e);
lt.push_back(e);
}
int begin1 = clock();
sort(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
lt.sort();
int end2 = clock();
printf("vector sort:%dn", end1 - begin1);
printf("list sort:%dn", end2 - begin2);
}
二、list将数据拷贝到vector排序,排完以后再拷贝回来和list排序对比
// N个数据需要排序,vector+ 算法sort list+ sort
void test_op()
{
srand(time(0));
const int N = 100000;
vectorint> v;
v.reserve(N);
listint> lt1;
listint> lt2;
for (int i = 0; i N; ++i)
{
auto e = rand();
lt1.push_back(e);
lt2.push_back(e);
}
// 拷贝到vector排序,排完以后再拷贝回来
int begin1 = clock();
for (auto e : lt1)
{
v.push_back(e);
}
sort(v.begin(), v.end());
size_t i = 0;
for (auto& e : lt1)
{
e = v[i++];
}
int end1 = clock();
int begin2 = clock();
lt2.sort();
int end2 = clock();
printf("copy vector sort:%dn", end1 - begin1);
printf("list sort:%dn", end2 - begin2);
}
我们可以看到,list sort的效率远低于vector sort ,甚至我们可以说,我们先把数据从list拷贝到vector中,在vector中进行排序完毕之后再拷贝到list的效率都比直接在list进行排序的效率高,此外,我们在测试性能的时候,我们在release版本下比在debug版本下得到的结果更具有参考价值。
三、list迭代器的实现
1.迭代器的分类
根据迭代器的功能,我们可以将迭代器分为三类:
1.单向迭代器:迭代器仅仅支持++和解引用操作,单链表的迭代器就是单向迭代器
2.双向迭代器:迭代器支持++ 、–和解引用操作,但不支持+ 、-操作,list双向带头循环链表就是双向迭代器
3.随机迭代器:迭代器支持 ++ – + -以及解引用操作,即迭代器能够随机访问,string 和vector 就是随机迭代器
函数声明 | 接口说明 |
---|---|
begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 |
【注意】
- begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
- rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
2.list迭代器的失效问题
迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响
list 和vector不同,list进行insert操作之后不会出现迭代器失效的问题,因为list插入的节点是动态开辟的,此外,list每个节点的物理地址是不相关的,所以插入的新节点并不会影响原来其他节点的地址
但是,list erase之后就会出现迭代器失效的问题,因为list删除节点时候直接将该节点释放了,此时我们再次访问该节点就会出现野指针的访问,从而造成越界访问
3.list迭代器的模拟实现
我们知道,迭代器是一个类似于一个指针一样的东西,能够像指针一样进行操作,比如++ – * + – 等等,对于string和vector的迭代器来说,我们可以使用原生指针,因为他们的结构是一个数组,指针解引用就是那个位置的数据,++就会移动数据类型的大小,地址是连续的,所以天然的支持以上的操作
对于list来说,list的节点是一个结构体,此外,list的每一个节点的物理地址是不连续的,如果我们还像string 、vector一样使用原生指针,将节点的指针typedef为迭代器的话,那么此时它并不能实现解引用、++等操作,所以我们需要使用一个类来对迭代器进行封装,再使用运算符重载使得迭代器能够实现我们期望的解引用、++、–等操作
SGI版本C++源码中对list迭代器实现的框架如下:
// 定义节点
template class T>
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
// 定义迭代器
typedef __list_iteratorT, T&, T*> iterator;
typedef __list_iteratorT, const T&, const T*> const_iterator;
// 定义迭代器类
templateclass T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iteratorT, T&, T*> iterator;
typedef __list_iteratorT, const T&, const T*> const_iterator;
typedef __list_iteratorT, Ref, Ptr> self;
link_type node;
__list_iterator(link_type x) : node(x) {}
__list_iterator() {}
__list_iterator(const iterator& x) : node(x.node) {}
bool operator==(const self& x) const { return node == x.node; }
bool operator!=(const self& x) const { return node != x.node; }
reference operator*() const { return (*node).data; }
}
从上面我们可以看出,我们为迭代器专门实现了一个__list_iterator类,然后在类中对解引用,++ != ==等等进行重载,使得该迭代器能够像指针一样使用
3.1普通迭代器
list的普通迭代器的实现非常简单,我们只需要一个模板参数T来代表数据类型,然后通过运算符重载来实现迭代器的各种操作:
templateclass T>
// 定义节点结构
struct list_node
{
list_nodeT> _prev;
list_nodeT> _next;
T _data;
// 构造
list_node(const T& x)
:_prev(nullptr)
,_next(nullptr)
,_data(x)
{}
};
// typedef __list_iterator iterator;
templateclass T>
struct __list_iterator
{
typedef list_nodeT> node; // 将list节点重命名为node
node* _pnode; // 节点的指针作为类的成员变量
// 构造
__list_iterator(const node* p)
:_pnode(p)
{}
// 重载解引用
T& operator*()
{
return _pnode->_data;
}
// 重载前置++
__list_iteratorT>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
// 重载后置++
__list_iteratorT> operator++(int)
{
__list_iteratorT> tmp(*this);
_pnode = _pnode->_next;
return tmp;
}
// 重载前置--
__list_iteratorT> operator--()
{
_pnode = _pnode->_prev;
return *this;
}
// 重载后置--
__list_iteratorT> operator--(int)
{
__list_iteratorT> tmp(*this);
_pnode = _pnode->_prev;
return tmp;
}
// 重载!=
bool operator!=(const __list_iterator it)
{
return _pnode != it._pnode;
}
// 重载==
bool operator!=(const __list_iterator it)
{
return _pnode == it._pnode;
}
};
3.2const 迭代器
我们已经实现了迭代器的普通迭代器,那我们该如何实现const迭代器呢,const迭代器与不同迭代器的区别在于:const迭代器不能修改节点的数据,即operator*() 函数的返回值应该是const T&,我们的第一反应是在__list_iterator类中重载一个返回值为const T& 的operator*()函数如下:
// typedef __list_iterator iterator;
// typedef list_const_iterator const_iterator;
// 重载解引用
T& operator*()
{
return _pnode->_data;
}
const T& operator*() const
{
return _pnode->_data;
}
这个看起来可行,但实际上是可行的,因为const __list_iterator
所以我们应该为const迭代器设计一个单独的类__list_const_iteartor
templateclass T>
// 定义节点结构
struct list_node
{
list_nodeT> _prev;
list_nodeT> _next;
T _data;
// 构造
list_node(const T& x)
:_prev(nullptr)
,_next(nullptr)
,_data(x)
{}
};
// typedef __list_iterator iterator;
templateclass T>
struct __list_iterator
{
typedef list_nodeT> node; // 将list节点重命名为node
node* _pnode; // 节点的指针作为类的成员变量
// 构造
__list_iterator(const node* p)
:_pnode(p)
{}
// 重载解引用
T& operator*()
{
return _pnode->_data;
}
// 重载前置++
__list_iteratorT>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
// 重载后置++
__list_iteratorT> operator++(int)
{
__list_iteratorT> tmp(*this);
_pnode = _pnode->_next;
return tmp;
}
// 重载前置--
__list_iteratorT> operator--()
{
_pnode = _pnode->_prev;
return *this;
}
// 重载后置--
__list_iteratorT> operator--(int)
{
__list_iteratorT> tmp(*this);
_pnode = _pnode->_prev;
return tmp;
}
// 重载!=
bool operator!=(const __list_iteratorT> it)
{
return _pnode != it._pnode;
}
// 重载==
bool operator!=(const __list_iteratorT> it)
{
return _pnode == it._pnode;
}
};
// typedef list_const_iterator const_iterator;
templateclass T>
struct __list_const_iterator
{
typedef list_nodeT> node; // 将list节点重命名为node
node* _pnode; // 节点的指针作为类的成员变量
// 构造
__list_const_iterator(const node* p)
:_pnode(p)
{}
// 重载解引用
const T& operator*()
{
return _pnode->_data;
}
// 重载前置++
__list_const_iteratorT>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
// 重载后置++
__list_const_iteratorT> operator++(int)
{
__list_iteratorT> tmp(*this);
_pnode = _pnode->_next;
return tmp;
}
// 重载前置--
__list_const_iteratorT>& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
// 重载后置--
__list_const_iteratorT> operator--(int)
{
__list_iteratorT> tmp(*this);
_pnode = _pnode->_prev;
return tmp;
}
// 重载!=
bool operator!=(__list_const_iteratorT>& it)
{
return _pnode != it._pnode;
}
// 重载==
bool operator!=(__list_const_iteratorT>& it)
{
return _pnode == it._pnode;
}
};
我们可以看到,__list_iterator类和__list_const_iterator类除了类名和operator*()函数的返回值不同之外,其他地方完全相同,这就造成了代码的冗余,C++大佬提出了一个解决方案,就是在-_list_iterator中增加一个模板参数,代码如下:
// const迭代器--增加参数模板,解决operator*()返回值问题
//typedef __list_iterator iterator;
//typedef __list_const_iterator const_iterator
templateclass T,class Ref>
struct __list_iterator
{
typedef list_nodeT> node; // 重命名list 节点
typedef __list_iteratorT, Ref> Self; /
node* pnode;
__list_iterator(node* p)
:_pnode(p)
{}
Ref& operator*()
{
return _pnode->data;
}
// 重载前置++
Self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
// 重载后置++
Self operator++(int)
{
__list_iteratorT> tmp(*this);
_pnode = _pnode->_next;
return tmp;
}
// 重载前置--
Self operator--()
{
_pnode = _pnode->_prev;
return *this;
}
// 重载后置--
Self operator--(int)
{
__list_iteratorT> tmp(*this);
_pnode = _pnode->_prev;
return tmp;
}
// 重载!=
bool operator!=(const Self& it)
{
return _pnode != it._pnode;
}
// 重载==
bool operator!=(const Self& it)
{
return _pnode == it._pnode;
}
};
所以我们可以通过传递不同的参数让编译器实例化出不同的类,从而实现了普通迭代器和const迭代器
3.3完整版迭代器
我们从迭代器的源码中可以发现,迭代器有有三个模板参数,我们通过下面的案列引出第三个模板参数,假设list第一个参数T是一个自定义类Pos,其定义如下:
struct Pos
{
int _row;
int _col;
Pos(int row = 0, int col = 0)
:_row(row)
,_col(col)
{}
};
那么我们通过list迭代器以下面的方式来访问Pos中的成员:
listPos> lt;
Pos p1(1, 1);
lt.push_back(Pos(2,2));
lt.push_back(Pos(3,3));
listPos>::iterator it = lt.begin();
while (it != lt.end())
{
it->_row++;
cout (*it)._row ":" (*it)._col endl;
++it;
}
cout endl;
我们可以看到,我们需要先使用解引用的方式得到Pos节点,但是Pos节点是一个结构体类型,所以我们需要通过.的方式来获取结构体的成员,但是这样使用起来非常的别扭,因为迭代器是模拟指针的行为,而结构体指针访问数据的方式是类名->变量名,那么我们是否可以使用下面的方式来实现呢?
coutit->_row":"it->_colendl;
显然,这样是不行的,因为it是一个自定义类型的对象(__list_iterator类的对象),而只有结构体指针才能像上面那样访问成员,所以我们需要运算符重载it支持->操作,具体实现如下:
templateclass T,class Ref>
struct __list_iterator
{
T* operator->()
{
return &_pnode->data;
}
}
所以我么输出的代码就可以以下面的方式书写:
coutit->_row":"it->.colendl;
为什么不是两个->而是一个->呢?这是因为C++为了代码的可读性,省略了一个->。编译器会将->转换为it.operator->(),它得到节点数据的地址,也就是Pos*,所以实际上Pos*还需要通过->操作符来得到_row和_col,但是it->->_row可读性太差了,所以C++省略了一个->,让编译器对其进行处理it.operator->()->_row;
但是此时这里又会出现一个问题,那就是const迭代器需要operator->()的返回值为const T*,所以我们再增加一个模板参数,把第三个模板参数作为operator->()函数的返回值,使得编译器会根据传入参数的不同实例化出不同的__list_iterator类
所以我们list迭代器的完整代码如下:
// 封装迭代器
// 同一个类模板实例化出的两个类型
// typedef __list_iterator iterator;
// typedef __list_iterator const_iterator;
templateclass T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_nodeT> node;
typedef __list_iteratorT, Ref, Ptr> Self;
node* _pnode;
// 构造
__list_iterator(node* p)
:_pnode(p)
{}
// 重载箭头
Ptr operator->()
{
return &_pnode->_data;
}
// 重载*
Ref operator*()
{
return _pnode->_data;
}
// 重载前置++
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& it) const
{
return it._pnode != _pnode;
}
// 重载等于
bool operator==(const Self& it) const
{
return _pnode == it._pnode;
}
};
3.4迭代器总结
1.const迭代器实现的两种方法:
(1)为const迭代器单独写一个类,次类与原来类只有类名和operator*(),operator->()函数的返回值类型不同,但是这样会造成代码的冗余
(2)给const迭代器增加Ref,Ptr两个模板参数,让编译器根据传入的Ref,Ptr的不同自动实例化出不同的迭代器类
2.对于普通类来说,类名=类型,对于类模板来说,类名!=类型,类型=类名+模板参数,但需要注意的是,在类中,不管是否存在模板,类名都等于类型,但是为了规范,我们不建议这样使用
四、模拟实现完整代码
1.list.h
#pragma once
#include
#include
namespace hdp
{
// 定义节点结构
templateclass T>
struct list_node
{
list_nodeT>* _prev;
list_nodeT>* _next;
T _data;
// 构造
list_node(const T& x)
:_prev(nullptr)
,_next(nullptr)
,_data(x)
{}
};
// 封装迭代器
// 同一个类模板实例化出的两个类型
// typedef __list_iterator iterator;
// typedef __list_iterator const_iterator;
templateclass T,class Ref,class Ptr>
// 迭代器类
struct __list_iterator
{
typedef list_nodeT> node; // 重命名为list节点
typedef __list_iteratorT, Ref, Ptr> Self;
// 成员变量
node* _pnode;
// 构造
__list_iterator(node* p)
:_pnode(p)
{}
// 重载箭头
Ptr operator->()
{
return &_pnode->_data;
}
// 重载*
Ref operator*()
{
return _pnode->_data;
}
// 重载前置++
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& it) const
{
return it._pnode != _pnode;
}
// 重载等于
bool operator==(const Self& it) const
{
return _pnode == it._pnode;
}
};
// 跟普通迭代器的区别:遍历,不能用*it修改数据
/*template
struct __list_const_iterator
{
typedef list_node node;
node* _pnode;
__list_const_iterator(node* p)
:_pnode(p)
{}
const T& operator*()
{
return _pnode->_data;
}
__list_const_iterator& operator++()
{
_pnode = _pnode->_next;
return *this;
}
__list_const_iterator& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
bool operator!=(const __list_const_iterator& it)
{
return _pnode != it._pnode;
}
};*/
//vector
//vector
//vector>
// 类名 类型
// 普通类 类名 等价于 类型
// 类模板 类名 不等价于 类型
// 如:list模板 类名list 类型list
// 类模板里面可以用类名代表类型,但是建议不要那么用
// 定义list类
templateclass T>
class list
{
// list 节点
typedef list_nodeT> node;
public:
typedef __list_iteratorT, T&, T*> iterator; //迭代器
typedef __list_iteratorT, const T&, const T*> const_iterator; //const迭代器
// 迭代器
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
// iterator it(_head);
// return it;
// 匿名对象构造
return iterator(_head);
}
// const 迭代器
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
// 创建哨兵节点
void empty_initialize()
{
_head = new node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
// 构造 不是list的原因,构造函数名和类名相同,而list是类型
list()
{
empty_initialize();
}
// 迭代器构造
templateclass InputIterator>
list(InputIterator first, InputIterator last)
{
empty_initialize();
while (first != last)
{
push_back(*first);
++first;
}
}
// 拷贝构造
// lt2(lt1)
list(const listT>& lt)
{
empty_initialize();
for (const auto& e : lt)
{
push_back(e);
}
}
// 拷贝构造现代写法
list(listT>& lt)
{
empty_initialize();
listT> tmp(lt.begin(), lt.end());
swap(tmp);
}
// 赋值重载
listT>& operator=(const listT>& lt)
{
if (this != <)
{
clear();
for (const auto& e : lt)
{
push_back(e);
}
}
return *this;
}
// 赋值重载现代写法
listT>& operator=(listT> lt)
{
swap(lt);
return *this;
}
// 交换
void swap(listT>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
size_t size() const
{
return _size;
}
bool empty() const
{
// return _head->_next == _head;
// return _head->_prev == _head;
return _size == 0;
}
// 析构
~list()
{
clear();
delete _head;
_head = nullptr;
}
// 清理
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
_size = 0;
}
// 尾插
void push_back(const T& x)
{
//node* newnode = new node(x);
//node* tail = _head->_prev;
_head tail newnode
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
insert(end(), x);
}
// 头插
void push_front(const T& x)
{
insert(begin(), x);
}
// 尾删
void pop_back()
{
//earse(end()->prev);
erase(--end());
}
// 头删
void pop_front()
{
erase(begin());
}
// 在pos之前插入数据
iterator insert(iterator pos, const T& x)
{
node* newnode = new node(x);
node* cur = pos._pnode;
node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
return iterator(newnode);
}
// 删除pos位置的数据
iterator erase(iterator pos)
{
assert(pos != end());
node* prev = pos._pnode->_prev;
node* next = pos._pnode->_next;
prev->_next = next;
next->_prev = prev;
delete pos._pnode;
--_size;
return iterator(next);
}
private:
node* _head;
size_t _size = 0; // 保存节点个数
};
}
2.test.cpp
#include "list.h"
#include
using namespace hdp;
using std::cout;
using std::endl;
#include
// 测试迭代器
void list_test1()
{
listint> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// iterator 1、内嵌类型 2、行为像指针一样
listint>::iterator it = lt.begin();
while (it != lt.end())
{
cout *it " ";
++it;
}
cout endl;
for (auto e : lt)
{
cout e " ";
}
cout endl;
}
void test_list2()
{
listint> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_front(5);
lt.push_front(6);
for (auto e : lt)
{
cout e " ";
}
cout endl;
cout lt.size() endl;
lt.pop_back();
lt.pop_back();
lt.pop_front();
lt.pop_front();
for (auto e : lt)
{
cout e " ";
}
cout endl;
cout lt.size() endl;
}
struct Pos
{
int _row;
int _col;
Pos(int row = 0, int col = 0)
:_row(row)
, _col(col)
{}
};
void print_list(const listPos>& lt)
{
listPos>::const_iterator it = lt.begin();
while (it != lt.end())
{
//it->_row++;
cout it->_row ":" it->_col endl;
++it;
}
cout endl;
}
void list_test3()
{
listPos> lt;
Pos p1(1, 1);
lt.push_back(p1);
lt.push_back(p1);
lt.push_back(p1);
lt.push_back(Pos(2, 2));
lt.push_back(Pos(3, 3));
// int* p -> *p
// Pos* p -> p->
listPos>::iterator it = lt.begin();
//list::iterator it2 = it;
while (it != lt.end())
{
it->_row++;
//cout _row
cout it->_row ":" it->_col endl;
//cout ()->_row _col
++it;
}
cout endl;
print_list(lt);
}
int main()
{
//list_test1();
//test_list2();
list_test3();
return 0;
}
五、vector和list的区别
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net