【C++】关于list的使用&&底层实现

文章目录

一、认识list

  • list 是一种序列容器,可以在序列中的任何位置进行常数时间插入删除 操作,并且支持双向迭代
  • list容器是用双向链表实现的 ;双向链表的每个元素存储在互不相关的独立节点中。通过每个节点的前驱和后继指针链接,内部保持了元素的顺序。
  • 它和单向链表(forward_list)非常相似:主要区别在于单向链表对象是单链表,因此只能向前迭代,但相对更小且更高效。
  • 与其他基本标准序列容器(array, vector and deque )相比,list在插入、提取和移动容器中任何位置的元素时表现通常更好,尤其是在已经获得迭代器的情况下,因此在频繁使用这些操作的算法(如排序算法)中也表现更佳。
  • list和单向链表相比其他序列容器的主要缺点 是它们无法通过位置直接访问元素;例如,要访问列表中的第六个元素,必须从已知位置(如开头或结尾)迭代到该位置,这需要线性时间。它们还会消耗一些额外的内存来保存与每个元素相关的链接信息

list 的详细介绍请参考:list

二、list的使用

2.1 constructor(构造函数)

构造函数 接口说明
list (size_type n, const value_type& val =value_type()) 构造的list中包含n个值为val的元素
list() 构造空的list
list (const list& x) 拷贝构造函数
list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造

注意: value_type表示第一个模板参数(T),size_type表示无符号整型。

2.2 Iterators

大家可暂时 将迭代器理解成一个指针,该指针指向list中的某个节点。

函数声明 接口说明
begin +end 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin+ rend 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置

注意:

  1. beginend正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)rend(begin)反向迭代器,对迭代器执行++操作,迭代器向前移动

📖示例:利用迭代器遍历链表

