当前位置: 首页 > news >正文

【C++】STL 容器—list 底层剖析

Ciallo~ (∠・ω< )⌒★

list

  • 1. 节点
  • 2. 迭代器结构及行为
  • 3. list成员变量
  • 4. empty_init
  • 5. insert
  • 6. push_back
  • 7. 构造函数
    • 7.1 默认构造 list()
    • 7.2 列表初始化 list(initializer_list<T> il)
  • 8. 拷贝构造函数
  • 9. erase(迭代器失效)
  • 10. begin / end
  • 11. clear
  • 12. 析构函数
  • 13. swap
  • 14. 赋值重载
  • 15. size
  • 16. pop_back
  • 17. push_front 和 pop_front

1. 节点

std::list 是C++中的双向链表容器,是用来管理节点的,所以节点的结构和list类要分开,这是一个节点的结构

template<class T>
struct list_node
{list_node* _next;list_node* _prev;T _data;list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_data(val){}
};

2. 迭代器结构及行为

template<class T, class Ref, class Ptr>
struct list_iterator
{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Self& operator++() {_node = _node->_next;return *this;}Ptr operator->(){return &(_node->_data);}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const Self& it) const{return _node == it._node;}bool operator!=(const Self& it) const{return _node != it._node;}
};

迭代器可以分为这几种:

  • 单向迭代器,只支持++
  • 双向迭代器,支持 ++ 和 –
  • 随机迭代器,支持++,–,+,-

string 和 vector 的迭代器都是随机迭代器,list 的迭代器是双向迭代器

为什么会有 Ref 和 Ptr 这两个模板参数:
对于const迭代器,const_iterator就是要自身指向的内容不能被修改,也就是解引用后不能被修改,*只读,那么只需要修改operator*的返回值

const T& operator*()
{return _node->_data;
}

因为在一个结构体里面,这样的返回值不同但参数相同的两个函数不能构成重载,这样一来就需要我们再单独写一个template<class T> struct list_const_iterator,这样做耗费资源且意义不大,因此我们加入模板参数 RefPtr,在使用时list_iterator<T, T&, T*> 用于声明普通迭代器,用list_iterator<T, const T&, const T*> 来声明const迭代器

因为 list 底层空间是不连续的,所以不支持 operator[] 和迭代器的大小比较,但支持operator* 解引用。

这里面还需要注意的是 operator->,考虑如下情境,list中存放结构体成员:

void test()
{struct AA{int _a;int _b;AA(int a = 0, int b = 0):_a(a),_b(b){}};wzf::list<AA> lt1;lt1.push_back({ 1,1 }); //隐式类型转换lt1.push_back({ 2,2 });lt1.push_back({ 3,3 });lt1.push_back({ 4,4 });auto it = lt1.begin();while (it != lt1.end()){//cout << *it << " ";cout << (*it)._a << ":" << (*it)._b << endl;cout << it->_a << it->_b << endl;}
}

cout << *it << " "; 报错是因为对AA*解引用,拿到的_data是AA结构体的一个对象,而AA这个结构体没有重载<<,因此无法用<<输出

cout << (*it)._a << ":" << (*it)._b << endl; 这样可以访问,但是太麻烦了,所以我们想要写一个迭代器的->重载

但从我们的实现来看,operator-> 只是返回了节点中 _data 的地址,对应于这个情景中,调用 opeartor-> 只是返回了每个节点中结构体成员的地址,要访问到其中的数据,还需要一次 -> 才对。这其实是编译器的优化,只需要重载 operator->,调用一次就可以访问到结构体成员中的数据。

3. list成员变量

template<class T>
class list
{typedef list_node<T> Node; //节点typedef list_iterator<T, T&, T*> iterator; //普通迭代器typedef list_iterator<T, const T&, const T*> const_iterator; //const迭代器
public://...
private:Node* _head;size_t _size;
};

要声明节点,普通迭代器,const迭代器,便于使用
_head是双向链表的头,_size是链表的长度

4. empty_init

将一个链表初始化为空

void empty_init()
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}

5. insert

在指定位置插入元素

void insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;
}

insert不涉及扩容,不会导致迭代器失效

6. push_back

尾插指定元素

void push_back(const T& x)
{insert(end(), x);
}

这样写是为了达到代码的复用,当然也可以自己实现:

void push_back(const T& x)
{Node* tail = _head->_prev;Node* newnode = new Node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}

