C++ | 容器list

🦌云深麋鹿
专栏C++ | 用C语言学数据结构 | Java

回顾:上一篇我们结束了容器vector,接下来这篇文章让我们进入到新的容器list,体会新的设计思路吧~

放个目录

  • [一 使用](#一 使用)
    • [1.1 sort](#1.1 sort)
    • [1.2 splice](#1.2 splice)
      • [1.2.1 两个链表之间使用](#1.2.1 两个链表之间使用)
      • [1.2.2 一个链表中结点调整](#1.2.2 一个链表中结点调整)
  • [二 模拟](#二 模拟)

一 使用

1.1 sort

数据量大的话,用list这个容器来sort效率很低。

cpp 复制代码
srand(time(0));
const int N = 10000000;
list<int> lt1;
list<int> lt2;
for (size_t i = 0;i < N;++i) {
	auto e = rand() + i;
	lt1.push_back(e);
	lt2.push_back(e);
}
// sort lt1
int begin1 = clock();
lt1.sort();
int end1 = clock();

// sort lt2
int begin2 = clock();
vector<int> vt(lt2.begin(), lt2.end());
sort(vt.begin(), vt.end());
// copy to lt2
lt2.assign(vt.begin(), vt.end());
int end2 = clock();

// output
cout << "sort lt1: " << end1 - begin1 << endl;
cout << "sort lt2: " << end2 - begin2 << endl;

运行:

结果显示:把 lt2 的数据放到 vector 里去排,再拷贝回原 lt2,都比单纯用 list 的 sort 排 lt1 来的效率高。

1.2 splice

1.2.1 两个链表之间使用

cpp 复制代码
list<int> lt1 = { 1,2,3,4,5 };
list<int> lt2 = { 6,7,8,9,10 };
lt1.splice(lt1.end(),lt2);

输出:

cpp 复制代码
cout << "lt1: ";
for (auto e : lt1) {
	cout << e << " ";
}
cout << endl << "lt2: ";
for (auto e : lt2) {
	cout << e << " ";
}
cout << endl;

运行:

1.2.2 一个链表中结点调整

cpp 复制代码
list<int> lt1 = { 1,2,3,4,5 };
auto it = --lt1.end();
lt1.splice(lt1.begin(), lt1, it);

输出:

cpp 复制代码
for (auto e : lt1) {
	cout << e << " ";
}
cout << endl;

运行:

二 模拟

这是一个双向带头链表。

2.1 定义list_node

2.1.1 成员变量

cpp 复制代码
T _data;
node* _pre = nullptr;
node* _next = nullptr;
  1. _data存储数据。
  2. _pre存储上一个结点的地址。
  3. _next存储下一个结点的地址。

2.1.2 构造函数

支持无参构造:

cpp 复制代码
list_node(const T& data = T())
    :_data(data)
{}

2.2 构造函数

2.2.1 无参构造

cpp 复制代码
list(){
    list_init();   
}

封装一个函数:生成一个 _pre 和 _next 都指向 自己 的_head。

cpp 复制代码
void list_init() {
	_head = new node();
	_head->_next = _head;
	_head->_pre = _head;
}

测试:

cpp 复制代码
list<int> l;

调试:

2.2.2 拷贝构造

我们先写一个不完善版本:

cpp 复制代码
list(list<T>& l1) {
    list_init();
    for (auto& e : l1) {
        push_back(e);
    }
}

复用push_back。

测试:

cpp 复制代码
list<int> l1 = {1,2,3,4,5,6};
list<int> l2(l1);

调试:

2.2.3 initializer_list

cpp 复制代码
list(initializer_list<T> il) {
    list_init();
    for (auto& e : il) {
        push_back(e);
    }
}

依旧复用。

测试:

cpp 复制代码
list<int> l = {1,2,3,4,5,6};

调试:

2.3 改变list

2.3.1 insert

cpp 复制代码
iterator insert(iterator pos, T& data) {
    if (pos == nullptr) {
        return nullptr;
    }
    node* posNode = pos._ptr;
    node* pre = posNode->_pre;
    node* newNode = new node(data);
    
    newNode->_pre = pre;
    newNode->_next = posNode;
    pre->_next = newNode;
    posNode->_pre = newNode;
    ++_size;
    return newNode;
}
  1. 检查参数合法性。
  2. 根据参数data造一个新结点。
  3. 处理相邻结点的链接关系。

2.3.2 erase

cpp 复制代码
iterator erase(iterator pos) {
    if (pos == nullptr) {
        return nullptr;
    }
    node* posNode = pos._ptr;
    node* pre = posNode->_pre;
    node* next = posNode->_next;
    
    pre->_next = next;
    next->_pre = pre;
    delete posNode;
    --_size;
    return iterator(next);
}
  1. 依旧先检查参数合法性。
  2. 记录pre,next结点。
  3. 处理链接关系,可以据 insert 的图倒推 erase 的过程。
  4. 释放 pos 结点空间。
  5. 返回值为更新后的iterator,以防止迭代器失效。

测试:

cpp 复制代码
list<int> l = {1,2,3,4,5,6};
// ...
list_iterator it = l.begin();
while(it != l.end()){
    if ((*it) % 2 == 0) {
        it = l.erase(it);
    }
    else {
        ++it;
    }
}
//...

输出部分:

cpp 复制代码
for (auto e : l) {
	cout << e << " ";
}
cout << endl;
// ...
cout << "--- after erase ---" << endl;
for (auto e:l) {
	cout << e << " ";
}
cout << endl;

运行:

2.3.3 push_back

cpp 复制代码
iterator push_back(const T& data) {
    return insert(end(), data);
}

直接复用。

测试:

cpp 复制代码
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);

调试:

2.3.4 pop_back

cpp 复制代码
iterator pop_back() {
    return erase(--end());
}

直接复用。

测试:

cpp 复制代码
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.pop_back();

调试:

2.3.5 push_front

cpp 复制代码
iterator push_front(const T& data) {
    return insert(begin(), data);
}

直接复用。

测试:

cpp 复制代码
list<int> l;
l.push_front(1);
l.push_front(2);
l.push_front(3);
l.push_front(4);

输出:

cpp 复制代码
for (auto e:l) {
	cout << e << " ";
}
cout << endl;

运行:

2.3.6 pop_front

cpp 复制代码
iterator pop_front() {
    return erase(begin());
}

直接复用。

测试:

cpp 复制代码
l.push_front(1);
l.push_front(2);
l.push_front(3);
l.push_front(4);
l.pop_front();

运行输出:

2.3.7 swap

cpp 复制代码
void swap(list<T>& l2) {
    if (this == &l2) {
        return;
    }
    std::swap(_head, l2._head);
    std::swap(_size, l2._size);
}

测试:

cpp 复制代码
list<int> l1 = { 1,2,3,4,5,6 };
list<int> l2 = { 6,5,4,3,2,1 };
l1.swap(l2);

输出:

cpp 复制代码
cout << "l1:";
for (auto e : l1) {
	cout << e << " ";
}
cout << endl;
cout << "l2:";
for (auto e : l2) {
	cout << e << " ";
}
cout << endl;

l1.swap(l2);
cout << "--- after swap ---" << endl;

cout << "l1:";
for (auto e : l1) {
	cout << e << " ";
}
cout << endl;
cout << "l2:";
for (auto e : l2) {
	cout << e << " ";
}
cout << endl;

运行:

2.4 封装一个list_iterator

不写析构和拷贝构造函数(默认浅拷贝就够用了)。

2.4.1 成员变量

cpp 复制代码
node* _ptr;

2.4.2 构造函数

cpp 复制代码
list_iterator(node* ptr)
    :_ptr(ptr)
{}

2.4.3 重载操作符

(1)operator*
cpp 复制代码
T& operator*() {
    return _ptr->_data;
}
(2)operator++
①前置
cpp 复制代码
Self& operator++() {
    _ptr = _ptr->_next;
    return *this;
}
  1. 往后走,遍历到下一个结点。
  2. 返回下一个结点的指针。

这里的 Self 是我们typedef出来的:

cpp 复制代码
typedef list_iterator<T> Self;
②后置
cpp 复制代码
Self operator++(int) {
    Self temp(*this);
    _ptr = _ptr->_next;
    return temp;
}
  1. 遍历到下一个结点。
  2. 返回原指针(事先存储)。
(3)operator--
①前置
cpp 复制代码
Self& operator--() {
	_ptr = _ptr->_pre;
    return *this;
}
②后置
cpp 复制代码
Self operator--(int) {
	Self temp(*this);
	_ptr = _ptr->_pre;
    return temp;
}
(4)operator!=
cpp 复制代码
bool operator!=(const Self& it) const{
    return _ptr != it._ptr;
}
(5)operator==
cpp 复制代码
bool operator==(const Self& it) const {
    return _ptr == it._ptr;
}
(6)operator->
cpp 复制代码
T* operator->() {
    return &_ptr->_data;
}
①使用场景

我们有一个自定义类型Aa:

cpp 复制代码
struct Aa {
    int _A;
    int _a;
    
    Aa(int A = 0,int a = 0) 
        :_A(A),_a(a)
    {}
};

造一个list< Aa >:

cpp 复制代码
list<Aa> l;
l.push_back({ 1,2 });
l.push_back({ 3,4 });
l.push_back({ 5,6 });

输出需要这么写:

cpp 复制代码
list_iterator it = l.begin();
while (it != l.end()) {
	cout << (*it)._A << ":" << (*it)._a << " ";
    ++it;
}
cout << endl;

(*it)._A 这种写法显得很复杂,我们可以重载 操作符-> ,使得写法更加简便。

②重载后用法
cpp 复制代码
while (it != l.end()) {
    cout << it.operator->()->_A << ":" << it.operator->()->_a << " ";
    ++it;
}

可以简便写成:

cpp 复制代码
cout << it->_A << ":" << it->_a << " ";

2.4.4 const_iterator

(1)第一版

我们发现 iterator 跟 const_iterator 两个 struct 有一个函数不一样:

cpp 复制代码
const T& operator*() {
    return _ptr->_data;
}

返回值需要加上修饰符 const。

(2)第二版

所以我们直接加个模板参数,让编译器自己生成 const_iterator。

cpp 复制代码
template<typename T,typename Ref>
struct list_iterator {
    //...
    Ref operator*() {
        return _ptr->_data;
    }
    //...
}

这个函数的返回值改为我们的模板参数。

list类里就可以这样玩:

cpp 复制代码
typedef list_iterator<T, T&> iterator;
typedef list_iterator<T, const T&> const_iterator;

我们上面写的拷贝构造的参数就可以加上 const 修饰了:

cpp 复制代码
list(const list<T>& l1) {
    list_init();
    for (auto& e : l1) {
        push_back(e);
    }
}
(3)第三版

还有一个函数 iterator 和 const_iterator 两个版本不一样:

cpp 复制代码
const T* operator->() {
    return &_ptr->_data;
}

我们再加一个模板参数Ptr:

cpp 复制代码
template<typename T,typename Ref,typename Ptr>

list类里这么搞:

cpp 复制代码
typedef list_iterator<T, T&,T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;

2.5 迭代器遍历

2.5.1 begin

(1)非const版本
cpp 复制代码
iterator begin() {
    return iterator(_head->_next);
}
(2)const版本
cpp 复制代码
const_iterator begin() const {
    return const_iterator(_head->_next);
}

2.5.2 end

(1)非const版本
cpp 复制代码
iterator end() {
    return iterator(_head);
}
(2)const版本
cpp 复制代码
const_iterator end() const {
    return const_iterator(_head);
}

2.5.3 print

这里需要用const_iterator。

cpp 复制代码
void print() const {
    const_iterator it = begin();
    while (it != end()) {
        cout << (*it) << " ";
        ++it;
    }
    cout << endl;
}

测试:

cpp 复制代码
list<int> l1 = { 1,2,3,4,5,6 };
l1.print();

运行:

2.6 容量相关

2.6.1 size

cpp 复制代码
size_t size() const {
    return _size;
}

直接返回成员变量。

2.6.2 clear

cpp 复制代码
void clear() {
	iterator it = begin();
    while (it != end()) {
        it = erase(it);
    }
}

复用+1。

测试:

cpp 复制代码
list<int> l = { 1,2,3,4,5,6 };
l.clear();

调试:

2.7 重载操作符

2.7.1 =

cpp 复制代码
list<T>& operator=(list<T> l2) {
    swap(l2);
    return *this;
}

复用+1。

测试:

cpp 复制代码
list<int> l1 = { 1,2,3,4,5,6 };
list<int> l2;
//...
l2 = l1;
//...

输出:

cpp 复制代码
//...
cout << "l1:";
for (auto e : l1) {
	cout << e << " ";
}
cout << endl;
cout << "l2:";
for (auto e : l2) {
	cout << e << " ";
}
cout << endl;

//...
cout << "--- after ---" << endl;

cout << "l1:";
for (auto e : l1) {
	cout << e << " ";
}
cout << endl;
cout << "l2:";
for (auto e : l2) {
	cout << e << " ";
}
cout << endl;

运行:

容器vector的学习就到这里啦,下一篇 容器stack&queue 也是今天会更出来~


相关推荐
deviant-ART2 小时前
java stream 的 findFirst 和 findAny 踩坑点
java·开发语言·后端
handler012 小时前
Linux 基本指令知识点(1)
linux·c++·笔记
hansel_sky2 小时前
题解-数字删除
c++·程序人生
Hical_W2 小时前
C++ 也能优雅写 Web?5 分钟用 Hical 搭建 REST API
开发语言·c++·github
历程里程碑2 小时前
55 Linux epoll高效IO实战指南
java·linux·服务器·开发语言·前端·javascript·c++
何包蛋H2 小时前
Java并发编程核心:JUC、AQS、CAS 完全指南
java·开发语言
️是782 小时前
信息奥赛一本通—编程启蒙(3373:练64.2 图像旋转翻转变换)
数据结构·c++·算法
Drache_long2 小时前
Docker(二)
运维·docker·容器
云深麋鹿2 小时前
C++ | 容器stack&queue
开发语言·c++