从看懂到看开:c++的实用容器list的讲解与模拟实现

1.什么是list类?

1.list是在常数范围内在任意位置进行插入删除的序列容器,并且该容器可以实现双向迭代

2.list类的底层是双向带头循环链表结构,每个元素都存储在互不相关的独立节点中,每个节点都有指向其前后节点的指针

3.该类与forward_list非常相似,但后者是单链表,适用性不如list类

4.与vector,array,deque相比,list通常在任意位置插入和删除的效率更好

5.list类的缺陷:和foward_list一样,不支持任意位置的随机访问只能进行遍历访问,消耗大量时间,list类还需要额外空间去存储元素的关联信息,当元素较小时显得效率低下

2.list类的节点

1.使用struct,标明公有属性,以便从外部调用

2.list是带头双向循环链表

template <class T>
struct ListNode
{
ListNode<T>*_prev;
ListNode<T>*_last;
T_val;
Listnode(const T&val=T):_prev(nullptr),_last(nullptr),_val(val)
{}
};

3.迭代器

1.与vector不同,由于物理空间上的不连续,导致迭代器不能像string或者vector那样设计为指针,而需封装为一个类,以便完成*,->,++,--等操作

        1.成员变量与默认构造函数

1.仍然使用struct,标明公有属性成员变量是一个结点的指针

template <class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref,  Ptr>self;
	ListIterator(Node* ptr)
		:Node(ptr)
	{}
 
	Node* _Node;
};
        2.operator*
Ref operator*()
{
	return _Node->_val;
}

与之前代码进行对比

T& operator[](const int i)
{
	assert(i < size());
	return *(_start + i);
}
const T& operator[](const int i)const
{
	assert(i < size());
	return *(_start + i);
}

很明显,为了对const和非const对象进行访问需要对函数进行分类重载,但此时operator*并没有对const和非const进行分类重载。由于权限的放大缩小原则,传参可以写成一样的,那就只有返回值不同了,所以直接使用模板泛型编程,直接设计参数Ref,把它当作返回值就可以实现重载的效果,这里的返回值是val类型的引用分为const和非const

        3.operator->
Ptr operator->()
{
	return &_Node->val;
 }

与operator*的实现相同,这就是迭代器模板参数有三个的原因,返回值是val类型的指针,分为const和非const

        4.operator++

为了区分前置与后置,后置参数加int(无实际意义),参数在前传引用,参数在后传值

self& operator++()
{
	_Node = _Node->_last;
	return _Node;
}
 
self operator++(int)
{
	Node* tmp = _Node;
	_Node = _Node->last;
	return tmp;
}
        5.operator--(与++同理)
self operator--(int)
{
 
	Node* tmp = _Node;
	_Node = _Node->_prev;
	return tmp;
}
self operator--()
{
	_Node = _Node->_prev;
	return _Node;
}
        6.operator==
bool operator==(self& s)
{
	return _Node == s._Node;
}
        7.operator!=
	bool operator!=(self& s)
	{
		return _Node != s._Node;
	}

4.list

        1.成员变量(head是哨兵位)
template<class T>
class List
{
public:
	typedef ListNode Node;
private:
	Node* _head;
};
        2.对迭代器的处理

typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&,const T*> const_iterator;

1.begin与end为正向迭代器,对迭代器进行++操作,迭代器向后移动

2.rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动,不常用

                1.begin

返回值类型要强制类型转换,而且返回值是head的下一个,不是哨兵位

iterator begin()
{
	return iterator(_head->_last);
}
const_iterator begin()const
{
	return const_iterator(_head->_last);
}
                2.end

返回值要强制类型转换,返回的是哨兵位

iterator end()
{
	return iterator(_head);
}
const_iterator end()const
{
	return const_iterator(_head);
}
        3.默认成员函数
                1.构造函数

                        1.创建哨兵位,缺省参数
List(T val=T())
:_head(new Node)
{
	_head->_last = _head;
	_head->_prev = _head;
	_head->_val = val;
}
                        2.迭代器区间构造
