【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
,这样做耗费资源且意义不大,因此我们加入模板参数 Ref
和 Ptr
,在使用时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() 时:
- 被删除元素的迭代器立即失效
- 指向其他元素的迭代器保持有效
- 被删除元素的引用和指针失效
为什么 list 有这种特性?
std::list 的底层实现是双向链表,每个元素都是独立节点:
- 删除操作仅修改相邻节点的指针
- 其他节点在内存中的位置不变
- 只有被删除节点本身被销毁
这与 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());
}