1.list的模拟实现
要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,通过上一篇的学习,这些内容已基本掌握,现在我们来模拟实现list。
1.1 成员变量和节点
首先,这里 list 设置成两个成员变量,_head 和 _size, _head用来指向头节点,_size用来记录list有多少个元素。然后还需要自定义list节点的类型。
template<class T> struct list_node//节点类 { T _data; list_node<T>* _next; list_node<T>* _prev; list_node(const T& x = T())//构造函数 :_data(x) , _next(nullptr) , _prev(nullptr) {} }; template<class T> class list//list类 { typedef list_node<T> Node; void empty_init() { _head = new Node; _head->_next = _head; _head->_prev = _head; _size = 0; } list()//构造函数 { empty_init(); } private: Node* _head; size_t _size; };
1.2 迭代器实现
在vector中,我们可以迭代器直接利用原生指针来实现,而在list中因为所使用的空间并不是连续的,我们需要对迭代器进行封装,内部存放一个Node* 类型的指针,运算符重载一些要使用的操作。
迭代器要使用的操作
- ++ / --
- * ->
- != / ==
1.2.1 非const的迭代器
template<class T> struct __list_iterator { typedef list_node<T> Node; typedef __list_iterator<T> self; Node* _node; __list_iterator(Node* node = nullptr) :_node(node) {} T& operator*() { return _node->_data; } T* operator->()//为数据存放的是自定义类型准备的。 { return &_node->_data; } };
1.2.2 const的迭代器
const 迭代器要保证指向的内容不能被修改,而不是简单的在非const 迭代器前加上一个const,这样只是保证了迭代器不能被改变,不能进行++操作,所以必须再实现一个const类型迭代器。
template<class T> struct __list_const_iterator { typedef list_node<T> Node; typedef __list_const_iterator<T> self; Node* _node; __list_const_iterator(Node* node = nullptr) :_node(node) {} //*it = 10; const T& operator*() { return _node->_data; } //it->a1 = 10; const T* operator->()//为数据存放的是自定义类型准备的。 { return &_node->_data; } };
1.2.3 一个模板实现 const 与 非const 迭代器
可以发现上面两个迭代器只有 * -> 返回值不一样,写起来比较繁琐,我们可以用一个模板实现。
//const_iterator 和 iterator需要实现两个类,太繁琐 // 可以利用模板实现 //同一个类模板,模板参数不容类型不同 //实例化的参数不容,类型完全不同, 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 = nullptr) :_node(node) {} self& operator++() { _node = _node->_next; return *this; } self& operator--() { _node = _node->_prev; return *this; } self operator++(int) { self tmp(*this); _node = _node->_next; return tmp; } self operator--(int) { self tmp(*this); _node = _node->_prev; return tmp; } Ref operator*() { return _node->_data; } Ptr operator->()//为数据存放的是自定义类型准备的。 { return &_node->_data; } bool operator!=(const self& it) { return _node != it._node; } bool operator==(const self& it) { return _node == it._node; } };
list类中使用迭代器:
public: /*typedef __list_iterator<T> iterator; typedef __list_const_iterator<T> const_iterator;*/ typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator; iterator begin() { return _head->_next;//node指针,单参数构造函数,隐式类型转换 } iterator end() { return _head; } //const迭代器 //const iterator 是修饰的是迭代器本身,迭代器就不能++了, //const迭代器的目的是让内容不能被修改 //const_iterator 与iterator是两种完全不同的类型,没有什么关系 //const_iterator 是重新定义的类型。本身可以修改,指向的内容不能修改 const_iterator begin()const { return _head->_next;//node指针,单参数构造函数,隐式类型转换 } const_iterator end()const { return _head; }
1.3 增删改查的实现
第一步我们来模拟实现insert 和 erase,实现这个接口后,我们其他插入删除操作可以通过嵌套这两个函数实现,这里使用到了链表的知识,不理解的可以画一下图。
iterator insert(iterator pos,const T& val) { Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(val); //prev -> newnode -> cur newnode->_next = cur; newnode->_prev = prev; prev->_next = newnode; cur->_prev = newnode; _size++; return iterator(newnode);//构造一个iterator的匿名对象返回, //也可以直接返回newnode,进行隐式类型转换 } iterator erase(iterator pos)//迭代器会失效 { if(_head->_next!=_head) { Node* cur = pos._node; Node* prev = cur->_prev; Node* next = cur->_next; //prev -> next prev->_next = next; next->_prev = prev; delete cur; _size--; return iterator(next); } } void push_back(const T& x) { insert(end(), x); } void push_front(const T& x) { insert(begin(), x); } void pop_back() { erase(--end()); } void pop_front() { erase(begin()); } void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } }
1.4 拷贝构造函数,析构函数与赋值运算符重载
这里实现构造函数,析构函数,与赋值运算符重载。通过嵌套可以实现的更简洁。
list(const list<T>& l) { empty_init(); for (auto e : l) { push_back(e); } } void swap(list<T>& l) { std::swap(_head, l._head); std::swap(_size, l._size); } list<T>& operator=(list<T> l) { //这里传参已经是实现拷贝构造,只需交换*this和l 就可以实现赋值赋值运算符重载 swap(l); return *this; } ~list() { clear(); delete _head; }
1.5 泛型编程实现打印
我们可以使用一个模板函数来实现可以打印不同容器的内容。
void test4() { list<int> l1; l1.push_back(1); l1.push_back(2); l1.push_back(3); l1.push_back(4); l1.push_back(5); l1.push_back(6); //print_list(l1); print_Container(l1); list<string> l2; l2.push_back("12345678");//这里list.push_back 不会涉及深拷贝的问题, //vector是因为开空间要拷贝原来数据 l2.push_back("12345678"); l2.push_back("12345678"); l2.push_back("12345678"); //print_list(l2); print_Container(l2); vector<string> v; v.push_back("1111111111111"); v.push_back("1111111111111"); v.push_back("1111111111111"); v.push_back("1111111111111"); v.push_back("1111111111111"); print_Container(v); }
1.实现只能打印vector <int> 的函数
void print_list(const list<int>& l) { list<int>::const_iterator it = l.begin();//返回const迭代器 while (it != l.end()) { //*it = 1;//const迭代器不能修改 cout << *it << " "; it++; } cout << endl; }
2.实现可以打印 vector<T> 的函数。
之前讲class 和 typename 的不同之处在这里可以体现。
template<typename T> void print_list(const list<T>& l) { //这里要加 typename //list<T> 未实例化,编译器不能去他里面找 //不能判断const_iterator 是一个静态成员变量还是一个内嵌类型 //所以加一个typename 就是告诉编译器这里是一个类型,等它实例化了再去取,通过初步检查 typename list<T>::const_iterator it = l.begin(); while (it != l.end()) { cout << *it << " "; ++it; } cout << endl; }
3.实现可以打印所有容器的函数
template<typename Container> void print_Container(const Container& con) { typename Container::const_iterator it = con.begin(); while (it != con.end()) { cout << *it << " "; ++it; } cout << endl; } //模板(泛型编程)本质,本来应该由我们做的事情交给编译器去做
2. list 反向迭代器的实现
通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。
template<class Iterator> class ReverseListIterator { // 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态成员变量 // 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量 // 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的 public: typedef typename Iterator::Ref Ref; typedef typename Iterator::Ptr Ptr; typedef ReverseListIterator<Iterator> Self; public: // 构造 ReverseListIterator(Iterator it) : _it(it) {} // 具有指针类似行为 Ref operator*() { Iterator temp(_it); --temp; return *temp; } Ptr operator->() { return &(operator*()); } // // 迭代器支持移动 Self& operator++() { --_it; return *this; } Self operator++(int) { Self temp(*this); --_it; return temp; } Self& operator--() { ++_it; return *this; } Self operator--(int) { Self temp(*this); ++_it; return temp; } // 迭代器支持比较 bool operator!=(const Self& l)const{ return _it != l._it;} bool operator==(const Self& l)const{ return _it != l._it;} Iterator _it; };
3.list 和 vector 的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:
vector | list | |
底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随 机 访 问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素 效率O(N) |
插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂 度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低 |
任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1) |
空 间 利 用 率 | 底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高 |
底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低 |
迭 代 器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效 |
插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响 |
使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随 机访问 |
本篇结束!