Ciallo~(∠・ω< )⌒☆ ~ 今天,我将继续和大家一起学习C++基础篇第七章----list类 ~
目录
一 list的介绍
- 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- 2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
- 3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
- 4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
- 5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第7个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
二 迭代器的划分
从功能上划分(四种) | 从性质上划分(三种) -- 底层结构 |
---|---|
iterator | 单向:forward_list unordered_xxx 支持:++ |
reverse_iterator | 双向:list map set 支持: ++ -- |
const_iterator | 随机:vector string deque 支持: ++ -- + - |
const_reverse_iterator |
底层结构决定可以使用哪些算法
如:
库中的sort支持随机迭代器~
而reverse支持双向迭代器~
find支持各种类型的迭代器~
双向迭代器是特殊的单向迭代器,随机迭代器是特殊的单向迭代器,也是特殊的双向迭代器。
#include<list>
#include<algorithm>// 算法头文件
int main()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// 会报错~ 只支持随机迭代器~
sort(lt.begin(), lt.end());
string s = "ciallozmcl";
sort(s.begin(), s.end()); // accilllmoz
cout << s << endl;
return 0;
}
三 list的一些使用
3.1 push_back & emplace_back
struct A
{
public:
A(int a1 = 1, int a2 = 1)
:_a1(a1)
,_a2(a2)
{}
int _a1;
int _a2;
};
void test3()
{
list<A> lt;
A aa1(1, 1);
lt.push_back(aa1);
lt.push_back(A(1,1));
//lt.push_back(2, 2); // 不支持(只能有一个参数)
lt.emplace_back(aa1);
lt.emplace_back(A(1, 1));
lt.emplace_back(2, 2); // 支持直接传构造A对象的参数
}
- emplace_back 在一些情况下比 push_back 更高效 ( 少一次拷贝构造 )。
3.2 insert & erase
void test_insert_erase()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
auto it = lt.begin();
int k = 3;
while (k--)
{
++it; // 迭代器到3位置插入
}
lt.insert(it, 30);
for (auto e : lt) // 打印
{
cout << e << " ";
}
cout << endl;
int x = 0;
cin >> x;
it = find(lt.begin(), lt.end(), x); // 找有没有x
if (it != lt.end())
{
lt.erase(it);
}
for (auto e : lt) // 打印
{
cout << e << " ";
}
cout << endl;
}
3.3 sort & reverse
void test_sort_reverse()
{
list<int> lt;
lt.push_back(1);
lt.push_back(20);
lt.push_back(3);
lt.push_back(5);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
// 升序
lt.sort();
// 降序 - 仿函数
// 有名对象写法
less<int> ls;
greater<int> gt;
lt.sort(gt);
// 匿名对象写法
lt.sort(greater<int>());
// reverse使用
lt.reverse();
reverse(lt.begin(), lt.end());
}
3.4 merge & unique & splice
- merge:通过将 x 在其各自排序位置的所有元素转移到容器中,将 x 合并到列表中(两个容器都应已排序)。
void test_merge()
{
std::list<double> first, second;
first.push_back(3.1);
first.push_back(2.2);
first.push_back(2.9);
second.push_back(3.7);
second.push_back(7.1);
second.push_back(1.4);
first.sort();
second.sort();
first.merge(second);
}
- unique:去除相等元素,只有当元素与紧随其后的元素相比相等时,才会从容器中删除该元素。因此,使用此函数须先排序。
lt.unique();
-
splice:剪切,将元素从一个列表转移到另一个列表。(会动原链表)
一个链表节点转移给另一个链表
void test_splice()
{
// 一个链表节点转移给另一个链表
std::list<int> mylist1, mylist2;
std::list<int>::iterator it;
// set some initial values:
for (int i = 1; i <= 4; ++i)
mylist1.push_back(i); // mylist1: 1 2 3 4
for (int i = 1; i <= 3; ++i)
mylist2.push_back(i * 10); // mylist2: 10 20 30
it = mylist1.begin();
++it;; // 指向2位置
mylist1.splice(it, mylist2);
// mylist1: 1 10 20 30 2 3 4
// mylist2 (empty)
// "it" 仍指向 2 (第5个元素)
}
调整当前链表节点的顺序
void test_splice2()
{
// 调整当前链表节点的顺序
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
std::list<int>::iterator it;
int x = 0;
cin >> x; // 若输入4
it = find(lt.begin(), lt.end(), x);
if (it != lt.end())
{
lt.splice(lt.begin(), lt, it);// 单个转移 it
// 412356
lt.splice(lt.begin(), lt, it, lt.end());// 区间转移 it~end
// 456123
}
}
四 list的模拟实现
4.1 基础结构
template<class T>
struct list_node //节点
{
T_ data;
list_node<T>* _next;
list_node<T>* _prev;
list_node(const T& data = T()) // 默认给T的匿名对象
:_data(data)
,_next(nullptr)
,_prev(nullptr)
{}
};
template<class T>
class list //链表
{
typedef list_node<T> Node;
public:
list()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
private:
Node* _head;
size_t _size;
};
4.2 迭代器的实现
4.2.1 迭代器
首先,写个迭代器类~
template<class T> // 迭代器类
struct list_iterator // struct默认共有
{
typedef list_node<T> Node;
typedef list_iterator<T> Self;
Node* _node;
list_iterator(Node* node) // 构造
:_node(node)
{}
T& operator*() // 重载* 获取data
{
return _node->_data;
}
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;
}
};
然后~加入 list 类~
typedef list_iterator<T> iterator;
iterator begin()
{
return iterator(_head->_next);
// return _head->_next; 也可以~ 隐式类型转换~
}
iterator end()
{
return _head; // 隐式类型转换~
}
4.2.2 const迭代器
那么~怎么实现const迭代器呢~
第一种方法就是新建一个类:
template<class T>
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;
}
//...
};
不过,这个类和普通迭代器过于相似,于是就有了第二种方法:并入list_iterator类。
template<class T, class Ref, class Ptr> // 迭代器类
struct list_iterator // struct默认共有
{
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
Node* _node;
list_iterator(Node* node) // 构造
:_node(node)
{}
Ref operator*() // 重载* 获取data
{
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;
}
};
list类中加入:
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
4.3 接口
4.3.1 push_back & insert
void push_back(const T& x)
{
Node* newnode = new Node(x);
Node* tail = _head->_prev; // 原头节点的_prev是尾节点
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
++_size;
}
void insert(iterator pos, const T& x)
{
Node* cur = pos._node; // 找到pos节点
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;
}
写完 insert 就可以改进 push_back 辣~
void push_back(const T& x)
{
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
insert 后迭代器不失效
4.3.2 erase & pop_back
iterator erase(iterator pos)
{
assert(pos != end()); // pos不能是哨兵位的头节点
Node* next = pos._node->_next; // 拿pos后一个~
Node* prev = pos._node->_prev; // 拿pos前一个~
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
return next; // 返回下一个迭代器
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
erase 后迭代器失效
4.3.3 析构 & clear
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);//erase返回下一个迭代器~不然会失效~
}
}
4.3.4 拷贝构造 & empty_init
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
// lt2(lt1);
list(const list<T>& lt)
{
void empty_init(); // 创建新的头节点,不然无法pushback~
for (auto& e : lt)
{
push_back(e);
}
}
4.3.5 赋值 & swap
// lt1 = lt3
list<T>& operator=(list<T>& lt)
{
swap(lt);
return *this;
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
4.3.6 initializer_list构造
另一种构造方式~
list(initializer_list<T> il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}
调用时:
void test_list()
{
// 直接构造
list<int> lt1({ 1,2,3,4,5,6 });
// 隐式类型转换
list<int> lt2 = { 1,2,3,4,5,6,7,8 };
const list<int>& lt3 = { 1,2,3,4,5,6,7,8 };
print_container(lt1);
}
4.4 打印类
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())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : con)
{
cout << e << " ";
}
cout << endl;
}
五 list与vector的对比
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
~完~