👑作者主页:@安 度 因
🏠学习社区: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; // 定位 newempty_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->_memberPtr 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;
}