构造函数 | 接口说明 |
---|---|
list() | 构造空的list |
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list (const list& x) | 拷贝构造函数 |
list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |
list<int> ls1();
//构造空的list对象
list<int> ls2(10, 2);
//创建10个元素值为2的对象
list<int> ls3(ls2);
//拷贝构造函数
list<int> ls4(l3.begin(), l3.end());
//使用迭代器区间创建一个ls4对象
函数声明 | 接口说明 |
---|---|
begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 |
1、begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
三种遍历方式
list<int> ls; //构造空的list对象
ls.push_back(1);
ls.push_back(2);
ls.push_back(3);
ls.push_back(4);
//迭代器遍历
list<int>::iterator it = ls.begin();
while (it != ls.end())
{
cout << *it << " ";
it++;
}
//范围for遍历
for(auto e : ls)
{
cout << e << " ";
}
//使用数组区间构造一个对象,对这个对象进行遍历
int arr[] = {
8,4,5,11,23,66,89,45,8,65,65 };
list<int>ls1(arr, arr + sizeof(arr) / sizeof(arr[0]));
for (auto e : ls1)
{
cout << e << " ";
}
cout << endl;
读者使用的时候可以结合文档去查询使用
函数声明 | 接口说明 |
---|---|
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
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中的有效元素 |
assign | 将新内容分配给容器 |
splice | 将元素从x转移到容器中,并将它们插入到位置。 |
remove | 在容器中删除val这个值,如果没有该值不做处理 |
unique | 只会删除一段区间连续重复的值,前提是要求这个容器是有序的 |
front函数原型
reference front();
//返回list的第一个元素的引用
const_reference front() const;
//函数返回返回list的第一个元素的引用类型是const_reference,对象属性就不允许被修改了
back函数原型
reference back();
//返回list的最后一个元素的引用
const_reference back() const;
//函数返回一个const_reference,对象属性就不允许被修改了
注意:
成员类型reference和const_reference是对容器元素的引用类型
测试:
void func()
{
list<int> ls;
ls.push_back(11);
ls.push_back(4);
//返回list的第一个元素和list的最后一个元素将他们的值相减
cout << ls.front() - ls.back() << endl;//7
}
push_front
函数原型:
void push_front (const value_type& val);
在list的开头,当前的第一个元素之前头插入一个新元素
pop_front
void pop_front();
删除list容器中的第一个元素,有效地将其大小减少1
测试:
void func()
{
list<int> ls;
for (int i = 0; i < 5; i++)
{
ls.push_front(i); //往list容器插入5个值
}
for (auto e : ls)
{
cout << e << " "; //4 3 2 1 0
}
cout << endl;
for (int i = 0; i < 5; i++)
{
ls.pop_front(); //头删list数据
}
cout << ls.size() << endl; //0
}
push_back
void push_back (const value_type& val);
在列表容器的末尾,当前最后一个元素之后添加一个新元素
这有效地增加了容器的大小1。
pop_back
void pop_back();
删除列表容器中的最后一个元素,有效地将容器大小减少1。
测试:
void func()
{
list<int> ls;
for (int i = 0; i < 5; i++)
{
ls.push_back(i);//往list容器插入5个值
}
for (auto e : ls)
{
cout << e << " "; //0 1 2 3 4
}
cout <<"\n插入值后size: "<< ls.size() << endl;
for (int i = 0; i < 5; i++)
{
cout << "删除值时size: " << ls.size() << endl;
ls.pop_front(); //尾删list数据
}
}
insert 的三个版本
//通过在指定位置的元素之前插入新元素来扩展容器。
iterator insert (iterator position,
const value_type& val);
//通过在指定位置的元素之前插入n个值为val的新元素来扩展容器。
void insert (iterator position, size_type n,
const value_type& val);
//通过在指定位置的元素之前插入一段迭代器区间的值来扩展容器。
template <class InputIterator>
void insert (iterator position,
InputIterator first, InputIterator last);
测试:
void func()
{
list<int> ls;
ls.push_back(1);
ls.push_back(2);
ls.push_back(3);
ls.push_back(4);
list<int> ::iterator it = ls.begin();
it++; //it++完后指向的时list的第二个元素
ls.insert(it, 30); //it位置前插入30
for (auto e : ls) {
cout << e << " "; } //1 30 2 3 4
cout << endl;
it++; //it此时指向值为3的元素
//pos位置前插入一段连续的值
int arr[] = {
10,20,30 };
ls.insert(it,arr,arr + sizeof(arr) / sizeof(arr[0]));
for (auto e : ls) {
cout << e << " "; } //1 30 2 10 20 30 3 4
cout << endl;
it--;
ls.insert(it, 5,1);
for (auto e : ls) {
cout << e << " "; } //1 30 2 10 20 1 1 1 1 1 30 3 4
cout << endl;
}
erase
//从列表容器中移除指定pos的单个元素(position)
iterator erase (iterator position);
//从列表容器中移除指定一段区间的元素([first,last))。
iterator erase (iterator first, iterator last);
返回值:
返回指向被函数调用删除元素下一个元素的迭代器。 如果操作删除了序列中的最后一个元素,
测试:
void func1()
{
list<int> ls;
ls.push_back(1);
ls.push_back(2);
ls.push_back(3);
ls.push_back(4);
for (auto e : ls) {
cout << e << " "; }
cout << endl;
cout << "移除前capacity:" << ls.size() << endl;
//移除值后打印
list<int> ::iterator it = ls.begin();
ls.erase(it); //删除第一个值
for (auto e : ls) {
cout << e << " "; }
cout << "\n移除后capacity:" << ls.size() << endl;
it = ls.begin();
//移除剩余的值
ls.erase(it,ls.end()); //删除一段区间的值
for (auto e : ls) {
cout << e << " "; }
cout << "\n移除后capacity:" << ls.size() << endl;
}
在list类的内部也有一个swap函数,swap函数常用于交换两个list容器里面的元素
测试:
void func2()
{
list<int>l1; //1 2
l1.push_back(1);
l1.push_back(2);
list<int>l2; //2 3 4
l2.push_back(2);
l2.push_back(3);
l2.push_back(4);
l1.swap(l2);
for (auto e : l1) {
cout << e << " "; } //2 3 4
cout << endl;
for (auto e : l2) {
cout << e << " "; }// 1 2
}
从运行结果可以看出确实swap函数是交换容器中的元素的
void func2()
{
list<int>L1, L2;
L1.push_back(1);
L1.push_back(2);
L1.push_back(2);
L1.push_back(3);
L1.push_back(4);
L2.push_back(10);
list<int>::iterator it = L2.begin();
L2.splice(it, L1);
for (auto e : L2)
{
cout << e << " "; //此时L2容器里面的元素是1 2 2 3 4 10
}
cout << endl;
cout << " L1.size():" << L1.size() << endl;//0
cout << " L2.size():" << L2.size() << endl;//6
L2.remove(10);
for (auto e : L2)
{
cout << e << " "; //此时L2容器里面的元素是1 2 2 3 4 10
}
cout << endl;
L2.remove(10);
//remove只会删除容器中已有的元素,如果没有该值,remove不处理
for (auto e : L2)
{
cout << e << " "; //此时L2容器里面的元素是1 2 2 3 4 10
}
}
功能:去重
注意:unique只会处理连续的相等元素组中删除除第一个元素外的所有元素。
测试:
void func2()
{
int arr[] = {
1,2,2,2,2,5,2,3,2,5};
list<int> ls(arr, arr+ sizeof(arr) / sizeof(arr[0]) - 1);
cout << "容器创建" << endl;
for (auto e : ls)
{
cout << e << " ";//1 2 2 2 2 5 2 3 2
}
cout << "\n开始去重" << endl;
ls.unique();
for (auto e : ls)
{
cout << e << " ";//1 2 5 2 3 2
}
cout << "\n--------------------------" << endl;
//现象 :容器中剩余的重复的值并没有去除掉
//原因1:unique只会处理连续的相等元素组中删除除第一个元素外的所有元素。
//原因2:数组是无序的
int arr1[] = {
1,2,2,2,2,5,2,3,2,5 };
list<int> ls1(arr1, arr1 + sizeof(arr1) / sizeof(arr1[0]) - 1);
cout << "\n容器创建" << endl;
for (auto e : ls1)
{
cout << e << " ";//1 2 2 2 2 5 2 3 2
}
cout << "\n开始去重" << endl;
ls1.sort(); //先排序再去重
ls1.unique();
for (auto e : ls1)
{
cout << e << " ";//1 2 3 5
}
}
程序的功能是去除容器中的偶数值
void func3()
{
int arr[] = {
1,2,3,4,5,6 };
list<int> ls(arr,arr + sizeof(arr) / sizeof(arr[0]) - 1);
list<int>::iterator it = ls.begin();
while (it != ls.end())
{
if (*it % 2 == 0)
{
ls.erase(it);
//erase 会删除it指向的这个结点,
//当这个结点被删除了,那么it指向的这个空间就会
//被操作系统回收,程序员没有使用权限,
//再去解引用的时候就会有非法访问,程序崩溃
}
it++;
}
for (auto e : ls)
{
cout << e << " "; // 预期结果是:1 3 5
}
}
void func3()
{
int arr[] = {
1,2,3,4,5,6 };
list<int> ls(arr,arr + sizeof(arr) / sizeof(arr[0]) - 1);
list<int>::iterator it = ls.begin();
while (it != ls.end())
{
if (*it % 2 == 0)
{
it = ls.erase(it);
//删除该值后,返回此元素的下一个元素的迭代器,
// it被更新,即使旧值失效也不影响
}
else
{
it++;
}
}
}
类声明
template<class T>
struct __list_node
{
__list_node<T>* prev;
__list_node<T>* next;
T data;
//提供全缺省参数,这里使用匿名对象,
//T()会去调用T类型的构造函数
__list_node(const T& val = T())
:prev(nullptr)
,next(nullptr)
,data(val)
{
}
};
定义 __list_node结构用于创建结点
迭代器类
用于去模拟原生指针的行为,和原生指针的区别是意义是不一样的,但是模拟实现的行为是类似的
template<class T,class Ref, class Ptr>
struct __list_iterator
{
typedef __list_iterator<T, Ref, Ptr> Self;
typedef __list_node<T> Node;
Node* _node;
__list_iterator(Node* node)
:_node(node)
{
}
Ref operator*()
{
return _node->data;
}
Ptr operator->()
{
return &_node->data;
}
bool operator!=(const Self &it)
{
return it._node != _node;
}
Self operator++(int)
{
Self tmp((*this)._node);
++(*this);
return tmp;
}
Self& operator++()
{
_node = _node->next;
return *this;
}
Self& operator--()
{
_node = _node->prev;
return *this;
}
Self operator--(int)
{
Self tmp((*this)._node);
--(*this);
return tmp;
}
};
//这里分别提供三个模板参数
// T:表示data的类型
//Ref:表示引用的类型
//Ptr:表示指针类型
template<class T,class Ref, class Ptr>
//三个模板参数前面已经解释了
template<class T,class Ref, class Ptr>
struct __list_iterator
{
typedef __list_iterator<T, Ref, Ptr> Self;
//使用typedef 为迭代器类型取别名的方式会更简洁,
//所以推荐使用这种方式,另外也可以针对不同的模板参数类型
//产生不同类型的迭代器对象,读者可以细细体会其中的妙用
typedef __list_node<T> Node;
Node* _node;//这里的_node用于记录迭代器此时的位置
//通过Node类型的指针作为参数调用迭代器的构造函数
//来初始化迭代器的对象的成员_node
__list_iterator(Node* node)
:_node(node) //初始化列表方式
{
}
};
这里想先让读者读懂list的框架,再来看后面实现的接口函数,便于整体的学习
//重载*运算符
Ref operator*() //Ref的类型是跟模板参数类型有关的,引用或常引用
{
return _node->data;
//上面说过_node是用来记录迭代器的位置的,
//对指针解引用返回该位置的值
}
//重载->运算符
Ptr operator->() //Ptr 的类型是跟模板参数类型有关的, T* 或者 const T *
{
return &_node->data;
//返回对象的指针就能通过->访问其成员变量
}
//后置++
Self operator++(int) //Self :这是一个迭代器对象
{
//调用iterator的构造函数用指针去创建一个局部迭代器对象,
//tmp出了作用域会被销毁掉,
Self tmp((*this)._node);
++(*this); //复用 operator++()
return tmp; //后置++返回++之前的值,中间为了返回会创建临时对象
}
Self& operator++() // Self :这是一个迭代器对象
{
_node = _node->next;//更新迭代器的位置,往后走
return *this; //前置++返回++之后的值
}
Self& operator--()//Self :这是一个迭代器对象
{
_node = _node->prev;//更新迭代器的位置,往前走
return *this; //前置--返回--之后的值
}
Self operator--(int)//Self :这是一个迭代器对象
{
//使用指针创建一个局部的迭代器对象
Self tmp((*this)._node);
--(*this); //复用operator--()
return tmp; //后置--返回--之前的值
}
类声明
template<class T>
class list
{
typedef __list_node<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
list()
{
_head = new Node();
_head->prev = _head;
_head->next = _head;
}
//拷贝构造函数
list(const list<T>& ls)
{
_head = new Node();
_head->next = _head;
_head->prev = _head;
for (auto& e : ls)
{
push_back(e);
}
}
//operator=
list<T>& operator=(list<T> ls)
{
swap(_head, ls._head);
return *this;
}
//返回普通迭代器
iterator begin()
{
return iterator(_head->next);
}
iterator end()
{
return iterator(_head);
}
//const迭代器
const_iterator begin()const
{
return const_iterator(_head->next);
}
const_iterator end()const
{
return const_iterator(_head);
}
//指定pos位置插入结点
void Insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* newNode = new Node(val);
Node* prev = cur->prev;
prev->next = newNode;
newNode->prev = prev;
newNode->next = cur;
cur->prev = newNode;
}
//尾插
void push_back(const T& val)
{
Insert(end(),val);
}
//头插
void push_Front(const T& val)
{
Insert(begin(), val);
}
iterator erase(iterator pos)
{
assert(pos._node != _head);
Node* cur = pos._node;
Node* prev = cur->prev;
Node* next = cur->next;
prev->next = next;
next->prev = prev;
delete cur;
return next;
}
bool empty() {
return _head->next == _head->prev; }
//尾删
void Pop_back()
{
assert(!empty());
erase(--end());
}
//头删
void Pop_Front()
{
assert(!empty());
erase(begin());
}
//清空容器
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
//析构
~list()
{
clear();
delete _head;
_head = nullptr;
}
private:
Node* _head;
};
template<class T>
class list
{
//推荐写法,简洁
typedef __list_node<T> Node;
public:
//普通迭代器类型:可读可写
typedef __list_iterator<T, T&, T*> iterator;
//const迭代器类型:只读
typedef __list_iterator<T, const T&,
const T*> const_iterator;
private:
Node* _head; //list是一个双向带头循环的链表结构,
//所以这里我们需要先定义一个_head头节点
};
这里同样的为了便于后面的学习也是先理解框架,为整体的理解梳理一个好的思路
//返回普通迭代器
iterator begin()
{
//使用指针创建一个迭代器对象,返回可读可写迭代器对象
//这个迭代器对象记录的是第一个结点的位置
return iterator(_head->next);
}
iterator end()
{
//使用指针创建一个迭代器对象,返回可读可写迭代器对象
//这个迭代器对象记录的是最后一个结点的下一个位置
return iterator(_head);
}
//const迭代器
const_iterator begin()const
{
//使用指针创建一个迭代器对象,返回只读的迭代器对象
//这个迭代器对象记录的是第一个结点的位置
return const_iterator(_head->next);
}
const_iterator end()const
{
//使用指针创建一个迭代器对象,返回可读迭代器对象
//这个迭代器对象记录的是最后一个结点的下一个位置
return const_iterator(_head);
}
以上几种方式均会产生临时对象,读者了解即可
//指定pos位置插入结点
void Insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* newNode = new Node(val);
Node* prev = cur->prev;
prev->next = newNode;
newNode->prev = prev;
newNode->next = cur;
cur->prev = newNode;
}
//指定pos位置删除结点
iterator erase(iterator pos)
{
assert(pos._node != _head);
Node* cur = pos._node;
Node* prev = cur->prev;
Node* next = cur->next;
prev->next = next;
next->prev = prev;
delete cur;
return next;
}
//尾插
void push_back(const T& val)
{
Insert(end(),val);
}
//头插
void push_Front(const T& val)
{
Insert(begin(), val);
}
bool empty() {
return _head->next == _head->prev; }
//尾删
void Pop_back()
{
assert(!empty());
erase(--end());
//end()返回的是_head->prev,
// --end() 复用operator--()
//也可以写成这种形式 :erase(_head->prev);
}
//头删
void Pop_Front()
{
assert(!empty());
erase(begin());
//begin()返回的是第一个结点的迭代器
//erase删除第第一个结点
}
void clear() //使用迭代器遍历删除结点
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
构造函数
list() //构造函数创建一个头节点
{
_head = new Node();
_head->prev = _head;
_head->next = _head;
}
拷贝构造函数
//拷贝构造函数
list(const list<T>& ls)
{
//创建头节点
_head = new Node();
_head->next = _head;
_head->prev = _head;
//将ls对象遍历的同时将结点取出来尾插在_head的后面
for (auto& e : ls)
{
push_back(e);
}
}
operator=
//operator=
list<T>& operator=(list<T> ls)
{
//传统写法
if (this != &ls)
{
clear(); //清空this对象,只保留头节点
for (auto e : ls)
{
push_back(e);
//将ls对象的结点尾插在this对象后面
}
}
}
operator=
list<T>& operator=(list<T> ls)
{
//现代写法
swap(_head, ls._head);
//交换this对象的_head和ls对象的_head
//_head就可以去连接ls对象中的结点了
return *this;
}
~list()
{
//完成资源清理,释放list对象的所有的结点
clear();
delete _head;
_head = nullptr;//防止野指针
}
vector是一个动态增长的数组,可以支持随机访问,很好的支持一些排序比如:二分查找、堆算法等等
缺点:头插和指定pos位置插入效率低,因为要挪动数据,空间不够需要增容,增容有性能的消耗
list是一个双向带头循环的链表,是一个任意位置插入删除数据时间效率上都是O(1)
缺点:不支持随机访问,遍历速度慢
总结:vector和list是两个相辅相成的容器