本笔记是在已熟悉 string 以及 vector 的使用的前提下进行的记录
list 底层是一个 带头双向循环链表
成员函数
有关构造、重载、迭代器(遍历)、容量操作、修改 这些操作在前面已经说过,在此不多赘述
需要注意:
- 从这里开始不再是空间上的顺序访问, [] 也就不会使用了,直接获取数据只能用 front、back 函数
- list 没有 reserve,只用 resize(也挺好理解的,链表没有随机访问,预留的空间使用起来可一点都不方便)
其他操作:
splice(知道即可):将链表节点从其链表中拿走,链接(插入)到你给出的迭代器位置之后
remove(知道即可):在链表中删除你所给的数值
unique(知道即可):删除重复项
sort(性能不行,用得少(甚至不如把 list 的数据拷贝到 vector 里排序再拷贝回来快)):排序
算法(algorithm)里的 sort 不能直接使用:
其一:减法不适用
其二:算法用的是快速排序
因为这些原因,算法的 sort (名字暗示)需要传入的是 随机迭代器:
● 补充:迭代器类型:单向(++)、双向(++,- -)、随机(++,- -,+,-)
模拟实现
Iterator.h (反向迭代器,可以考虑在学完栈和队列后再看)
#pragma once
//★反向迭代器 —— 借助正向迭代器实现(迭代器适配器)
template<class Iterator, class Ref, class Ptr>
//(如果没有后两个模板参数)重载 * 会很麻烦(库里使用的方法现在学不来)
struct reserve_iterator {
typedef reserve_iterator<Iterator, Ref, Ptr> self;
Iterator _it;
reserve_iterator(Iterator it)
:_it(it)
{}
self& operator++() {
--_it;
return *this;
}
self operator--() {
++_it;
return *this;
}
bool operator!=(const self& s) {
return _it != s._it;
}
Ref operator*() {
Iterator tmp = _it;
--tmp;
return *tmp;
}
};
//单论list,写这样一个麻烦的反向迭代器的确不如把正向迭代器拷贝一份再稍作修改来的直接
//但这么写的优势在于它是一个模板,能为所有容器所用 —— vector 可没法对正向迭代器拷贝改改就能了事(因为我们模拟实现的vector迭代器不是自定义类型)
list.h
#pragma once
#include<iostream>
#include<assert.h>
#include"Iterator.h"
using namespace std;
namespace hazb {
//节点
template<class T>
struct list_node {//● list_node:类名 list_node<T>:类型
list_node<T>* _prev;
list_node<T>* _next;
T _x;
//构造(主要用于 push_back )
list_node(const T& x = T())//要给缺省值,要不 list 的构造函数过不了 //匿名对象做缺省值(调用相应的默认构造),而不是用 0
:_prev(nullptr) //***如果这个对象默认构造没有主动实现过不了吗?等着试试
, _next(nullptr)
, _x(x)
{}
};
//●迭代器实现思路:不同于(我们模拟实现的)vector成员变量是三个迭代器(也就是存储数据的指针),list的成员变量是链表头节点(自定义类型,其内部存储数据)的指针
//●因为这种特性,直接将节点指针设为迭代器就不好使了(++之类操作完全用不了,直接重载也不行——因为重载是相对这个类的啊,你这成员用不了),于是我们借鉴源码的写法——
template<class T,class Ref,class Ptr>//后两个参数是用于完成 const 迭代器的模板的,如果没有这两个模板参数,const 迭代器就只能为了几处的小变动把以下的代码复制一遍
//Ref:引用 Ptr:指针
struct list_iterator {
typedef list_node<T> node;
typedef list_iterator<T, Ref, Ptr> self;
node* _pnode;
list_iterator(node* pnode)
:_pnode(pnode)
{}
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;
}
Ref operator*() {
return _pnode->_x;
}
Ptr operator->() {//后面有测试代码
return &_pnode->_x;
}
bool operator!=(const self& s) {
return _pnode != s._pnode;
}
};
//这里的迭代器不再是一个原生指针,而是自定义类型对指针的封装,模拟指针的功能
//★反向迭代器见 Iterator.h 文件
template<class T>
class list {
public:
//为图方便
typedef list_node<T> node;
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
typedef reserve_iterator<iterator, T&, T*> reserve_iterator;
//空初始化 —— 函数复用,方便实现迭代器区间构造
void empty_init() {
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
//构造函数
list() {
empty_init();
}
//迭代器区间构造
template<class Iterator>
list(Iterator first, Iterator last) {
empty_init();
while (first != last) {
push_back(*first);
++first;
}
}
//拷贝构造
//list(const list<T>& lt) {
// empty_init();
// for (auto e : lt) {
// push_back(e);
// }
//}
void swap(list<T>& tmp)
{
std::swap(_head, tmp._head);//直接交换头节点指针,简单又迅速(
}
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
//清理
void clear() {
iterator it = begin();
while (it != end()) {
it = erase(it);
}
}
~list() {
clear();
delete _head;
_head = nullptr;
}
//赋值
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
//增
void push_back(const T& x) {
//node* tail = _head->_prev;
//node* newnode = new node(x);//因为在这一步直接使用 list_node 的构造,所以需要先去上面去实现
//
//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() {
erase(--end());
}
void pop_front() {
erase(begin());
}
//插入删除(注意参数为迭代器)
void insert(iterator pos, const T& x)
{
node* cur = pos._pnode;
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._pnode->_prev;
node* next = pos._pnode->_next;
prev->_next = next;
next->_prev = prev;
delete pos._pnode;//迭代器失效(直接把迭代器所在位置给释放了,肯定失效)
return iterator(next);
}
//查(迭代器)
iterator begin() {
构造迭代器
//iterator it(_head->_next);
返回迭代器
//return it;
//返回 匿名对象 ,便于优化
return iterator(_head->_next);
}
const_iterator begin() const{
return const_iterator(_head->_next);
}
reserve_iterator rbegin() {
return reserve_iterator(end());//反向迭代器由正向迭代器构造而来
}
iterator end() {
return iterator(_head);
}
const_iterator end() const{
return const_iterator(_head);
}
reserve_iterator rend() {
return reserve_iterator(begin());
}
private:
node* _head;
};
void test1() {
list<int> ls1;
ls1.push_back(1);
ls1.push_back(2);
ls1.push_back(3);
ls1.push_back(4);
list<int>::iterator it1 = ls1.begin();//我们没有主动实现迭代器的拷贝构造,这里显然是一个浅拷贝,但不会出错
//——我们也没用主动实现析构函数, 这里迭代器不过是暂时借用节点的地址,析构是链表的事
while (it1 != ls1.end()) {
cout << *it1 << " ";
++it1;
}
cout << endl;
list<int> ls2 = ls1;
list<int>::iterator it2 = ls2.begin();
while (it2 != ls2.end()) {
cout << *it2 << " ";
++it2;
}
cout << endl;
}
//测试迭代器的 -> 访问
struct AA {
int _a1;
int _a2;
AA(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{}
};
void test2() {
list<AA> ls;
ls.push_back(AA(1, 1));
ls.push_back(AA(2, 2));
ls.push_back(AA(3, 3));
typename list<AA>::iterator it = ls.begin();//(如果把这个写在list的类内)会有暂且无法理解的问题(在最前面加typename似乎能解决),但最好的解决方法是把这东西写在 list 类的外面
//2024.1.23补充:取一个类模板的内嵌类型,前面要加typename,因为编译器无法区分你取的是类型还是静态变量,typename说明这是类型
while (it != ls.end()) {
cout << it->_a1 << " ";//本应写为 it->->_a1 (示例:it-> 获取 AA(1,1) 的地址,&AA(1,1)->_al获取我想要的数据)
//但为了可读性,编译器将其优化成了这样的写法
++it;
}
cout << endl;
}
//用于测试反向迭代器
void test3() {
list<int> ls;
ls.push_back(1);
ls.push_back(2);
ls.push_back(3);
ls.push_back(4);
list<int>::reserve_iterator it = ls.rbegin();
while (it != ls.rend()) {
cout << *it << " ";
++it;
}
cout << endl;
}
}