👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:C++修炼之路
文章目录
- 一、读源码
- 二、成员
- 三、默认成员函数
- 1、构造
- 2、析构
- 3、拷贝构造
- 4、赋值重载
- 四、迭代器
- 五、其他接口
如果无聊的话,就来逛逛 我的博客栈 吧! 🌹
一、读源码
list 是双向带头循环链表,不了解这个结构可以去看我的双向链表。
list 不支持 [ ]
,因为地址不连续,list 的访问通过迭代器。
迭代器:
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef __list_iterator<T, Ref, Ptr> self;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
link_type node;
// ...
};
迭代器是自定义类型。
成员变量:
template <class T, class Alloc = alloc>
class list {
// ...
protected:
link_type node;
}
查看 link_type 本质:
typedef list_node* link_type;
typedef __list_node<T> list_node;
// 链表节点
template <class T>
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
实质上就是节点类型的指针。
创建节点:
link_type create_node(const T& x) {
link_type p = get_node();
__STL_TRY {
construct(&p->data, x);
}
__STL_UNWIND(put_node(p));
return p;
}
get_node:
link_type get_node() { return list_node_allocator::allocate(); } // 空间配置器开辟空间
construct 实际上就是定位 new 。
list 构造:
list() { empty_initialize(); }
void empty_initialize() {
node = get_node();
node->next = node;
node->prev = node;
}
list 构造没有调用定位 new ,因为哨兵位上面并不需要存放有效数据,所以只需要开辟空间,确定链接关系;普通节点上,存放自定义类型数据,若赋值空间上存在随机值,对空间进行释放可能会导致崩溃的现象,所以对于普通节点需要调用定位 new 进行构造。
二、成员
list_node 为节点,需要经常访问,开 struct :
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _val;
list_node(const T& val = T())
:_next(nullptr)
,_prev(nullptr)
,_val(val)
{}
};
list:
template<class T>
class list
{
typedef list_node<T> Node; // 给类内部用的
// ...
private:
Node* _head; // 哨兵位
size_t _size;
};
三、默认成员函数
1、构造
给哨兵位完成初始化:
void empty_init()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
list()
{
empty_init();
}
2、析构
~list()
{
clear();
delete _head;
_head = nullptr;
}
// 清除除哨兵位的所有节点
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
_size = 0;
}
3、拷贝构造
// list(const list& lt) // 类中类名可以充当类型
list(const list<T>& lt)
{
// new(this)list; // 定位 new
empty_init();
// & 提高效率
for (auto& e : lt)
{
push_back(e);
}
}
可以使用定位 new 直接调用构造函数,也可以复用 empty_init
。赋值通过 push_back
,push_back 中会完成对自定义类型成员的深拷贝。
4、赋值重载
// void swap(list& lt)
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
// list& operator=(const list<T>& lt)
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
调用拷贝构造,进行交换。
四、迭代器
list 的迭代器是自定义类型,不是原生指针Node*
.
若:
typedef Node* iterator;
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
(*it) += 1;
cout << *it << " ";
++it;
}
那么 *it
获得的是节点类,并不是节点中存储的值。
所以,迭代器为自定义类型,其中 *, ++
等都是通过运算符重载来完成的。
通过封装自定义类型为迭代器,利用节点指针构造迭代器,用类封装类型,统一迭代器的使用方法,来获得统一的结果。
思考,普通迭代器的模板类型构成:
我们需要如下重载符号:*, 前置++,后置++,前置--,后置--,->, !=, ==
,返回类型分别为:T&, 迭代器引用,迭代器拷贝,迭代器引用,迭代器拷贝,T*, 布尔值,布尔值
->
模拟的行为:
struct A
{
A(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{}
int _a1;
int _a2;
};
void test_list2()
{
list<A> lt;
lt.push_back(A(1, 1));
lt.push_back(A(2, 2));
lt.push_back(A(3, 3));
lt.push_back(A(4, 4));
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
//cout << (*it)._a1 << " " << (*it)._a2 << endl; // 自定义类型成员访问成员变量
cout << it->_a1 << " " << it->_a2 << endl; // 打印自定义类型的成员变量
++it;
}
cout << endl;
}
类比 const 迭代器,const 迭代器与普通迭代器的区别无非是 *
返回的值不能修改,->
返回的 T 类型指针指向的成员变量不能修改。
所以,我们完全可以将类模板写为:
template<class T, class Ref, class Ptr> // T 为任意类型,Ref 为 T& 为数据的引用,Ptr 为 T* 为返回的 T 类型的指针
迭代器根据节点来构造,通过运算符重载来完成统一行为,写出迭代器:
template<class T, class Ref, class Ptr>
struct _list_iterator
{
typedef list_node<T> Node;
typedef _list_iterator<T, Ref, Ptr> self; // 重命名避免繁琐,selft 就是迭代器本身
Node* _node;
_list_iterator(Node* node)
:_node(node)
{}
// 返回引用的值
Ref operator*()
{
return _node->_val;
}
// 编译器做了优化
// Ptr 返回的是 T*
// 调用时 it->_member
// 那么此刻为 T*_member,原本为 T*->_member
// 所以其实调用方应该写为 it->->_member,为了符合逻辑,编译器做了优化,只要写 it->_member
Ptr operator->()
{
return &_node->_val;
}
self& operator++()
{
_node = _node->_next;
return *this;
}
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;
}
// it != it.end()
// it.end() 返回的是临时对象,为常量,所以要加 const 修饰
bool operator!=(const self& it) const
{
return _node != it._node;
}
bool operator==(const self& it) const
{
return _node == it._node;
}
};
迭代器的拷贝只要浅拷贝,期望拿到的就是对应节点的 begin;迭代器没有析构,析构工作交给 list 统一处理,迭代器只需要完成它的本质工作:对节点的访问。
普通迭代器和 const 迭代器只需要在 list
中 typedef :
typedef _list_iterator< T, T&, T*> iterator;
typedef _list_iterator< T, const T&, const T*> const_iterator;
注:类中的自定义类型有两方面 - 1. typedef 的内嵌类型 2. 内部类。
iterator begin()
{
return _head->_next; // 隐式类型转换
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
返回时,隐式类型转换,例如 begin,实际上是 iterator(_head->_next)
,单参数的构造函数支持隐式类型转换。
五、其他接口
void push_back(const T& val)
{
/*Node* newnode = new Node(val);
Node* tail = _head->_prev;
newnode->_prev = tail;
tail->_next = newnode;
newnode->_next = _head;
_head->_prev = newnode;*/
insert(end(), val);
}
void push_front(const T& val)
{
insert(begin(), val);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
// pos 位置之前插入
iterator insert(iterator pos, const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node; // 迭代器访问成员变量,获取 Node*
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
return newnode; // 插入的位置
}
// 删除 pos 位置
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; // 删除后的位置
}
size_t size()
{
return _size;
}