在C++标准库中,list是一个非常重要的序列容器,它本质上是一个双向链表。与vector这样的连续存储容器不同,list的每个元素都存储在自己独立的节点中,并通过指针相互连接。这种结构使得list在任何位置插入和删除元素都非常高效,时间复杂度为O(1),但同时也失去了随机访问的能力。
作为C++学习者,手动实现标准库容器是深入理解其工作原理的最佳方式。通过这个过程,你将:
一个标准的双向链表节点通常包含三个部分:
这种双向链接的结构使得我们可以高效地在两个方向上遍历链表,也为各种操作提供了便利。
我们首先定义一个模板类ListNode来表示链表的节点:
cpp复制template <class T>
struct ListNode
{
ListNode(const T& data = T())
:_data(data)
, _next(nullptr)
, _prev(nullptr)
{}
T _data;
ListNode<T>* _next;
ListNode<T>* _prev;
};
这里有几个关键点需要注意:
T使得我们的list可以存储任意类型的数据T()作为默认参数,这可以正确处理内置类型和自定义类型nullptr,确保新节点处于确定状态提示:对于自定义类型,
T()会调用默认构造函数创建匿名对象。如果类型没有默认构造函数,需要在实例化时显式提供数据。
构造函数中的= T()语法值得特别关注:
T()会进行值初始化(int初始化为0)我们的list类需要维护两个关键成员:
cpp复制template<class T>
class list
{
typedef ListNode<T> Node;
public:
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
_count = 0;
}
private:
Node* _head;
size_t _count;
};
这里采用了"带头节点的双向循环链表"设计,这种结构有几个优点:
构造函数完成了以下工作:
这种设计下,即使是空链表也包含一个头节点,这使得后续的插入删除操作更加统一。
与vector不同,list的迭代器不能简单地用指针实现,因为:
因此,我们需要设计一个专门的迭代器类来封装这些行为。
cpp复制template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node = nullptr)
:_node(node)
{}
// 解引用操作符
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;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
};
Ref和Ptr参数,我们可以用同一个类模板实现普通迭代器和const迭代器通过在list类中添加以下typedef,我们可以方便地使用const迭代器:
cpp复制typedef ListIterator<T, const T&, const T*> const_iterator;
这种设计避免了代码重复,同时保持了类型安全。
反向迭代器与正向迭代器的主要区别在于移动方向相反:
我们可以通过封装正向迭代器来实现反向迭代器:
cpp复制template<class Iterator>
class ReverseIterator
{
public:
ReverseIterator(Iterator it)
:_it(it)
{}
// 前置++
ReverseIterator& operator++()
{
--_it;
return *this;
}
// 后置++
ReverseIterator operator++(int)
{
ReverseIterator tmp(*this);
--_it;
return tmp;
}
// 前置--
ReverseIterator& operator--()
{
++_it;
return *this;
}
// 后置--
ReverseIterator operator--(int)
{
ReverseIterator tmp(*this);
++_it;
return tmp;
}
// 解引用操作
auto operator*() -> decltype(*_it)
{
auto tmp = _it;
return *--tmp;
}
// 箭头操作
auto operator->() -> decltype(_it.operator->())
{
return &(operator*());
}
bool operator!=(const ReverseIterator& rit)
{
return _it != rit._it;
}
bool operator==(const ReverseIterator& rit)
{
return _it == rit._it;
}
private:
Iterator _it;
};
rbegin()应该返回指向最后一个元素的迭代器rend()应该返回指向头节点的迭代器cpp复制iterator insert(iterator pos, const T& val)
{
Node* newNode = new Node(val);
Node* cur = pos._node;
Node* prev = cur->_prev;
newNode->_next = cur;
newNode->_prev = prev;
prev->_next = newNode;
cur->_prev = newNode;
++_count;
return iterator(newNode);
}
cpp复制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;
--_count;
return iterator(next);
}
push_back/push_frontpop_back/pop_frontsize/emptyclearcpp复制template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
typedef ReverseIterator<iterator> reverse_iterator;
typedef ReverseIterator<const_iterator> const_reverse_iterator;
// 构造函数
list();
list(size_t n, const T& val = T());
list(const list<T>& l);
list<T>& operator=(const list<T> l);
~list();
// 迭代器相关
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
reverse_iterator rbegin();
reverse_iterator rend();
const_reverse_iterator rbegin() const;
const_reverse_iterator rend() const;
// 容量相关
size_t size() const;
bool empty() const;
// 元素访问
T& front();
const T& front() const;
T& back();
const T& back() const;
// 修改操作
void push_back(const T& val);
void push_front(const T& val);
void pop_back();
void pop_front();
iterator insert(iterator pos, const T& val);
iterator erase(iterator pos);
void clear();
void swap(list<T>& l);
private:
Node* _head;
size_t _count;
};
cpp复制void TestList()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
for(auto it = l.begin(); it != l.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
for(auto rit = l.rbegin(); rit != l.rend(); ++rit)
{
cout << *rit << " ";
}
cout << endl;
auto it = l.begin();
++it;
l.insert(it, 10);
it = l.begin();
++it;
l.erase(it);
list<int> l2(l);
l2 = l;
}
在实现list时,最容易出现的问题就是内存泄漏。特别是在以下情况:
解决方案:
new都有对应的deletelist的迭代器在以下情况下会失效:
解决方案:
链表操作中最容易出错的就是边界条件,特别是:
解决方案:
对于小型对象,可以考虑:
如果需要线程安全的list,可以:
可以进一步实现:
在实际项目中使用list时,有几个经验值得分享:
优先考虑使用std::list,除非有特殊需求
对于频繁随机访问的场景,vector通常更合适
list在以下场景表现优异:
调试技巧:
性能调优: