目录
一.list容器介绍
二.C++中list的基本组成
三.list容器相关接口的模拟实现
1.push_back()
2.迭代器的begin()和end()
3.insert()
4.erase()
5.pop_front()
6.pop_back()
7.size()
8.empty()
9.析构~list()和清除数据clear()
10.拷贝构造
11.赋值运算
四.模拟实现时所遇到的问题
1.实现链表的迭代器
(1)原生指针的正确使用
2.自定义类型要想支持运算符重载需要自己写
3.const迭代器不能被修改
4.合并const类型迭代器和普通迭代器(模板的进阶使用)
一.list容器介绍
- list是序列容器,允许在序列中的任何位置执行固定O(1)时间复杂度的插入和删除操作,并在两个方向进行迭代。
- list容器使用双链表实现;双链表将每个元素存储在不同的位置,每个节点通过next,prev指针链接成顺序表。
- list与其他标准序列容器(array,vector和deque)相比,list通常可以在容器内的任何位置插入、提取和移动元素。
- list与其他标准序列容器(array,vector和deque)相比,list和forward_list(单链表实现)的主要缺点是他们不能通过位置直接访问元素;例如,要访问列表中的第五个元素,必须从已知位置(开始或结束)迭代到该位置,需要哦线性时间开销。
- 存储密度低,list要使用一些额外的内容空间(next,prev)来保持与每个元素相关联(前后续的线性)的链接信息,从而导致存储小元素类型(如char,short,int等)的列表的存储密度低。
二.C++中list的基本组成
namespace List
{
template <class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& x = T())
:_next(nullptr)
,_prev(nullptr)
,_data(x)
{}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
//构造函数(哨兵位)
list()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
private:
Node* _head;
size_t _size;
};
}
三.list容器相关接口的模拟实现
1.push_back()
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;
}
2.迭代器的begin()和end()
typedef ListIterator<T> iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
3.insert()
//pos位置插入
void insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* newnode = new Node(val);
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
_size++;
}
4.erase()
//删除
iterator erase(iterator pos)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
_size--;
return iterator(next);
}
5.pop_front()
void pop_front()
{
erase(begin());
}
6.pop_back()
void pop_back()
{
erase(--end());
}
7.size()
size_t size() const
{
return _size;
}
8.empty()
bool empty()
{
return _size == 0;
}
9.析构~list()和清除数据clear()
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
10.拷贝构造
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
//拷贝构造
list(list<T>& It)
{
empty_init();
for (auto& e : It)//写&为了避免It是String等类进行拷贝构造
{
push_back(e);
}
}
11.赋值运算
void swap(list<T> It)
{
std::swap(_head, It.head);
std::swap(_size, It.size);
}
//赋值运算 It1=It3
list<T>& operator=(list<T> It)
{
swap(It);
return *this;
}
四.模拟实现时所遇到的问题
1.实现链表的迭代器
(1)原生指针的正确使用
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
这里和vecctor进行对比,vecor中指针T*是可以进行++操作的,而指针Node*无法进行++操作,原因为:链表的每一个节点的物理空间不是连续的。
其次,Node*进行解引用并不能得到我们想要的数据(比如1),得的的是一个Node结点。
我们称这样的指针T*为不符合我们需求的原生指针。
- 解决方法
1.封装一个自定义迭代器的类
2.重载运算符,掌控原生指针的行为,使其满足需求
- 代码
template <class T> struct ListIterator { typedef ListNode<T> Node; typedef ListIterator<T> Self; Node* _node;//内置类型,编译器自动调用拷贝构造(浅拷贝) ListIterator(Node*node) :_node(node) {} //解引用 *it T& operator*() { return _node->_data; } //前置++ 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; } //比较两个迭代器是否相等 bool operator!=(const Self& it) { return _node != it._node; } };
- 注意 :该迭代器类不需要写析构函数,因为该迭代器的本质是要访问我们链表中的节点。不可以在访问完成后对该链表进行释放。
2.自定义类型要想支持运算符重载需要自己写
- 代码
struct A
{
int _a1;
int _a2;
A(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{}
};
void test_list2()
{
list<A> lt;
A aa1(1, 1);
A aa2 = { 1, 1 };
lt.push_back(aa1);
lt.push_back(aa2);
lt.push_back(A(2, 2));
lt.push_back({ 3, 3 });
lt.push_back({ 4, 4 });
A* ptr = &aa1;
(*ptr)._a1;
ptr->_a1;
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
cout <<*it << endl; 报错
++it;
}
cout << endl;
}
}
- 解决方法
1.公有数据直接访问
while (it != lt.end()) { cout <<(*it)._a1<<":"<<(*it)._a2 << endl; ++it; }
2.使用 -> 访问
T* operator ->() { return &_node->_data; } while (it != lt.end()) { cout << it->_a1 << ":" << it->_a2 << endl; //cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;编译器转换的样子 }
什么意思呢?
- 图解
3.const迭代器不能被修改
- 代码
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return iterator(_head->_next);
}
const_iterator end() const
{
return iterator(_head);
}
要同时写上不同版本迭代器和const版本迭代器,如果只写了const版本的迭代器会导致非const对象也可以调用const成员函数(权限缩小),造成本身不可以被修改的结果最终可以被修改。
- 注意
const迭代器指的是迭代器指向的内容不能被修改。
eg. iterator begin() const
const iterator 指的是迭代器本身不能被修改不是我们所需要的迭代器
4.合并const类型迭代器和普通迭代器(模板的进阶使用)
- 代码
template <class T,class Ref,class Ptr>//Ref引用,Ptr指针
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T,Ref,Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{}
//解引用 *it
Ref operator*()
{
return _node->_data;
}
//->
Ptr operator ->()
{
return &_node->_data;
}
//前置++
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;
}
//比较两个迭代器是否相等
bool operator!=(const Self& it)
{
return _node != it._node;
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
/* typedef ListIterator<T> iterator;
typedef ListIterator<T> const_iterator;*/
typedef ListIterator<T,T&,T*> iterator;
typedef ListIterator<T,const T&,const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return iterator(_head->_next);
}
const_iterator end() const
{
return iterator(_head);
}
}
- 图解