前言
在vector容器的简单模拟实现中,我们构造了一个简单的vector容器,那相比于其它的容器,vector有哪些优势呢?
- vector的元素是连续存储的,所以vector的遍历速度非常快。访问下一个元素,不需要利用指针间接寻址,这使得vector元素的线性扫描(
find()
和copy()
)接近最优。 - vector支持简单且高效的随机访问,这使得vector上的很多算法(如
sort()
和binary_search()
)非常高效。
vector在oj题目里有一个比较常用的用法:二维数组。一个3×4的vector<vector<int>>
的内存布局如下所示:
vector有没有什么缺点?当然是有的,vector除了在尾部位置插入删除以外,在其它位置进行插入删除操作都涉及到移动元素,这样做时间复杂度很高。那如果我们希望在一个序列中添加和删除元素的同时无需移动其它元素,我们应该使用list。
一、list的介绍
list
是C++标准库(STL)提供的一种带头双向循环链表:
不同于vector,list中的每个元素在物理空间上不一定连续,即list的元素都独立分配空间,而且要保存指向前驱和后继的指针。
list可以在任意位置插入和删除,且时间复杂度为O(1)
。当你向一个list插入元素或从一个list删除元素时,list中其他元素的位置不会受到影响,特别是,指向其他元素的迭代器也不会受到影响。
list不支持下标访问,我们可以用迭代器遍历链表,list提供了双向迭代器,forward_list(单向链表)只提供了单向迭代器。
二、list的使用
2.1 list的构造
认识一个容器,先要学会它的构造函数:
构造函数((constructor)) | 接口说明 |
---|---|
list(size_type n, const value_type&val = value_type()) | 用n个值为val的元素构造 |
list() | 构造一个空的list |
list(const list& x) | 拷贝构造函数 |
list(InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |
list的构造使用代码演示:
void TestList1()
{
list<int> first; // 构造空的first
list<int> second(4, 100); // second中放4个值为100的元素
list<int> third(second.begin(), second.end()); // 用second的[begin(), end())左闭右开的区间构造third
list<int> fourth(third); // 用third拷贝构造fourth
// 以数组为迭代器区间[)构造fifth
int array[] = { 16,2,77,29 };
list<int> fifth(array, array + sizeof(array) / sizeof(int));
// 初始化列表 格式 初始化,C++11支持
list<int> sixth{ 1,2,3,4,5 };
}
2.2 list iterator的使用
我们暂时把迭代器理解成一个指针,该指针指向list中的某个节点。
函数声明 | 接口说明 |
---|---|
begin+end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
rbegin+rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置 |
这种设计叫对称设计,rbegin和rend解引用(*)
返回的是前一个元素的引用。
list的迭代器使用代码演示:
void TestList2()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
// 使用正向迭代器正向打印list中的元素
// list<int>::iterator it = lt.begin(); // C++98中语法
auto it = lt.begin(); // C++11之后推荐写法
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 使用反向迭代器逆向打印list中的元素
// list<int>::reverse_iterator rit = lt.rbegin();
auto rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
运行结果:
2.3 list capacity
函数声明 | 接口说明 |
---|---|
empty | 检测list是否为空,是返回true,否则返回false |
size | 返回list中有效节点的个数 |
2.4 list element access
函数声明 | 接口说明 |
---|---|
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
2.5 list modifiers
函数声明 | 接口说明 |
---|---|
push_front(const value_type& val) | 将val添加到list的首元素之前 |
pop_front() | 删除list的首元素 |
emplace_front(Args&&… args) | 将T{args}添加到list的首元素之前 |
remove(const value_type& val) | 删除list中所有值等于val的元素 |
remove_if(Predicate pred) | 删除list中所有满足 pred()==true 的元素 |
unique() | 删除list中所有的相邻重复元素 |
unique(BinaryPredicate binary_pred) | 删除list中所有的相邻重复元素,用pred进行相等判定 |
merge(list& x) | 合并有序链表,用<确定序;将x合并入list,然后将其清空 |
merge(list& x, Compare comp) | 合并有序链表,用comp确定序;将x合并入list,然后将其清空 |
sort() | 排序list,用<确定序 |
sort(Compare comp) | 排序list,用comp确定序 |
reverse() | 反转list中的元素顺序;不抛出异常 |
splice(iterator position, list& x) | 将x的元素插入到pos之前;x变为空 |
splice(iterator position, list& x, iterator i) | 将i指向的x中的元素插入到pos之前;该元素从x中删除 |
splice(iterator position, list& x, iterator first, iterator last) | 将[first,last)指向的x中的元素插入到p之前;这些元素从x中删除 |
2.5.1 push和pop
// list插入和删除
// push_back/pop_back/push_front/pop_front
void TestList3()
{
int array[] = { 1, 2, 3 };
list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
// 在list的尾部插入4,头部插入0
lt.push_back(4);
lt.push_front(0);
for (auto& x : lt)
{
cout << x << " ";
}
cout << endl;
// 删除list尾部节点和头部节点
lt.pop_back();
lt.pop_front();
for (auto& x : lt)
{
cout << x << " ";
}
cout << endl;
}
运行结果:
2.5.2 insert和erase
// insert /erase
void TestList4()
{
int array[] = { 1, 2, 3 };
list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
// 获取链表中第二个节点
auto pos = ++lt.begin();
cout << *pos << endl;
// 在pos前插入值为4的元素
lt.insert(pos, 4);
for (auto& x : lt)
{
cout << x << " ";
}
cout << endl;
// 在pos前插入5个值为5的元素
lt.insert(pos, 5, 5);
for (auto& x : lt)
{
cout << x << " ";
}
cout << endl;
// 在pos前插入[v.begin(), v.end)区间中的元素
vector<int> v{ 7, 8, 9 };
lt.insert(pos, v.begin(), v.end());
for (auto& x : lt)
{
cout << x << " ";
}
cout << endl;
// 删除pos位置上的元素
lt.erase(pos);
for (auto& x : lt)
{
cout << x << " ";
}
cout << endl;
// 删除list中[begin, end)区间中的元素,即删除list中的所有元素
lt.erase(lt.begin(), lt.end());
for (auto& x : lt)
{
cout << x << " ";
}
cout << endl;
}
运行结果:
2.5.3 emplace_front:
void TestList5()
{
list<pair<int, char>> lt;
lt.emplace_front(10, 'a');
lt.emplace_front(20, 'b');
lt.emplace_front(30, 'c');
for (auto& x : lt)
{
cout << " (" << x.first << "," << x.second << ")";
}
cout << endl;
}
运行结果:
2.5.4 remove和remove_if:
bool single_digit(const int& value)
{
return (value < 10);
}
void TestList6()
{
int arr[] = { 17,89,7,14 };
list<int> lt(arr, arr + 4);
lt.remove(89);
for (list<int>::iterator it = lt.begin(); it != lt.end(); ++it)
cout << *it << ' ';
cout << endl;
lt.remove_if(single_digit);
for (list<int>::iterator it = lt.begin(); it != lt.end(); ++it)
cout << *it << ' ';
cout << endl;
}
运行结果:
2.5.5 unique
void TestList7()
{
double arr[] = { 12.15, 2.72, 73.0, 12.77, 3.14, 12.77, 73.35, 72.25, 15.3, 72.25 };
list<double> lt(arr, arr + 10);
lt.sort();
for (auto& x : lt)
cout << x << ' ';
cout << endl;
lt.unique();
for (auto& x : lt)
cout << x << ' ';
cout << endl;
lt.unique(same_integral_part);
for (auto& x : lt)
cout << x << ' ';
cout << endl;
}
运行结果:
2.5.6 merge
bool cmp(double first, double second)
{
return (int(first) < int(second));
}
void TestList8()
{
std::list<double> lt1, lt2;
lt1.push_back(3.1);
lt1.push_back(2.2);
lt1.push_back(2.9);
lt2.push_back(3.7);
lt2.push_back(7.1);
lt2.push_back(1.4);
lt1.sort();
lt2.sort();
lt1.merge(lt2);
for (auto& x : lt1)
cout << x << ' ';
cout << endl;
// (lt2此时为空)
lt2.push_back(2.1);
for (auto& x : lt2)
cout << x << ' ';
cout << endl;
lt1.merge(lt2, cmp);
for (auto& x : lt1)
cout << x << ' ';
cout << endl;
}
运行结果:
2.5.7 reverse
void TestList9()
{
list<int> lt;
for (int i = 1; i < 10; ++i)
{
lt.push_back(i);
}
lt.reverse();
for (auto& x : lt)
cout << x << ' ';
cout << endl;
}
运行结果:
2.5.8 splice
void TestList10()
{
list<int> lt1, lt2;
list<int>::iterator it;
for (int i = 1; i <= 4; ++i)
lt1.push_back(i); // lt1: 1 2 3 4
for (int i = 1; i <= 3; ++i)
lt2.push_back(i * 10); // lt2: 10 20 30
it = lt1.begin();
++it; // it指向2
lt1.splice(it, lt2); // lt1: 1 10 20 30 2 3 4
// it仍然指向 2 // lt2此时为空
lt2.splice(lt2.begin(), lt1, it); // lt1: 1 10 20 30 3 4
// it失效 // lt2: 2
it = lt1.begin();
advance(it, 3); // advance使迭代器前进3个位置,it此时指向30
lt1.splice(lt1.begin(), lt1, it, lt1.end());
// lt1: 30 3 4 1 10 20
std::cout << "lt1:";
for (auto& x : lt1)
cout << x << ' ';
cout << endl;
std::cout << "lt2:";
for (auto& x : lt2)
cout << x << ' ';
cout << endl;
}
运行结果:
三、list的简单模拟实现
list是带头双向循环链表,它的简单实现应该包含一个哨兵位头结点,所以在定义list类前我们要先定义结点类:
//为了不与STL库里的list冲突,下面所有模拟实现的操作都在我们自己的命名空间内
namespace wh
{
template <class T>
struct ListNode {
ListNode(const T& val = T())
: _pPre(nullptr)
, _pNext(nullptr)
, _val(val)
{}
ListNode<T>* _pPre;
ListNode<T>* _pNext;
T _val;
};
}
有了结点类我们就可以简单定义一个list了。
template <class T>
class list {
typedef ListNode<T> Node;
typedef Node* PNode;
public:
// List的空构造
list()
{
CreateHead();
}
private:
void CreateHead()
{
_pHead = new Node;
_pHead->_pPre = _pHead;
_pHead->_pNext = _pHead;
_size = 0;
}
PNode _pHead;
size_t _size;
};
3.1 类对象的修改
3.1.1 push_back
为了能让我们的list类简单跑起来,先实现push_back,往list里面添加元素。过程如下图所示:
void push_back(const T& val)
{
PNode newnode = new Node(val);
PNode tail = _pHead->_pPre;
tail->_pNext = newnode;
newnode->_pPre = tail;
newnode->_pNext = _pHead;
_pHead->_pPre = newnode;
}
这个过程很像C语言时候的写法,有没有觉得很复杂?我们可以复用其它的类方法(insert)
,达到简化代码的目的。
void push_back(const T& val)
{
insert(end(), val);
}
3.1.2 insert
insert
实现的是在任意位置之前插入,所以要传入pos位置的迭代器(list的迭代器和vector不同,详情可以往下翻)。
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
PNode cur = pos._pNode; // 记录当前位置结点
PNode prev = cur->_pPre; // 记录当前位置的前一个结点
PNode newnode = new Node(val); // 创建新结点
prev->_pNext = newnode;
newnode->_pPre = prev;
newnode->_pNext = cur;
cur->_pPre = newnode;
++_size;
return newnode;
}
这里注意,list的insert
不存在迭代器失效的问题,因为list的每个结点都是独立的,且插入的过程不需要扩容。但是为了统一,还是要返回新插入元素的第一个位置的迭代器。
3.1.3 push_front
有了insert,我们自然可以实现push_front(头插)。
void push_front(const T& val)
{
insert(begin(), val);
}
3.1.4 pop_back
void pop_back()
{
erase(--end());
}
3.1.5 pop_front
void pop_front()
{
erase(begin());
}
3.1.6 erase
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
Node* cur = pos._pNode;
Node* prev = cur->_pPre;
Node* next = cur->_pNext;
prev->_pNext = next;
next->_pPre = prev;
delete cur;
--_size;
return next;
}
list的erase
存在迭代器失效的问题:
引用被函数删除的元素的迭代器、指针和引用是无效的。所有其他迭代器、指针和引用都保持其有效性。
在vs下继续使用被删除元素的迭代器会直接报错,只能对其重新赋值后使用,所以我们用被删除元素下一个位置的迭代器接收。
3.1.7 clear
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
clear
只是清除有效元素,不能删除头结点。
3.1.8 swap
void swap(list<T>& l)
{
std::swap(_pHead, l._pHead);
std::swap(_size, l._size);
}
3.2 类对象的遍历和访问
3.2.1 iterator和const_iterator
之前list的修改我们多次用到了迭代器,那list的迭代器究竟是什么样的?我们知道迭代器模仿的是T*
和const T*
的行为,list的每个结点在物理空间上都是独立的,内置指针不能完成++
访问下一个元素的这种操作。C++提供了一种方法,将迭代器封装成一个类,利用运算符重载来实现。
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*()
Ref operator*()
{
return _pNode->_val;
}
// T* operator->()
Ptr operator->()
{
return &_pNode->_val;
}
Self& operator++() // 前置++
{
_pNode = _pNode->_pNext;
return *this;
}
Self operator++(int) // 后置++
{
Self tmp(*this);
_pNode = _pNode->_pNext;
return tmp;
}
Self& operator--()
{
_pNode = _pNode->_pPre;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
_pNode = _pNode->_pPre;
return tmp;
}
bool operator!=(const Self& l)
{
return _pNode != l._pNode;
}
bool operator==(const Self& l)
{
return _pNode == l._pNode;
}
public:
PNode _pNode;
};
解引用(*)
大家都知道它的用法,那成员指针访问(->)
该怎么用呢?下面我们先看一段代码:
struct A
{
int _a1;
int _a2;
A(int a1 = 0, int a2 = 0)
: _a1(a1)
, _a2(a2)
{}
};
void test_list2()
{
list<A> lt;
A aa1(1, 1);
A aa2 = { 1, 1 };
lt.push_back(aa1);
lt.push_back(aa2);
lt.push_back(A(2, 2));
lt.push_back({ 3, 3 });
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
//cout << (*it)._a1 << ":" << (*it)._a2 << endl;
cout << it->_a1 << ":" << it->_a2 << endl;
//cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl; //编译器省略了一个->
++it;
}
cout << endl;
}
当T是自定义类型A时,要访问A类中的成员,有两种方法:一种是对迭代器解引用得到A,再用A._a1
访问_a1
;另外一种是直接利用箭头运算符访问it->_a1
。这个时候有人就会奇怪为什么会设置这种用法,原因是我们经常用自定义类型指针箭头A*->_a1
来访问_a1
,迭代器模仿的就是T*
的行为,可以看出这种设计是非常巧妙的。
定义了ListIterator
类之后,我们就可以在list
类中使用begin
和end
方法了。
template <class T>
class list {
typedef ListNode<T> Node;
typedef Node* PNode;
public:
typedef ListIterator<T, T&, T*> iterator; //typedef可以定义内置类型
typedef ListIterator<T, const T&, const T*> const_iterator; //这里typedef是重命名
public:
iterator begin() { return _pHead->_pNext; }
iterator end() { return _pHead; }
const_iterator cbegin() const { return _pHead->_pNext; }
const_iterator cend() const { return _pHead; }
private:
PNode _pHead;
size_t _size;
};
3.2.2 reverse_iterator和const_reverse_iterator
简单实现reverse_iterator就是模仿ListIterator
定义一个ListIReverseIterator
,里面的内容大差不差,但是库里面不是这样写的,它使用了迭代器适配器来构建反向迭代器。直接上代码:
template <class Iterator, class Ref, class Ptr>
class ReverseIterator {
public:
typedef ReverseIterator<Iterator, Ref, Ptr> Self;
ReverseIterator(Iterator it)
: _it(it)
{}
Ref operator*() //解引用返回前一个元素
{
Iterator tmp = _it;
return *(--tmp);
}
Ptr operator->() //箭头返回指向前一个元素的指针
{
return &(operator*());
}
Self& operator++()
{
--_it;
return *this;
}
Self& operator--()
{
++_it;
return *this;
}
bool operator!=(const Self& s)
{
return _it != s._it;
}
bool operator==(const Self& s)
{
return _it == s._it;
}
public:
Iterator _it;
};
定义了ReverseIterator
类之后,我们就可以在list
类中使用rbegin
和rend
方法了。
template<class T>
class list
{
public:
typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;
reverse_iterator rbegin() { return reverse_iterator(end()); } //返回的匿名对象,也可以写成return end();
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator crbegin() const { return const_reverse_iterator (end()); }
const_reverse_iterator crend() const { return const_reverse_iterator (begin()); }
}
3.2.3 front和back
// List Access
T& front()
{
return begin()._node->_val;
}
const T& front()const
{
return begin()._node->_val;
}
T& back()
{
return --end()._node->_val;
}
const T& back()const
{
return --end()._node->_val;
}
3.3 容量操作
3.3.1 size
size_t size() const
{
return _size;
}
3.3.2 empty
bool empty() const
{
return _size == 0;
}
3.4 默认成员函数
为什么把默认成员函数放到最后,因为它复用了很多其它的类方法。
3.4.1 (constructor)
// List的构造
list()
{
CreateHead();
}
list(int n, const T& value = T())
{
CreateHead();
while (n--)
{
push_back(value);
}
_size = n;
}
template <class Iterator>
list(Iterator first, Iterator last)
{
CreateHead();
while (first != last)
{
push_back(*first);
++first;
}
}
list(const list<T>& l) //拷贝构造
{
CreateHead();
for (auto& e : l)
{
push_back(e);
}
}
3.4.2 operator=
list<T>& operator=(const list<T> l)
{
swap(l);
return *this;
}
3.4.3 (destructor)
~list()
{
if (!empty())
{
clear();
}
delete _pHead;
_pHead = nullptr;
}
好了,这篇文章到此就结束了,说实话,写完我人还是有点懵的,如果有错误轻喷(。ŏ_ŏ)