template <class InputIterator>
List(InputIterator first, InputIterator last)
{
	_head=new Node;
	_head->_last = _head;
	_head->_prev = _head;
	_head->_val = T();
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}
        2.析构函数
~List()
{
	clear();
	delete _head;
	_head = nullptr;
}

clear的作用是消除哨兵位以外的节点

        3.拷贝构造函数
List(List<T>& l)
{
	_head(new Node);
	_head->_last = _head;
	_head->_prev = _head;
	_head->_val = T();
	List<T>tmp(l.begin(), l.end());
	swap(tmp);
}
        4.operator=
传参变为传值,这样就会拷贝构造出一个临时对象,再使用list中的swap,交换*this与tmp的值,完成赋值重载
                
List<T>& operator=(List<T>l)
{
	swap(l);
	return*this;
}

4.修改

        1.insert(迭代器不会失效,本来在这个位置的节点后移)
void intsert(iterator pos,T val)
{
	Node* cur = pos._Node;
	Node* prev = cur->_prev;
	Node* NewNode = new Node(val);
	NewNode->_prev = prev;
	prev->_last = NewNode;
	NewNode->_last = cur;
	cur->_prev = NewNode;
}
        2.push_front(头插)
void push_front(T val)
{
	Node* NewNode = new Node(val);
	Node* last = _head->_last;
	NewNode->_prev = _head;
	NewNode->_last = last;
	last->_prev = NewNode;
	_head->_last = NewNode;
}
        3.push_back(尾插)
void  push_back(T val)
{
	Node* NewNode = new Node(val);
	Node* tail = _head->_prev;
	tail->_last = NewNode;
	NewNode->_prev = tail;
	NewNode->_last = _head;
	_head->_prev = NewNode;
}
        4.erase(指定位置删除)

断言防止删除哨兵位,返回删除节点的下一位,防止迭代器失效

iterator erase(iterator pos)
{
assert(pos!=end());
	Node* cur = pos._Node;
	Node* prev = cur->_prev;
	Node* last = cur->_last;
	prev->_last =last;
	last->_prev = prev;
	delete cur;
	return iterator(last);
}
        5.pop_front(头删)
void  pop_front()
{
	erase(begin());
}
        6.pop_back(尾删)
void pop_back()
{
	erase(--end());
}
        7.clear(清除除哨兵位外的所有节点)
void clear()
{
	Node* New = _head->_last;
	while (New != _head)
	{
		Node* News = New->_last;
		delete New;
		New = News;
	}
}

        8.swap(交换两个list的值)

调用std库中的swap函数,要带有std::,否则会被认为是自己写的swap,导致无限递归

void swap(List<T>&l)
{
	std::swap(_head,l._head);
}

        9.迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针, 迭代器失效即迭代器所指向的节点的无效,即该节 点被删除了 。因为 list 的底层结构为带头结点的双向循环链表 ,因此 在 list 中进行插入时是不会导致 list 的迭代 器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响 。

5.与vector的对比

相关推荐

  1. C++ list模拟实现

    2024-04-10 09:18:01       31 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-10 09:18:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-10 09:18:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-10 09:18:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-10 09:18:01       20 阅读

热门阅读

  1. easyui 使用记录

    2024-04-10 09:18:01       13 阅读
  2. 第四十七章 为 Web 应用程序实现 HTTP 身份验证

    2024-04-10 09:18:01       12 阅读
  3. hbase的基础搭建

    2024-04-10 09:18:01       12 阅读
  4. mysql create procedure

    2024-04-10 09:18:01       12 阅读
  5. HBase详解(3)

    2024-04-10 09:18:01       9 阅读
  6. 封装Element-Plus表单组件

    2024-04-10 09:18:01       15 阅读
  7. textcnn做多分类

    2024-04-10 09:18:01       10 阅读
  8. Android ViewStub

    2024-04-10 09:18:01       10 阅读
  9. 自动驾驶中的传感器融合算法

    2024-04-10 09:18:01       50 阅读
  10. 【前端】项目Vue2升级Vue3注意事项

    2024-04-10 09:18:01       10 阅读
  11. 需求分析思路

    2024-04-10 09:18:01       12 阅读