接口分析
list底层是双向带头循环链表。头节点不保存有效数据。
list的遍历方法中就没有方括号了,只能使用迭代器。
没有reserve接口了,因为链表是按需申请的。
remove相当于find+erase,如果这个元素不存在就不会做任何操作,也不会报错。
sort默认是从小到大进行排序,为什么不用算法库里的sort?因为list的迭代器与算法库里给的迭代器类型不同,算法库里sort需要的是随机迭代器,但是list只是双向迭代器。【迭代器按照功能分类:单向迭代器++,比如单向链表和哈希;双向迭代器++、–,比如list,底层用归并排序;随机迭代器++、–、+、-,比如vector和string,底层可以用三数取中法快排】
unique去重,要求list必须要先有序。
底层实现
迭代器
版本1 一个模板参数
【iterator实际是内置类型,行为像指针的东西】
list要实现迭代器,就要有*
、++
以及!=
的功能,即要实现运算符重载成员函数,因此要封装一个类去实现这个功能。begin就是head的next,end就是head。
//封装一个迭代器类 以实现*、++和!=的功能
template<class T>
struct _list_iterator
{
typedef list_node<T> node;
node* _pnode;//成员变量
_list_iterator(node* pn)
:_pnode(pn)
{}
T& operator*()
{
return _pnode->_data;
}
_list_iterator<T>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
bool operator!=(const _list_iterator<T>& it)
{
return _pnode != it._pnode;
}
};
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);//用匿名对象返回
}
//...
};
以下代码测试均能通过
list<int>::iterator it = l1.begin();
while (it != l1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : l1)
{
cout << e << " ";
}
以上这个写法如果要加上const版本迭代器,则需要再写一个类封装,即两个类一样,只不过*运算符重载不一样。
//普通迭代器
template<class T>
struct _list_iterator
{
typedef list_node<T> node;
node* _pnode;//成员变量
_list_iterator(node* pn)
:_pnode(pn)
{}
T& operator*()//与模板const迭代器的唯一区别
{
return _pnode->_data;
}
_list_iterator<T>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
_list_iterator<T>& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
bool operator!=(const _list_iterator<T>& it)
{
return _pnode != it._pnode;
}
};
//const版本迭代器
template<class T>
struct _list_const_iterator
{
typedef list_node<T> node;
node* _pnode;//成员变量
_list_const_iterator(node* pn)
:_pnode(pn)
{}
const T& operator*()//与普通版本的iterator的区别 就是返回的是const T&
{
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;
}
};
template<class T>
class list
{
public:
typedef _list_iterator<T> iterator;//普通迭代器
typedef _list_const_iterator<T> const_iterator;//const迭代器
}
版本2 两个模板参数 新增->重载以及后置++、–
大佬写的,给模板多传了参数,能实现同一个类模板实例化出两个类型,同时实现普通迭代器和const迭代器
template<class T, class Ref>//Ref可以是T&或const T&
struct _list_iterator
{
typedef list_node<T> node;
typedef _list_iterator<T, Ref> self;
node* _pnode;//成员变量
_list_iterator(node* pn)
:_pnode(pn)
{}
Ref operator*()
{
return _pnode->_data;
}
T* operator->()//这个写法无法实现const版本
{
return &_pnode->_data;//相当于&(_pnode->_data)
}
//前置++
self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
//后置++
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)
{
return _pnode != it._pnode;
}
};
---------------------------
template<class T>
class list
{
public:
typedef _list_iterator<T, T&> iterator;//普通迭代器
typedef _list_iterator<T, const T&> const_iterator;//const迭代器
}
版本3 三个模板参数
template<class T, class Ref, class Ptr>//Ref可以是T&或const T&
struct _list_iterator
{
typedef list_node<T> node;
typedef _list_iterator<T, Ref> self;
node* _pnode;//成员变量
_list_iterator(node* pn)
:_pnode(pn)
{}
Ref operator*()
{
return _pnode->_data;
}
Ptr operator->()//多传一个参数以实现const版本
{
return &_pnode->_data;//相当于&(_pnode->_data)
}
//前置++
self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
//后置++
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)
{
return _pnode != it._pnode;
}
};
----------------------------
template<class T>
class list
{
public:
typedef _list_iterator<T, T&, T*> iterator;//普通迭代器
typedef _list_iterator<T, const T&, const T*> const_iterator;//const迭代器
}
实现运算符->重载 普通/const
如下应用场景
struct Pos
{
int _row;
int _col;
Pos()
:_row(0)
,_col(0)
{}
Pos(int row, int col)
:_row(row)
,_col(col)
{}
};
void test3()
{
list<Pos> lt;//链表里存坐标
Pos p1(1, 1);
lt.push_back(p1);
lt.push_back(Pos(1, 2));
lt.push_back(Pos(1, 3));
lt.push_back(Pos(1, 4));
list<Pos>::iterator it = lt.begin();
while (it != lt.end())
{
//重载了操作符->后,相当于it->->_row
cout << it->_row << ", " << it->_col << " | ";
cout << it.operator->()->_row << ", " << it.operator->()->_col << " | ";
cout << (*it)._row << ", " << (*it)._col << " | ";
cout << (&(*it))->_row << ", " << (&(*it))->_col << " | ";
++it;
}
cout << endl;
}
it->
相当于it.operator->()
,我们得到的返回值是Pos* tmp
。it->_row
本来是it->->_row
【因为编译器为了可读性,做了特殊处理,省略了一个->
】,就是tmp->_row
。
不允许的写法it->->_row
,干扰了编译器识别;
允许的写法it->_row
it.operator->()->_row
。
insert
在pos位置前插入数据
iterator insert(iterator pos, const T& x)
{
node* newnode = new node(x);
node* cur = pos._pnode;
node* prev = cur->_prev;
//链接prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
erase
删除该节点数据
iterator erase(iterator pos)
{
assert(pos != end());
node* cur = pos._pnode;
node* prev = cur->_prev;
node* next = cur->_next;
//链接prev和next
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
clear 和 析构函数
clear是删除所有有效数据节点,但保留头节点;而析构函数会把数据节点和头节点一起删除
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
拷贝构造函数
要用深拷贝
//拷贝构造函数--传统写法
list(const list<T>& lt)//建议写成上面用类型的
//list(const list& lt)//stl库给的,因为在类中可以用类名表示类型
{
empty_init();
for (auto e : lt)
{
push_back(e);
}
}
--------------------------------
//现代写法需要的辅助函数封装
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
}
template<class InputIterarot>
list(InputIterarot first, InputIterarot last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造函数--现代写法
list(const list<T>& lt)
{
//给this一个初始值,避免随机值,导致tmp析构出问题
empty_init();
//迭代器区间初始化
list<T> tmp(lt.begin(), lt.end());
//交换,出了函数tmp就会销毁
swap(tmp);
}
复制构造函数
//复制构造函数--传统写法
list<T>& operator=(const list<T>& lt)//建议写成上面用类型的
//list& operator=(const list& lt)//stl库给的,因为在类中可以用类名表示类型
{
//判断是否为自己给自己赋值
if (this != <)
{
clear();
for (const auto& e : lt)
{
push_back(e);
}
}
return *this;
}
---------------------------------------
//复制构造函数--现代写法
list<T>& operator=(const list<T> lt)//注意这个不要加引用,这样得到的lt是经过复制构造后的
{
//判断是否为自己给自己赋值
swap(lt);
return *this;
}
严格区分类模板的类型和类名
类模板中类型不等于类名,普通类中类型就是类名。
比如list是类名,list才是类型。构造函数是特殊的成员函数,函数名与类名相同。
有两个例外:拷贝构造list(const list& x);
以及运算符重载list& operator(const list& x);
我们知道函数的参数和返回值应该写 类型,参数应该写成list<T>
,这两个为什么不是这样呢?
答:因为官方库认为在类模板内可以用类名表示类型,但这个写法不好。建议规范用类型list(const list<T>& lt);
和list<T>& operator=(const list<T>& lt);
。
vector和list对比
vector cpu缓存命中率高。
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效;删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效;删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
注意,string的迭代器也会失效,和vector类似,不过一般不关心string的失效,因为它的接口设计用的都是pos下标,不是迭代器。