网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
我们通过一段测试代码来比较,同样的长度但是花费时间是巨大的。
void test_op()
{
srand(time(0));
const int N = 100000;
vector<int> v;
v.reserve(N);
list<int> lt1;
list<int> lt2;
for (int i = 0; i < N; ++i)
{
auto e = rand();
//v.push_back(e);
lt1.push_back(e);
lt2.push_back(e);
}
// 拷贝到vector排序,排完以后再拷贝回来
int begin1 = clock();
for (auto e : lt1)
{
v.push_back(e);
}
sort(v.begin(), v.end());
size_t i = 0;
for (auto& e : lt1)
{
e = v[i++];
}
int end1 = clock();
int begin2 = clock();
// sort(lt.begin(), lt.end());
lt2.sort();
int end2 = clock();
printf("vector sort:%d\n", end1 - begin1);
printf("list sort:%d\n", end2 - begin2);
}
在运行结果:
1.Debug
vector sort:437
list sort:69372.Release
vector sort:7
list sort:16
在N个数据需要排序,vector+ 算法sort list+ sort通过测试发现list中sort是非常耗时的,vector中sort想对来说更加省时直接用list排序还不如将list的数据拷贝到vector中快。
二、list的模拟实现–非const
我们会通过几个阶段来进行模拟实现,如果一下将全部模拟实现是加大了学习的成本,是对学习很不友好的。
2.1list的节点
因为我们知道list是一个双向链表,所以节点里面有一个前指针,一个后指针,有一个数据data。同时我们也模拟一个构造函数list (const list& x),用于list节点的初始化。
template < class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
//构造函数
list_node(const T& x)
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{}
};
2.2list的迭代器
stl3.0当中的迭代器实现:
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;
__list_iterator(link_type x) : node(x) {}
__list_iterator() {}
__list_iterator(const iterator& x) : node(x.node) {}
bool operator==(const self& x) const { return node == x.node; }
bool operator!=(const self& x) const { return node != x.node; }
reference operator*() const { return (*node).data; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
self& operator++() {
node = (link_type)((*node).next);
return *this;
}
self operator++(int) {
self tmp = *this;
++*this;
return tmp;
}
self& operator--() {
node = (link_type)((*node).prev);
return *this;
}
self operator--(int) {
self tmp = *this;
--*this;
return tmp;
}
对于stl3.0当中的迭代器实现,我们首先不去关系为什么是3个模板参数,也不用去深研其他的typedef,我们可以理解在iterator中不单单是一个指针了,当然在iterator也有指针,如下
typedef __list_node* link_type;
link_type node;
在之前我们理解中,我们直接对指针进行操作,进行++,–,*(解引用)等等操作,但是对于迭代器iterator,我们通过观察发现iterator中,因为list是链式结构,我们对其++,–,接引用是不能直接实现,所以就不能简单的通过指针就能完成这些操作,那么它是通过对iterator封装来实现++,–等操作的。使用++,–等操作是通过函数调用
大家感兴趣可以先看看上面的,我们先用一个简述版的来带大家简要实现一下
template < class T>
struct _list_iterator
{
typedef list_node<T> node;
node* _pnode;
//构造函数
_list_iterator(node* p)
:_pnode(p)
{}
//接引用就是返回节点的值
T& operator*()
{
return _pnode->_data;
}
//++操作就是就将当前指向下一个节点
_list_iterator<T>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
//--操作就是将当前指向前一个节点
_list_iterator<T>& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
//不等于传参节点的指针不等于this节点的指针
bool operator!=(const _list_iterator<T>& it)
{
return _pnode != it._pnode;
}
};
2.2迭代器的价值
这里虽然我们使用vector和list的使用方法是基本类似的,但是我们发现他们的底层已经出现很大的差别。这里*(接引用),在gcc下的vector中我们是直接对原生指针进行操作,但是在list下我们是通过函数调用来实现的。
最后关于迭代器需要强调一点,关于vector中的 iterator其实他可能也不是用原生指针,这个需要看代码的实现,因为我们在gcc和vs中我们发现迭代器失效,他们失效并不一样,意味着他们底层代码实现时不一样的,在gcc它可以是通过原生指针的方式实现,在vs也可以通过封装来实现。
结论:
1.封装底层实现,不暴露底层的实现细节
2.提供统一访问方式,降低使用成本
物理层面比较
对于list类封装中的iteratior与vector中的iteratior的字节大小是一样的,他们都是4字节。
为什么list也是4字节,因为只看成员变量,不看函数。所以说指针在类中封装后并没有变大,在内存中还是实实在在的4byte。
他们不同的是list中存的时候节点位置的指针,而vector中的是数据位置的指针。
所以在物理层面上他们是没有区别的,他们字节的大小是一样的,但是他们的类型不一样,底层实现就是天差地别了–类型的力量。
2.3list的接口
template < class T>
class list
{
typedef list_node<T> node;
public:
typedef _list_iterator<T> iterator;
iterator begin()
{
//使用了匿名对象--调用拷贝构造
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
//当为null时,next与prev都指向head
void empty_initalize()
{
_head = new node(T());
_head->_next = _head;
_head->_prev = _head;
}
//为null调用empty_initalize
list()
{
empty_initalize();
}
//拷贝构造
list(const list<T> & lt)
{
empty_initalize();
//这里需要特别注意&(引用)
//如果是内置类型就不影响,e接受数据后,push_back传入后销毁,下一个传又是从lt引用
//如果lt是其他类型,比如是vector类型或者是泛型,数据是一串,
//当push_back完后,没有用引用那么push_back会销毁,传一个数据他销毁了再传一个数据它又没了
for (const auto &e : lt)
{
push_back(e);//this.push_back
}
return *this;//返回链接值 head->1->2->3
}
//析构函数
~list()
{
clear();//第一步清楚数据
//第二步将头结点置空
delete _head;
_head = nullptr;
}
//将数据一一清理
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
list<T>& operator=(const list<T>& lt)
{
if (this != <)//判断链表数据不一样后进行操作
{
clear();//先清空
for (const auto&e : lt)//传值
{
push_back(e);
}
}
}
//头插
void push_front(const T& x)
{
insert(begin(), x);//调用insert
}
//头删
void pop_front()
{
erase(begin());//调用erase
}
//尾删
void pop_back()
{
erase(--end());
}
//尾插
void push_back(const T& x)
{
node* newnode = new node(x);
node* tail = _head->_prev;
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
//insert(end(),x)
}
iterator insert(iterator pos, const T& x)
{
node* newnode = new node();//新建节点
node* cur = pos._pnode;//插入节点是在数据后插,保存改数据后的节点
node* prev = cur._prve;//保存数据前的节点
//4步链接头尾
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);//返回节点
}
iterator erase(iterator pos)
{
assert(pos != end());//不能删除头节点
node* prev = pos._pnode->_prev;//保存pos前的节点
node* next = pos._pnode->_next;//保存pos后的节点
prev->_next = next;//将后节点链接前节点
next->_prev = prev;//将前节点链接后节点
delete pos._pnode;//删除pos节点--会失效
return iterator(next);//返回pos节点后的节点
}
private:
node* _head;
};
list接口的模拟实现主要insert和erase有些难度,但是对于写过数据结构的代码,我相信大家稍花功夫就能够轻松解决。
insert图解如下
erase图解如下
三、list的模拟实现–const
下面内容主要是迭代器被const修饰,普通人的写法和大佬的写法,他们尽显锋芒。
3.1理解const修饰iterator
错误写法
void list_test(const list<int>& lt)
{
const list<int>::iterator lt1 = lt.begin();
}
首先需要理解const T* p1与T* const p2,const迭代器类似p1的行为,保护指向对象不被修改,但是迭代器本身是可以被修改的。
这里的const是修饰的lt1,不符合const迭代器的行为,因为他保护迭代器本身不能修改,那么我们也就不能++迭代器。
因为iterator是被封装使用的,我们发现在struct _list_iterator中,我们改变其返回值用const修饰,那么我们还是可以对迭代器进行++,–等操作,只是返回值不能被改变。
T& operator*()
{
return _pnode->_data;
}
既然是需要const修饰返回值,那么我们能不能直接通过函数重载来支持呢?
T& operator*()
{
return _pnode->_data;
}
const T& operator*() const
{
return _pnode->_data;
}
答案是不能的。如果clt(被const修饰的参数)++,就调用operator++(),返回值是不被修改,这里的this也被const修改,那么this指向的节点都被修改,该节点就不能实现++操作,这样实现的话只能接引用,但不能++。
const T& operator*() const
{
return _pnode->_data;
}
_list_iterator<T>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
如果我们是实现
const _list_iterator& operator++() const
答案也是不行的,因为这个过程就是对this值进行操作,这里其实就this已经都被const修饰了。有点绕但是我们主要需要知道整个this值,不然很容易就昏了。
3.2实现const修饰iterator
既然上述解释了在一个类中通过函数重载,const修饰的this会影响,因为我们需要函数重载的话,const不仅仅需要修返回值,也要修改this,上面写法就是错误的
const T& operator*() const
那么我们重新建立类,其他不变,只将返回参数改成被const修饰即可。
const T& operator*()
template<class T>
struct _list_const_iterator
{
typedef list_node<T> node;
node* _pnode;
_list_const_iterator(node* p)
:_pnode(p)
{}
const T& operator*()
{
return _pnode->_data;
}
_list_const_iterator<T>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
_list_const_iterator<T>& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
bool operator!=(const _list_const_iterator<T>& it)
{
return _pnode != it._pnode;
}
};
我们建立了两个类,所以我们在list直接进行用就可以了。添加上begin+end被const修饰的接口就可以了。
template <class T>
class list
{
typedef list_node<T> node;
public:
typedef _list_iterator<T> iterator;
typedef _list_const_iterator<T> const_iterator;
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
}
四、list的模拟实现–大佬的iterator
上面写的iterator,我们发现大多数代码都是一样的,仅仅是返回值不一样,就建立了两个类,这样的话代码就比较冗余。那么大佬是不可能写出这样的代码,大佬就通过增加模板参数就很好的解决这一问题了。
4.1第三个参数
原来模板参数只有一个类型,现在将模板参数增加为两个,以前我们是通过自己构建两个类,来实现他们不同功能,但是我们直接在模板里多增加一个类型,编译器就默认实例化两个类型。
template<class T, class Ref>
同一个类模板实例化出的2个类型
typedef __list_iterator<T, T&,> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
这里T就是普通参数,Ref就是被const修饰的参数。那么在看源码的时候有三个参数,这里就不得不提到返回数据的指针了。
struct pos
{
int _row;
int _col;
pos(int row=0, int col=0)
:_row(row)
, _col(col)
{
}
};
int main()
{
list<pos> lt;
pos p1(1,1);
lt.push_back(p1);
lt.push_back(p1);
lt.push_back(p1);
lt.push_back(pos(2,2));
lt.push_back(pos(3,3));
list<pos>::iterator it = lt.begin();
while (it != lt.end())
{
cout << (*it)._row << ":" << (*it)._col << endl;
++it;
}
return 0;
}
我们在结构体中,是通过接引用(迭代器的位置)的值,该值再通过**.**去访问结构体中的行的值。这样用起来是非常别扭,以前访问结构体就是直接地址->指向实参,it->_row。
回忆C语言中->的用法
struct Data
{
int a, b, c;
};
int main()
{
struct Data * p;
struct Data A = { 1, 2, 3 };
int x;
p = &A;
x = p->a;
cout << x << endl;
return 0;
}
这里it是迭代器,所以我们需要封装->,然后在进行应用。
Ptr operator->()
{
return &_pnode->_data;
}
Ptr返回的是节点数据的地址,拥有->后,我们再看看使用。
while (it != lt.end())
{
cout << it->_row << “:” << it->_col << endl;
++it;}
it->_row是什么意思?it->是调用it.operator->(),返回节点数据的地址,紧接着是返回节点数据的地址->_row,正确的写法应该是it->->_row。但是这是既不好看又不好用,编译器为了可读性,省略了一个->。
while (it != lt.end())
{
cout << it.operator->()->_row << “:” << it->_col << endl;
++it;}
当然这样用也是不错的。
综上所述知道我们所添加的第三个参数就是T*,用于接受返回的地址。
template<class T, class Ref, class Ptr>
typedef __list_iterator<T, T&, T*> iterator;
4.2大佬的iterator
#pragma once
#include<assert.h>
namespace qhx
{
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T& x)
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{}
};
// 同一个类模板实例化出的2个类型
// typedef __list_iterator<T, T&, T*> iterator;
// typedef __list_iterator<T, const T&, const T*> const_iterator;
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> node;
typedef __list_iterator<T, Ref, Ptr> Self;
node* _pnode;
__list_iterator(node* p)
:_pnode(p)
{}
//因为很多时候我们需要返回到数据的指针
//返回数据的指针
Ptr operator->()
{
return &_pnode->_data;
}
Ref operator*()
{
return _pnode->_data;
}
Self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
// it++
Self operator++(int)
{
Self tmp(*this);
_pnode = _pnode->_next;
return tmp;
}
Self& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
_pnode = _pnode->_prev;
return tmp;
}
bool operator!=(const Self& it) const
{
return _pnode != it._pnode;
}
bool operator==(const Self& it) const
{
return _pnode == it._pnode;
}
};
//vector<int>
//vector<string>
//vector<vector<int>>
// 类名 类型
// 普通类 类名 等价于 类型
// 类模板 类名 不等价于 类型
// 如:list模板 类名list 类型list<T>
// 类模板里面可以用类名代表类型,但是建议不要那么用
template<class T>
class list
{
typedef list_node<T> node;
public:
typedef __list_iterator<T, T&, T*> iterator;
//typedef __list_const_iterator<T> const_iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
//iterator it(_head);
//return it;
return iterator(_head);
}
void empty_initialize()
{
_head = new node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
empty_initialize();
}
//迭代器区间构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_initialize();
while (first != last)
{
push_back(*first);
++first;
![img](https://img-blog.csdnimg.cn/img_convert/cc6eede4115685f035f0e75bbdcb0c58.png)
![img](https://img-blog.csdnimg.cn/img_convert/e837b48435e021260c9930a8814fc3cd.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
//iterator it(_head);
//return it;
return iterator(_head);
}
void empty_initialize()
{
_head = new node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
empty_initialize();
}
//迭代器区间构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_initialize();
while (first != last)
{
push_back(*first);
++first;
[外链图片转存中...(img-X7VLDZZp-1715682022883)]
[外链图片转存中...(img-PpPK6687-1715682022884)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**