cpp 复制代码
void test_list1()
{
	//迭代器
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	//打印
	list<int>::iterator it = l1.begin();
	while (it != l1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//支持迭代器同样支持范围for
	for (auto ch : l1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

list的迭代器不支持+-,因为底层不是原生指针了,像stringvector的迭代器可以用原生指针实现,是因为其底层物理空间是连续的。

2.2.1 迭代器的分类:

  • 按功能分:
迭代器 说明
iterator 正向迭代器
reverse_iterator 反向迭代器
const_iterator const正向迭代器
coonst_reverse_iterator const反向迭代器
  • 按性质分:
迭代器 举例 支持功能
单向迭代器 forwad_list/unordered_map 只支持++
双向迭代器 list/map/set 支持++/--
随机迭代器 vector/string 支持++/--/+/-
input/output

说明:

这些迭代器都是包含的关系,单向是特殊的双向,双向是特殊的随机。

性质由容器的底层结构决定,底层结构又决定可以使用哪些算法(algorithm),比如 sort只能使用随机迭代器, reverse(逆置)支持双向迭代器随机迭代器也能用,find(查找)使用的是input迭代器

cpp 复制代码
void test_list2()
{
	//迭代器
	list<int> l1; 
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	//sort(l1.begin(),l1.end());    会报错
	string s1("asenncewoc");
    sort(s1.begin(), s1.end());
    cout << s1 << endl;
}

2.3 Capacity(容量相关)

函数声明 接口说明
empty 检测list是否为空,是返回true,否则返回false
size 返回list中有效节点的个数

list没有扩容。

2.4 Element access(元素访问)

函数声明 接口说明
front 返回list的第一个节点中值的引用
back 返回list的最后一个节点中值的引用

📖示例:

cpp 复制代码
void test_list3()
{
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	cout << l1.front() << endl;  //1
	cout << l1.back() << endl;   //4
}

2.5 Modifiers(链表修改)

函数声明 接口说明
push_front 在list首元素前插入值为val的元素
pop_front 删除list中第一个元素
push_back 在list尾部插入值为val的元素
emplace_back 构造并在末尾插入元素
pop_back 删除list中最后一个元素
insert 在list position 位置前插入值为val的元素
erase 删除list position位置的元素
swap 交换两个list中的元素
clear 清空list中的有效元素

说明:

insert 插入元素并不会导致迭代器失效 ,因为相较于 vector 中的 insert,list 中的 insert 并不会去扩容挪动数据,而 vector 中的 insert 可能会进行扩容挪动数据,最终导致迭代器失效。erase会导致迭代器失效,失效的只有指向被删除节点的迭代器,其他迭代器不会受到影响。

📖erase:

cpp 复制代码
void test_list4()
{
	//迭代器
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
    //删除下标为3位置处的元素
    //l1.erase(it+3); 这样list不支持了
    list<int>::iterator it = l1.begin();
    int k=3;
    while(k)
    {
      k--;
      it++;
    }
    l1.erase(it);
	for (auto ch:l1)
	{
		cout << ch << " ";
	}
	//运行结果:1 2 3
	cout << endl;
}

📖emplace_back和push_back的区别:

cpp 复制代码
void test_list5()
{
	//emplace_back相比于push_back更高效一点
	//emplace_back是模板的可变参数
	list<A> l2;
	A aa1(2,2);
	l2.push_back(aa1);
	l2.push_back(A(2,2));  //传入匿名对象
	l2.push_back(3,3);  //不支持两个参数,这是push_back与emplace_back的区别所在
	l2.emplace_back(aa1);
	l2.emplace_back(A(3,3));
	l2.emplace_back(4,4);  //支持
}

📖insert:

cpp 复制代码
void test_list7()
{
	
	//iterator insert (iterator position, const value_type& val);
	//如果想在下标为3前插入数据
	//不能像这样了,l1.insert(it+3,4);
	auto It = l1.begin();
	int k = 3;
	while (k)
	{
		It++;
		k--;
	}
	l1.insert(It, 5);
	for (auto ch : l1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

2.6 Operations(对链表的一些操作)

函数声明 接口说明
reverse 对链表进行逆置
sort 对链表中的元素进行排序
merge 对两个有序的链表进行合并,得到一个有序的链表
unique 对链表中的元素去重,前提链表必须有序
remove 删除具有特定值的节点
splice 将 A 链表中的节点转移到 B 链表,A链表中的节点会删除。

说明

链表逆置可以使用 list 自身的接口,也可以使用算法库中的 reverse,二者没有什么区别。链表排序只能使用 list 自身的 sort 接口(底层是利用归并排序),不能使用算法库的 sort,因为算法库中的 sort 底层是通过快排来实现的,而快排中会涉及到三数取中需要迭代器 - 迭代器,而list迭代器不支持。在Debug版本下,算法库中的sort不及list中的sort,但在release版本下,算法库中的sort速度远快于list中的sort。

所以当我们要对链表排序时,可以先将list中的数据拷贝到vector中,在vector中排序,排完序后再拷贝回list。

📖merge:

cpp 复制代码
void test_list8()
{
	//merge合并list
	list<double> first, second;
	first.push_back(3.1);
	first.push_back(2.2);
	first.push_back(2.9);
	second.push_back(3.7);
	second.push_back(7.1);
	second.push_back(1.4);
	first.sort();//默认排升序
	second.sort();
	first.merge(second);
	// (secon现在为空了)
	cout << "first contains:";
	for (list<double>::iterator it = first.begin(); it != first.end(); ++it)
		cout << ' ' << *it;
	cout << '\n';
}

📖 unique:

cpp 复制代码
void test_list9()
{
	//unique删除重复元素:要求数据必须有序
	//底层去重可以用双指针解决
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(4);
	l1.push_back(5);
	l1.push_back(9);
	l1.push_back(7);
	l1.sort();
	l1.unique();
	for (list<int>::iterator it = l1.begin(); it != l1.end(); ++it)
		cout << ' ' << *it;
	cout << '\n';

}

📖splice:

cpp 复制代码
void test_list10()
{
	//splice意思是剪接:Transfer elements from list to list
	list<int> mylist1, mylist2;
	list<int>::iterator it;

	//插入数据
	for (int i = 1; i <= 4; ++i)
		mylist1.push_back(i);      // mylist1: 1 2 3 4

	for (int i = 1; i <= 3; ++i)
		mylist2.push_back(i * 10);   // mylist2: 10 20 30

	it = mylist1.begin();
	++it;                        
	//将mylist2的所有节点转移到mylist1中it指向的节点后。
	mylist1.splice(it, mylist2); // mylist1: 1 10 20 30 2 3 4
								// mylist2 (empty)
								// "it" still points to 2 (the 5th element)
	int k = 0;
	cin >> k;
	list<int>::iterator lt = find(mylist1.begin(),mylist1.end(),k);
	//void splice (iterator position, list& x, iterator i);
	//仅将x指向i的元素传输到容器中。
	mylist1.splice(mylist1.begin(),mylist1,lt);
	for (list<int>::iterator it = mylist1.begin(); it != mylist1.end(); ++it)
		cout << ' ' << *it;
	cout << '\n';

	int c = 0;
	cin >> c;
	list<int>::iterator at = find(mylist1.begin(), mylist1.end(), c);
	//void splice (iterator position, list& x, iterator first, iterator last);
	//将范围[first,last]从x传输到容器中。
	mylist1.splice(mylist1.begin(), mylist1, at,mylist1.end());
	for (list<int>::iterator It = mylist1.begin(); It != mylist1.end(); ++It)
		cout << ' ' << *It;
	cout << '\n';
}

三、list的底层实现

✨ list的节点:ListNode

list底层就是一个双向链表,它是通过一个一个的节点连接而成的。每个节点是由一个结构体封装的,节点包括指针域,有前驱指针prev(指向前一个节点)和后驱指针next(指向后一个节点),以及数据域_data,存储数据。

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;
};

编译器默认生成的构造函数达不到我们的要求,需要自己实现。在这里并没有使用class定义节点类,因为节点是要经常访问的,而struct默认访问权限是public,class默认访问权限是private,所以使用struct定义更直观。

将这个类加上template<class T>后,就能够实现节点存储不同类型的数据,这也是C++模板的好处。

✨ list的成员变量:

设计思路:ListNode只是一个单独的节点,要想对一个链表操作,需要定义一个list类,对链表的各种操作,都在这个类里面实现。

cpp 复制代码
template<class T>
class list
{
public:
	typedef ListNode<T> Node;
private:
	Node* _head;
	size_t _size;
}

list的成员变量有一个指针_head,指向list的头节点,还有size,表示list存在几个有效的节点。

因为list中有指针_head,指向list的头节点,需要手动写构造函数,否则编译器默认生成的构造会将_head初始化为随机值,导致程序运行时出现未定义行为。

⭐ list 的迭代器:

设计思路 :list底层的物理空间不是连续的,用原生指针实现list迭代器就不行了,可以将迭代器封装成一个类,在类中重载++,- -,解引用*等。迭代器类中的成员变量只有结点类类型的指针 _node,因为迭代器的本质就是指针。

🌸list_iterator:

cpp 复制代码
template<class T>
struct list_iterator
{
	//结点类的类型取的别名是 Node
	typedef ListNode<T> Node;
	//为迭代器类取的别名是Self。
	typedef list_iterator<T> Self;
	//构造函数
	list_iterator(Node* node)
			:_node(node)
	{ 
		
	}
	//解引用希望得到的是Node中的data
	T& operator*()
	{
		return _node->_data;
	}
	//迭代器指向下一个节点
	//返回的是迭代器类型
	Self operator++()
	{
		_node=_node->_next;
		return *this;//解引用得到指向下一个节点的迭代器
	}
	Self operator--()
	{
		_node=_node->_prev;
		return *this;
	}
	//后置++,返回的是++之前的迭代器
	Self operator++(int)
	{
		Self tmp(*this);
		_node=_node->_next;
		return tmp;
	}
	Self operator--(int)
	{
		Self tmp(*this);
		_node=_node->_prev;
		return tmp;
	}
	T* operator->()
	{
		return &(_node->data);
	}
	bool operator!=(const Self& s) const
	{
		//比较迭代器指向的节点的地址是否一样
		return _node!=s._node;		
	}
	bool operator==(const Self& s) const
	{
		return _node==s._node;		
	}
	Node* _node;//指向节点的指针
};

注意 :这里的类名不能直接命名为iterator,因为每种容器的迭代器底层实现都有所不同,即可能会为每一种容器都单独实现一个迭代器类,如果都直接使用 iterator,会导致命名冲突,命名为list_iterator表示实现的是list的迭代器。

⭐list的iterator是否要实现拷贝构造、析构函数、赋值重载?

其中重载operator++(int)时,即后置++,会调用到iterator的拷贝构造,但是iterator并不用实现拷贝构造,编译器默认生成的浅拷贝即可 。函数结束后后调用编译器默认生成的析构函数为浅析构仅销毁 _node 指针本身 ,而不会释放_node指向的 Node 节点,这就不会有浅拷贝常见的问题:同一块空间被释放两次。tmp 是函数内的局部对象,存储在栈内存中,栈内存由编译器自动管理。

list_iterator不用自己实现拷贝构造和析构,这恰好符合迭代器的设计要求,迭代器的作用是指向节点、提供访问接口 ,而非直接对list中的节点操作,节点的创建和释放是由 list 容器负责。

🌸list_const_iterator:

cpp 复制代码
template<class T>
struct list_const_iterator
{
	typedef ListNode<T> Node;
	typedef list_const_iterator<T> Self;
	//解引用希望得到的是Node中的data
	const T& operator*()
	{
		return _node->_data;
	}
	//迭代器指向下一个节点
	//返回的是迭代器类型
	Self operator++()
	{
		_node=_node->_next;
		return *this;//解引用得到指向下一个节点的迭代器
	}
	Self operator--()
	{
		_node=_node->_prev;
		return *this;
	}
	//后置++,返回的是++之前的迭代器
	Self operator++(int)
	{
		Self tmp(*this);
		_node=_node->_next;
		return tmp;
	}
	Self operator--(int)
	{
		Self tmp(*this);
		_node=_node->_prev;
		return tmp;
	}
	const T* operator->()
	{
		return &(_node->data);
	}
	bool operator!=(const Self& s) const
	{
		//比较迭代器指向的节点的地址是否一样
		return _node!=s._node;		
	}
	bool operator==(const Self& s) const
	{
		return _node==s._node;		
	}
	Node* _node;//指向节点的指针
};

🌸关于const_iterator中间为什么要加_?

  • 第一个原因是:
        C++规定:const_iterator 表示 "常量迭代器"(只能读取元素,不能修改元素),与普通的 iterator(可读可写)形成对比。
  • 第二个原因是:避免与const iterator混淆。
    const_iterator:迭代器本身可以移动(++/-- --),但通过它访问的元素是 const 的(不能修改)。
    const iterator:迭代器本身是 const 的(不能移动),但通过它访问的元素可以修改(如果元素本身非 const)。

⭐迭代器的模板化复用:

上面实现的list_iteratorlist_const_iterator在很多地方存在冗余,只有operator和operator->的返回值类型不同,当是list_iterator类时,它们的返回值类型是T&T*,当是list_const_iterator类时,它们的返回值类型是const T&const T*。我们可以将operator 和operator->的返回值类型设置成迭代器类的模板参数,当需要的返回值类型是T&和T时,就传入模板参数T&和T

所以我们定义一个通用的迭代器模板类 ,通过模板参数 Ref(引用类型)Ptr(指针类型),统一 operator* 和 operator-> 的实现,同时适配普通迭代器和 const 迭代器。

cpp 复制代码
template<class T,class Ref,class Ptr>
struct _list_iterator
{
	typedef ListNode<T> Node;
	typedef _list_iterator<T,Ref,Ptr> Self;
	typedef Ref reference;
	typedef Ptr pointer;
	//构造函数
	_list_iterator(Node* node)
		:_node(node)
	{

	}
	reference operator*()
	{
		return _node->_data;
	}

	//重载++
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	
	Self operator++(int)
	{
		Self tmp(*this);
		_node = _node->_next;
		return tmp;
	}
	Self& operator--()
	{
		this->_node = _node->_prev;
		return *this;
	}
	//后置--
	Self operator--(int)
	{
		Self tmp(*this);
		this->_node = _node->_prev;
		return tmp;
	}

	//重载!=
	bool operator!=(const Self& s) const
	{
		return _node != s._node;
	}
	//重载->
	pointer operator->() 
	{
		//如果链表中的节点中的数据为自定义类型,需要对链表中的节点的数据解引用才能访问
		return &(_node->_data);
	}
	Node* _node;
};

✨ list的成员函数:

🌀list(构造):

完成对链表对象的初始化,即得到一个空的新链表。

cpp 复制代码
//默认构造
list()
{
	_head=new Node;//为头节点申请合法的空间,并构造一个节点
	_head->next=_head;
	_head->prev=_head;
}

new Node先调用operator new申请空间 ,再在申请的空间上调用Node的构造。

🌀迭代器相关:

cpp 复制代码
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
//可读可写
iterator begin() 
{
    //写法一:
	/*iterator it(_head->_next);
	return it;*/
	
    //写法二:
	//return it(_head->_next);
	
    //写法三:
	return _head->_next;//会进行隐式类型转换:单参数类型的构造函数支持隐式类型转换
}
iterator end()//最后一个数据的下一个位置,即哨兵位的头节点
{
	return _head;
}

//只读
const_iterator begin() const
{
	return _head->_next;
}
const_iterator end() const
{
	return _head;
}

🌀insert:

cpp 复制代码
iterator insert(iterator pos,const T& x)
{
	Node* newnode = new Node(x);
	//在it迭代器之前插入
	Node* pcur = pos._node;
	Node* prev = pcur->_prev;
	//prev newnode pcur
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = pcur;
	pcur->_prev = newnode;
	
	_size++;
	//C++标准规定:返回刚插入的元素的迭代器
	return newnode;
}

🌀push_back:

复用insert。

cpp 复制代码
void push_back(const T& x)
{
	insert(end(),x);
}

🌀push_front:

复用insert。

cpp 复制代码
void push_front(const T& x)
{
	insert(begin(), x);
}

🌀erase:

cpp 复制代码
iterator erase(iterator pos)
{
	assert(pos!=end());
	Node* pcur = pos._node;
	Node* next = pcur->_next;
	Node* prev = pcur->_prev;
	//pcur pos next
	prev->_next = next;
	next->_prev = prev;
	delete pcur;
	_size--;
	//返回下一个位置的迭代器,因为删除后那个位置的迭代器失效了。
	return next;
}

🌀pop_back:

复用erase。

cpp 复制代码
void pop_back()
{
	erase(--(this->end()));
}

🌀pop_front:

复用erase。

cpp 复制代码
void pop_front()
{
	erase(this->begin());
}

🌀empty:

cpp 复制代码
//判空
bool empty() const
{
	return _size == 0;
}

🌀size:

cpp 复制代码
size_t size() const
{
	return _size;
}

⭐clear:

通过遍历节点的方式,逐个释放节点,只保留头节点。

法一:依赖实现的erase,实现链表的清空。

cpp 复制代码
void clear()
{
	auto it = this->begin();
	while(it!=this->end())
	{
		it=erase(it);
	}
}

法二:不依赖实现的erase。

cpp 复制代码
//清空链表
void clear()
{
	if (_head->_next == _head) {
		return;
	}
	Node* pcur = _head->_next;
	while (pcur != _head)
	{
		Node* next = pcur->_next;//保存下一个节点
		delete pcur;
		pcur = next;
	}
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}

⭐~list(析构):

通过复用clear,将链表清空,然会释放头指针,最后将头指针置空。

cpp 复制代码
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

⭐list(拷贝构造-深拷贝):

拷贝构造是用一个已有对象去构造出另一个对象,首先将待构造对象进行初始化,然后通过复用push_back,将数据尾插到新链表,达到深拷贝的目的。

cpp 复制代码
void empty_list()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}
//拷贝构造(深拷贝)现代写法
list(list<T>& x)
{
	//创建一个新的空链表,只有头节点
	empty_list();
	for (iterator it = x.begin(); it != x.end(); it++)
	{
		push_back(*it);
	}
}

⭐operator=:

cpp 复制代码
		void swap(list<T>& t1, list<T>& t2)
		{
			std::swap(t1._head, t2._head);
			std::swap(t1._size, t2._size);
		}
		//重载赋值
		list<T>& operator=(list<T> t)
		{
			swap(*this, t);
			return *this;
		}

四、list 容器的模拟实现整体代码

🌈list.h:

cpp 复制代码
#pragma once
#include<iostream>
#include<assert.h>
using std::cout;
using std::cin;
using std::endl;
using std::ostream;
using std::istream;
namespace hwy_list
{
	template<class T>
	struct ListNode
	{
		//构造函数
		ListNode(const T& val = T())// 带默认参数的构造函数
			//若 T 是内置类型(如 int、double):T() 会执行 "零初始化",因此 _data 被初始化为 0(int)、0.0(double)等。
			//若 T 是指针类型(如 int*):T() 会初始化为 nullptr,因此 _data(指针)被初始化为空指针。
			//若 T 是自定义类型(如 string、AA):T() 会调用 T 的默认构造函数,_data 的值由 T 的默认构造函数决定。
			:_data(val),
			_prev(nullptr),
			_next(nullptr)

		{

		}
		T _data;
		ListNode* _prev;
		ListNode* _next;
	};

	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		typedef ListNode<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self;
		typedef Ref reference;
		typedef Ptr ptrainer;
		//构造函数,指向一个节点
		list_iterator(Node* node)
			:_node(node)
		{

		}
		//重载*
		reference operator*()
		{
			return _node->_data;
		}
		Self& operator++()
		{
			//获取下一个节点的地址
			Self tmp(*this);//会调用拷贝构造,是浅拷贝
			_node = _node->_next;
			return *this;
		}

		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//后置--
		Self operator--(int)
		{
			//返回修改以前的迭代器
			Self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
		//后置++
		Self operator++(int)
		{
			//返回之前的迭代器
			Self tmp(*this);
			_node = _node->_next;
			return tmp;//返回拷贝构造的临时对象(浅拷贝)
		}
		bool operator!= (const Self& s)
		{

			return _node != s._node;
		}
		//返回迭代器指向的节点中的_data的地址
		ptrainer operator->()
		{
			return &(_node->_data);
		}
		Node* _node;//_node是一个指针,指向一个节点
	};
	//创建链表管理类
	template<class T>
	class list
	{
	public:
		typedef ListNode<T> Node;//1.先调用operator new申请空间  2.在在申请的空间上执行构造
		//构造函数
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;

		void empty_list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		list()
		{
			empty_list();
		}
		//尾插,即最后一个节点后插入
		void push_back(const T& x)
		{
			insert(end(), x);
		}
		//在pos位置前面插入节点
		iterator insert(iterator pos, const T& x)
		{
			Node* newnode = new Node(x);
			Node* pcur = pos._node;
			Node* prev = pcur->_prev;
			//prev newnode next
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = pcur;
			pcur->_prev = newnode;
			_size++;

			return newnode;
		}
		//删除pos位的节点,该节点就失效了
		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* pcur = pos._node;
			Node* next = pcur->_next;
			Node* prev = pcur->_prev;
			//prev newnode next
			prev->_next = next;
			next->_prev = prev;
			delete pcur;
			_size--;
			return next;//隐式类型转换
		}
		//头删
		void pop_back()
		{
			erase(begin());
		}
		//头插
		void push_front(const T& x)
		{
			insert(begin(),x);
		}
		void pop_front()
		{
			erase(--(end()));
		}
		//普通迭代器
		iterator begin()
		{
			iterator it(_head->_next);//传入一个节点的地址
			return it;
			//return _head->_next;//会进行隐式类型转换
		}
		iterator end()//最后一个数据的下一个位置,即哨兵位的头节点
		{
			return _head;
		}
		//const迭代器
		const_iterator begin() const
		{
			const_iterator it(_head->_next);//传入一个节点的地址
			return it;
			//return _head->_next;//会进行隐式类型转换
		}
		const_iterator end() const//最后一个数据的下一个位置,即哨兵位的头节点
		{
			return _head;
		}

		size_t size() const
		{
			return _size;
		}

		bool empty()
		{
			return _size == 0;
		}

		//拷贝构造(深拷贝)
		list(list<T>& x)
		{
			//创建一个新的空链表,只有头节点
			empty_list();
			for (iterator it = x.begin(); it != x.end(); it++)
			{
				push_back(*it);
			}
		}
		void swap(list<T>& t1, list<T>& t2)
		{
			std::swap(t1._head, t2._head);
			std::swap(t1._size, t2._size);
		}
		//重载赋值
		list<T>& operator=(list<T> t)
		{
			swap(*this, t);
			return *this;
		}
		//清空链表
		void clear()
		{
			if (_head->_next == _head) {
				return;
			}
			Node* pcur = _head->_next;
			while (pcur != _head)
			{
				Node* next = pcur->_next;//保存下一个节点
				delete pcur;
				pcur = next;
			}
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		//析构
		~list()
		{
			//delete只能作用于指针
			clear();
			delete _head;
			_head = nullptr;
		}
	private:
		Node* _head;
		size_t _size;
	};
	template<class Container>
	void print_container(const Container& v)
	{
		//传入的是const迭代器,需要调用const迭代器。
		//所以需要实现const迭代器
		typename Container::const_iterator lt = v.begin();
		//auto lt = v.begin();
		while (lt != v.end())
		{
			//const 迭代器不能修改
			//*lt += 20;
			cout << *lt << endl; //这里的->返回的是指针类型呀,cout并没有指针类型的<<重载
			lt++;
		}
		cout << endl;
		/*for (auto& ch:v)
		{
			cout << ch << " ";
		}
		cout << endl;*/
	}
}

🌈测试代码:

test.cpp:

cpp 复制代码
#include"list.h"
namespace hwy_list
{
	void test01()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		auto it = l1.begin();
		int k;
	    cin >> k;
		while (k)
		{
			k--;
			++it;
		}
		l1.insert(it, 9);

		list<int>::iterator lt = l1.begin();
		while (lt != l1.end())
		{
			
			cout << " " << *lt;
			lt++;
		}
		cout << endl;
	}
	void test02()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		auto it = l1.begin();
		int k;
		cin >> k;
		while (k)
		{
			k--;
			++it;
		}
		l1.erase(it);
		for (auto ch : l1)
		{
			cout << " " << ch;
		}
		cout << endl;
	}

	void test03()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		l1.push_back(99);
		l1.push_front(66);
		for (auto ch : l1)
		{
			cout << " " << ch;
		}
		cout << endl;
		l1.pop_back();
		l1.pop_front();
		for (auto ch : l1)
		{
			cout << " " << ch;
		}
		cout << endl;
		cout << l1.size() << endl;
	}
	void test04()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		l1.push_back(99);
		l1.push_front(66);
		print_container(l1);
		int c = l1.size();
		cout << c << endl;
		cout << l1.empty() << endl;
	}
	struct AA
	{
		AA(int a1 = 0, int a2 = 0)
			:_a1(a1),
			_a2(a2)
		{

		}
		int _a1;
		int _a2;
	};
	template<class T>
	ostream& operator<<(ostream& os, const T& obj)
	{
		os << obj._a1 << obj._a2;  // 自定义输出格式
		return os;
	}
	void test05()
	{
		list<AA> l1;
		l1.push_back(AA());
		l1.push_back(AA());
		l1.push_back(AA());
		l1.push_back(AA());
		auto it = l1.begin();
		while (it != l1.end())
		{
			//cout << (*it)._a1 << ':' <<(*it)._a2<<endl ;
			//cout << it.operator->()->_a1 << ':' << it.operator->()->_a2 << endl; 原本应该有两个->
			//简写

			cout << *it << endl;	//这里就是对的		
			it++;
		}
		print_container(l1);//有问题
	}

	//迭代器失效问题
	void test06()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		l1.push_back(4);
		l1.push_back(99);
		l1.push_front(66);

		//插入不会造成迭代器失效
		//删除节点会,删除后就是野指针了

		l1.clear();
		print_container(l1);
	}
	void test07()
	{
		list<int> l2;
		l2.push_back(1);
		l2.push_back(2);
		l2.push_back(3);
		l2.push_back(4);
		l2.push_back(5);
		list<int> l1(l2);
		print_container(l1); //这个里边对象没有_a1和_a2
		print_container(l2);
	}
	void test08()
	{
		list<int> l2, l1;
		l2.push_back(1);
		l2.push_back(2);
		l2.push_back(3);
		l2.push_back(4);
		l2.push_back(5);
		l1 = l2;
		print_container(l2);
		print_container(l1);
	}
}
int main()
{
	
	hwy_list::test01();
	hwy_list::test02();
	hwy_list::test03();
	hwy_list::test04();
	hwy_list::test05();
	hwy_list::test06();
	hwy_list::test07();
	hwy_list::test08();
	return 0;
}

🎁结语:

今天的分享就到这里,感谢各位大佬的关注,还请大家多多支持哦!

相关推荐
Bug退退退1232 小时前
ArrayList 与 LinkedList 的区别
java·数据结构·算法
再睡一夏就好2 小时前
【C++闯关笔记】unordered_map与unordered_set的底层:哈希表(哈希桶)
开发语言·c++·笔记·学习·哈希算法·散列表
mjhcsp2 小时前
C++ 贪心算法(Greedy Algorithm)详解:从思想到实战
c++·ios·贪心算法
potato_15542 小时前
现代C++核心特性——内存篇
开发语言·c++·学习
沐怡旸3 小时前
【穿越Effective C++】条款13:以对象管理资源——RAII原则的基石
c++·面试
一个不知名程序员www3 小时前
算法学习入门---二分查找(C++)
c++·算法
2301_807997384 小时前
代码随想录-day26
数据结构·c++·算法·leetcode
闭着眼睛学算法4 小时前
【双机位A卷】华为OD笔试之【排序】双机位A-银行插队【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解
java·c语言·javascript·c++·python·算法·华为od
TL滕4 小时前
从0开始学算法——第一天(认识算法)
数据结构·笔记·学习·算法