目录
前言
一、list 的使用
1、构造函数
2、迭代器
3、增删查改
4、其他函数使用
二、list 的模拟实现
1、节点的创建
2、push_back 和 push_front
3、普通迭代器
4、const 迭代器
5、增删查改(insert、erase、pop_back、pop_front)
6、构造函数和析构函数
6.1、默认构造
6.2、构造 n 个 val 的对象
6.3、拷贝构造
6.4、迭代器区间构造
6.5、 赋值运算符重载
6.6、析构函数
三、list 模拟实现源代码
四、list 的迭代器失效
五、list 和 vector的对比
构造函数 | 接口说明 |
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 |
int main()
{
// 默认构造
list lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// 拷贝构造
list lt2(lt);
// 构造 n 个节点
list lt3(5, 1);
// 迭代器区间构造
list lt4(lt.begin(), lt.end());
return 0;
}
函数声明 | 接口说明 |
begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置 |
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9 };
list lt(a, a + 9);
auto it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
it++;
}
cout << endl;
return 0;
}
迭代器一般是用来遍历和查找的;
而反向迭代器的使用是类似的,只不过调用的函数换成了 rbegin 和 rend 。
注意:反向迭代器的迭代使用的也是++。但迭代器区间一样是[rbegin, rend);
函数声明 | 接口说明 |
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 中的有效元素 |
int main()
{
vector v = { 1,2,3,4,5,6,7,8,9 };
list lt(v.begin(), v.end());
for (auto e : lt) cout << e << " ";
cout << endl;
lt.push_front(10);
lt.push_back(20);
for (auto e : lt) cout << e << " ";
cout << endl;
lt.pop_front();
lt.pop_back();
for (auto e : lt) cout << e << " ";
cout << endl;
auto pos = find(lt.begin(), lt.end(), 5);
lt.insert(pos, 50);
for (auto e : lt) cout << e << " ";
cout << endl;
pos = find(lt.begin(), lt.end(), 8);
lt.erase(pos);
for (auto e : lt) cout << e << " ";
cout << endl;
return 0;
}
函数声明 | 接口说明 |
empty | 检测 list 是否为空,是返回 true ,否则返回 false |
size | 返回 list 中有效节点的个数 |
front | 返回 list 的第一个节点中值的引用 |
back | 返回 list 的最后一个节点中值的引用 |
template
struct list_node//节点
{
list_node* _next;
list_node* _prev;
T _data;
// 构造函数
list_node(const T& x = T())
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{}
};
由于节点存储的数据可能是任意类型,所以我们需要将将节点定义为模板类。这里我们需要写一个给缺省值的默认构造函数,便于之后在主类中new一个新节点时直接初始化,同时将两个指针置为空,将数据写入数据域中。
class list
{
public:
typedef list_node node;
private:
node* _head;
}
//尾插
void push_back(const T& x) const
{
node* new_node = new node(x);
node* tail = _head->_prev;
//链接节点之间的关系
tail->_next = new_node;
new_node->_prev = tail;
new_node->_next = _head;
_head->_prev = new_node;
}
//头插
void push_front(const T& x)
{
node* head = _head->_next;
node* new_node = new node(x);
_head->_next = new_node;
new_node->_prev = _head;
new_node->_next = head;
head->_prev = new_node;
}
这里模拟的头插和尾插也很简单,因为和我们之前在数据结构时候的双向循环链表是一样的,只需要找到头或者尾,然后链接四个节点间的关系即可。
注意:list 的迭代器是自定义类型,不是原生指针node*。
迭代器为自定义类型,其中*,++等都是通过运算符重载来完成的。
所以我们需要重载的符号:*,->,前置++,后置++,前置--,后置--,!=,==;
template
struct __list_iterator
{
typedef list_node node;
typedef __list_iterator self;
node* _node;
//构造函数
__list_iterator(node* n)
:_node(n)
{}
//重载*运算符
T& operator*()
{
return _node->_val;
}
T* operator->()
{
return &_node->_data;
}
//重载前置++运算符
self& operator++()
{
_node = _node->_next;
return *this;
}
//重载后置++运算符
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
//重载前置--运算符
self& operator--()
{
_node = _node->_prev;
return *this;
}
//重载后置--运算符
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
//重载!=运算符
bool operator!=(const self& s)
{
return _node != s._node;
}
//重载==运算符
bool operator==(const self& s)
{
return _node == s._node;
}
};
此处我实现了一个简单的正向迭代器,使用一个模板参数T表示类型。
当普通迭代器封装好了之后,我们需要在list类中来实现它的 begin() 和 end() 方法。由于迭代器的名字一般都是 iterator,而且对于范围for来说,也只能通过 iterator 来转换为迭代器进行遍历。所以这里我们将其typedef为iterator。
template
class list//链表
{
typedef list_node node;
public:
typedef __list_iterator iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
private:
node* _head;
};
const迭代器与普通迭代器的区别在于const迭代器指向的内容是不能修改的,但是它的指向是可以修改的。
template
class list//链表
{
typedef list_node node;
public:
typedef __list_const_iterator const_iterator;
const_iterator begin()
{
return const_iterator(_head->_next);
}
const_iterator end()
{
return const_iterator(_head);
}
private:
node* _head;
};
我们最好的做法就是在__list_iterator 的类模板中的添加两个模板参数,然后再 list 类中 typedef 两份分别将第二个参数分别改成 T& 和 const T& 的类型,本质上就是让编译器根据传入的 Ref 的不同来自动示例化出 const 迭代器类,而我们还需要重载一个->运算符,因为list中可能存储的是自定义类型,这个自定义类型如果要是有多个成员变量的话,我们就需要使用->来解引用访问成员变量,同样还是要区分普通迭代器和const 迭代器,所以就增加了另一个模版参数 Ptr。具体的解决做法如下:
template
struct __list_iterator
{
typedef list_node node;
typedef __list_iterator self;
node* _node;
__list_iterator(node* n)
:_node(n)
{}
Ref operator*()//解引用
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
...
};
然后,最终在链表类中使用如下:
template
class list//链表
{
typedef list_node node;
public:
typedef __list_iterator iterator;//普通迭代器
typedef __list_iterator const_iterator;//const迭代器
iterator begin()
{
return iterator(_head->_next);//匿名对象的返回
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end() const
{
return const_iterator(_head);
}
private:
node* _head;
};
// 指定位置插入
void insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = cur->_prev;
node* new_node = new node(x);
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = cur;
cur->_prev = new_node;
}
// 指定位置删除
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;
return iterator(next);
}
// 尾删
void pop_back()
{
erase(--end());
}
// 头删
void pop_front()
{
erase(begin());
}
由于后面会频繁对空进行初始化,所以在这里对它进行了封装,方便后面的调用。
void empty_init()//空初始化
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
//用n个val构造对象
list(int n, const T& val = T())
{
empty_init();
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
//拷贝构造传统写法
list(const list& lt)
{
empty_init();
for (auto e : lt)
{
push_back(e);
}
}
//拷贝构造现代写法
list(const list& lt)
{
empty_init();
list tmp(lt.begin(), lt.end());
swap(tmp);
}
template
list(Iterator first, Iterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
//赋值运算符重载
list& operator=(list lt)//注意这里不能用引用
{
swap(lt);
return *this;
}
//要全部清理掉
~list()
{
clear();
delete _head;
_head = nullptr;
}
//不释放头结点
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
//这样也可以
//erase(it++);
}
}
template
struct list_node//节点
{
list_node* _next;
list_node* _prev;
T _data;
list_node(const T& x = T())
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{}
};
template
struct __list_iterator
{
typedef list_node node;
typedef __list_iterator self;
node* _node;
__list_iterator(node* n)
:_node(n)
{}
Ref operator*()//解引用
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
//前置++
self& operator++()
{
_node = _node->_next;
return *this;
}
//后置++
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
//前置--
self& operator--()
{
_node = _node->_prev;
return *this;
}
//后置--
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
template
class list//链表
{
typedef list_node node;
public:
typedef __list_iterator iterator;//普通迭代器
typedef __list_iterator const_iterator;//const迭代器
iterator begin()
{
return iterator(_head->_next);//匿名对象的返回
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end() const
{
return const_iterator(_head);
}
void empty_init()//空初始化
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
//迭代器区间构造
template
list(Iterator first, Iterator last)
{
empty_init();
while (first != last)
{
push_back(*first);//push_back使用的前提是要有哨兵位的头结点
++first;
}
}
// 交换函数
void swap(list& tmp)
{
std::swap(_head, tmp._head);
}
//现代拷贝构造
list(const list& lt)
{
list tmp(lt.begin(), lt.end());
swap(tmp);
}
//现代赋值写法
list& operator=(list lt)
{
swap(lt);
return *this;
}
~list()//要全部清理掉
{
clear();
delete _head;
_head = nullptr;
}
void clear()//不释放头结点
{
iterator it = begin();
while (it != end())
{
it = erase(it);
//这样也可以
//erase(it++);
}
}
void insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = cur->_prev;
node* new_node = new node(x);
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = cur;
cur->_prev = new_node;
}
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;
return iterator(next);
}
//尾插
void push_back(const T& x) const
{
//node* new_node = new node(x);
//node* tail = _head->_prev;
链接节点之间的关系
//tail->_next = new_node;
//new_node->_prev = tail;
//new_node->_next = _head;
//_head->_prev = new_node;
insert(end(), x);
}
//头插
void push_front(const T& x)
{
//node* head = _head->_next;
//node* new_node = new node(x);
//_head->_next = new_node;
//new_node->_prev = _head;
//new_node->_next = head;
//head->_prev = new_node;
insert(begin(), x);
}
//尾删
void pop_back()
{
erase(--end());
}
//头删
void pop_front()
{
erase(begin());
}
private:
node* _head;
};
当我们使用 erase 进行删除后,此时指向删除位置的迭代器就失效了,再次使用就会令程序崩溃。
因此若要多次删除,则需要在使用后利用 erase 的返回值更新迭代器,这样使用才不会出现错误。
int main()
{
vector v = { 1, 2,3,5,4,6 };
list lt(v.begin(), v.end());
list::iterator pos = find(lt.begin(), lt.end(), 3);
for (int i = 0; i < 3; i++)
{
pos = lt.erase(pos); //利用erase的返回值更新迭代器
}
for (auto e : lt) cout << e << " ";
cout << endl;
return 0;
}
vector | list | |
底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随 机 访 问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素 效率O(N) |
插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂 度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1) |
空 间 利 用 率 | 底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高 | 底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低 |
迭 代 器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响 |
使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。