7. 构造函数

7.1 默认构造 list()

因为 empty 可以起到初始化的作用,这里直接调用 empty

list()
{empty_init();
}

7.2 列表初始化 list(initializer_list il)

list(initializer_list<T> il)
{empty_init();for (auto& e : il){push_back(e);}
}

8. 拷贝构造函数

list(const list<T>& lt)
{empty_init();for (auto& e : lt){push_back(e);}
}

一个一个尾插就好

9. erase(迭代器失效)

iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return next;
}

std::list 是C++中的双向链表容器,其 erase() 函数的行为与连续内存容器(如 std::vector)有显著不同。

std::list::erase 的迭代器失效规则:
当调用 list::erase() 时:

  1. 被删除元素的迭代器立即失效
  2. 指向其他元素的迭代器保持有效
  3. 被删除元素的引用和指针失效

为什么 list 有这种特性?
std::list 的底层实现是双向链表,每个元素都是独立节点:

  1. 删除操作仅修改相邻节点的指针
  2. 其他节点在内存中的位置不变
  3. 只有被删除节点本身被销毁

这与 std::vector 显著不同,后者删除元素会导致后续元素移动。

10. begin / end

iterator begin()
{return iterator(_head->_next);
}iterator end()
{return iterator(_head);
}const_iterator begin() const
{return const_iterator(_head->_next);
}const_iterator end() const
{return const_iterator(_head);
}

11. clear

清空一个链表
复用erase

void clear()
{auto it = begin();while (it != end()){it = erase(it);}
}

12. 析构函数

复用clear

~list()
{clear();delete _head;_head = nullptr;
}

13. swap

交换两个链表

void swap(list<T>& lt)
{std::swap(_head, lt._head);std::swap(_size, lt._size);
}

14. 赋值重载

list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}

这是现代写法,由swap完成

15. size

获取链表的长度

size_t size() const
{return _size;
}

16. pop_back

尾删
复用erase

void pop_back()
{erase(--end());
}

手动实现

void pop_back()
{Node* tail = _head->_prev;_head->prev = tail->_prev;tail->_prev->_next = _head;delete tail;
}

17. push_front 和 pop_front

头插和头删
复用 insert 和 erase

void push_front(const T& x)
{insert(begin(), x);
}void pop_front()
{erase(begin());
}
http://www.dtcms.com/a/334749.html

相关文章:

  • Java应届生求职八股(5)---并发编程篇
  • JCTools 无锁并发队列基础:ConcurrentCircularArrayQueue
  • 【论文阅读笔记】--Eurosys--HCache
  • 安全审计-firewall防火墙
  • 探索粒子世界:从基础理论到前沿应用与未来展望
  • 基于动捕实现Epuck2的轨迹跟踪
  • 每日算法刷题Day62:8.16:leetcode 堆8道题,用时2h30min
  • 【Java基础面试题】数据类型
  • 【电路笔记 通信】AXI4-Lite协议 论文阅读 简化的高级可扩展接口(AdvancedeXtensibleInterface4Lite)
  • 小白挑战一周上架元服务——元服务开发06
  • 元宇宙教育:打破时空限制的学习革命
  • MQ迁移方案
  • 顶刊分享--MYC ecDNA增强胰腺癌的瘤内异质性及可塑性
  • 测试18种RAG技术,找出最优方案(四)
  • 云蝠智能VoiceAgent:AI赋能售后服务场景的创新实践
  • docker镜像解决的一些问题
  • 搭建ktg-mes
  • 每日面试题22:静态代理和动态代理的区别
  • C语言指针运算题
  • [Python]PTA:实验2-3-2-for 求N分之一序列前N项和
  • HTML 常用属性介绍
  • 教育的终极指向:一场精心准备的“得体退出”
  • InfluxDB 数据迁移工具:跨数据库同步方案(一)
  • 一个.NET开源、轻量级的运行耗时统计库
  • 解决 Windows 下运行 MCP 脚本弹出 WSH 错误窗口的问题 | Windows Script Host
  • vscode配置cpp运行和调试环境(保姆级)
  • 一文入门Gin框架
  • 【运维心得】三步10分钟拆装笔记本键盘
  • 【自用】JavaSE--特殊文件Properties与XML、日志技术
  • 《零基础掌握飞算Java AI:核心概念与案例解析》