与介绍vector时相同为了方便大家理解, 我们采用边模拟实现边讲解使用的方式, 展开对list容器的讲解.
目录
一.list的介绍
使用时注意包含头文件: #include<list>
list相关特性介绍:
1.list的底层是一个带头结点的双向循环链表.
2. list是可以在常数范围内在任意位置进行插入和删除的序列式容器, 并且该容器可以前后双向迭代.
3. list的底层是双向链表结构, 双向链表中每个元素存储在互不相关的独立节点中, 在节点中通过指针指向其前一个元素和后一个元素.
4. list与forward_list非常相似:最主要的不同在于forward_list是单链表, 只能朝前迭代, 而list让其更简单高效.
5. 与其他的序列式容器相比(array,vector,deque), list通常在任意位置进行插入、移除元素的执行效率更好.
6. 与其他序列式容器相比, list和forward_list最大的缺陷是不支持任意位置的随机访问, 比如: 要访问list的第6个元素, 必须从已知的位置(比如头部或者尾部)迭代到该位置, 在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间, 以保存每个节点的相关联信息.(对于存储类型较小元素的大list来说这可能是一个重要的因素)
7.在类和对象的位置, 知道静态成员变量, 也可以通过: 类名: :静态成员变量 进行访问, 而类中包含的其他类型, 也可以通过: 类名: :类内部定义类型 使用, 但要注意访问的是类型时要以"typename"为前缀来表示类型, 要不然编译器无法区分是类型还是静态成员变量.
前面看不懂没关系, 跟随讲解后面便会一一了解.
二.list中的经常使用的一些方法
1.构造函数
2.迭代器的使用
我们这里要注意, 前面介绍过的string和vector中因为底层使用的是连续空间的原因, 他们的迭代器都是原生态的指针, 但到list就不同了, list的底层是以双向链表的方式组织起来的不同空间位置的节点, 此时我们必须在list的类外重新封装一个迭代器的类, 该迭代器类中必须对++,--,==,!=等运算符进行重载, 与其他容器中的迭代器的某些功能保持一致, 之所以这样要求是因为有一些常用的算法例如: sort, find等等, 我们只用实现一份代码然后用不同容器的迭代器进行传参, 就能使用该算法, 不仅节省了资源, 还能防止了出现大量的重复代码.
3.容量的相关函数
4.容器中元素的访问
5.容器中元素的修改
三.list实现中的相关声明(后面对其进行具体实现)
我们要实现的list, c++系统已经给出了, 所以我们这里的实现将其放到自已定义的一个命名空间中.
namespace lz {
// List的节点类
template<class T> class list;
template<class T>
struct ListNode {
ListNode(const T& val = T());
ListNode<T>* _pPre;
ListNode<T>* _pNext;
T _val;
};
//List的正向迭代器类
template<class T, class Ref, class Ptr>
class ListIterator {
friend class list<T>;
typedef ListNode<T>* PNode;
typedef ListIterator<T, Ref, Ptr> Self;
public:
ListIterator(PNode pNode = nullptr);
ListIterator(const Self& l);
T& operator*();
T* operator->();
Self& operator++();
Self& operator++(int);
Self& operator--();
Self& operator--(int);
bool operator!=(const Self& l);
bool operator==(const Self& l);
private:
PNode _pNode;
};
//list类
template<class T>
class list {
typedef ListNode<T> Node;
typedef Node* PNode;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T&> const_iterator;
public:
///
// List的构造
list();
list(int n, const T& value = T());
template <class Iterator>
list(Iterator first, Iterator last);
list(const list<T>& l);
list<T>& operator=(list<T> l);
~list();
///
// List Iterator
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
///
// List Capacity
size_t size()const;
bool empty()const;
// List Access
T& front();
const T& front()const;
T& back();
const T& back()const;
// List Modify
void push_back(const T& val);
void pop_back();
void push_front(const T& val);
void pop_front();
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val);
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos);
void clear();
void swap(list<T>& l);
private:
void CreateHead();
PNode _pHead;
};
};
四.list中的节点类
说明:
因为在list底层是一个带头结点的双向循环链表, 所以我们要对其节点进行封装, 具体形式和C语言中的相同, 两个类类型的指针, 一个指向前面的节点类, 一个指向后面的节点类, 再加一个数据域来存储数据, 我们这里采用模板类的方式进行实现, 与c++系统的list保持同步, 并且这种模板类的方式, 可以针对于各种类型的数据, 只用使用者使用时将类型传入即可, 避免了同类代码的冗余.
代码说明:
根据上面说明的, 我们类中的数据成员有三个, 类类型的两个指针, 外加一个数据域, 再自定义一个传入数据用来初始化的构造函数即可, 至于节点的组织则再list类中进行实现.
// List的节点类
template<class T> class list;
template<class T>
struct ListNode {
ListNode(const T& val = T()) {//构造函数
_pPre = nullptr;
_pNext = nullptr;
_val = val;
}
//数据成员
ListNode<T>* _pPre;
ListNode<T>* _pNext;
T _val;
};
五.list中迭代器的封装
1.一个友元外加两个重命名
friend class list<T>;
typedef ListNode<T>* PNode;
typedef ListIterator<T, Ref, Ptr> Self;
①.我们需要在迭代器的类中将list类声明为其的友元类, 因为我们实现迭代器类就是为了在list中使用, 所以在list中需要访问到迭代器中的所有成员.
②.对节点类的类类型指针进行重命名, 方便我们后续的使用.
③.对自身的类类型进行重命名, 方便后续的使用.