C++中list类的使用及模拟实现

news2024/12/25 13:13:33

目录

1.C++中list的底层结构

2.C++中list容器各个接口函数的使用

3.迭代器的分类

3.1从功能上进行分类 

3.2从性质上进行分类

4.list的结构

5.list的模拟实现 

5.1默认成员函数(Member functions)

5.1.1构造函数(constructor)

5.1.1.1默认构造函数

5.1.1.2 initializer list构造函数

5.1.1.3拷贝构造(copy)

5.1.2析构函数(destructor) 

5.1.3 赋值运算符重载(operator=)

5.2list的迭代器(Iterator) 

5.2.1迭代器类模板及类里面的成员变量

5.2.2迭代器类构造函数 

5.2.3解引用重载

5.2.3.1 '*' 的重载

5.2.3.2 ‘->’的重载 

5.2.4迭代器++/--的实现

5.2.5迭代器的比较运算符

5.2.6 begin()和end()

5.3list的空间操作(Capacity)

5.3.1empty()

5.3.2size() 

5.4list的修改操作(Modifiers)

5.4.1swap()

5.4.2clear()

5.4.3push_back()

5.4.4push_front()

5.4.5insert()

5.4.6pop_back()

5.4.7pop_front()

5.4.8erase()

6.参考代码 

6.1 list.h


1.C++中list的底层结构

        C++中的list容器底层是一个带头双向循环链表。具体结构参考C语言实现双向链表中双向链表的结构。

2.C++中list容器各个接口函数的使用

        由于在C++的STL中各容器都封装了比较相似的接口,使用的方式也比较相似,所有这里就不一一介绍了,可以参考C++中string类的使用和C++中vector类的使用,下面直接给的是各个接口的测试代码,感兴趣可以自行进行测试。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <list>
#include <vector>
#include <time.h>
#include <algorithm>
using namespace std;

//Member functions
void test_list1()
{
	//1.constructor
	//(1)default
	list<int> l1;
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;

	//(2)fill
	list<int>l2(10);
	for (auto& e : l2)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int>l3(10, 1);
	for (auto& e : l3)
	{
		cout << e << " ";
	}
	cout << endl;

	//(3)range
	list<int>l4(10, 2);
	list<int>l5(++l4.begin(), --l4.end());
	for (auto& e : l5)
	{
		cout << e << " ";
	}
	cout << endl;

	//(4)copy
	list<int>l6(10, 3);
	list<int>l7(l6);
	for (auto& e : l7)
	{
		cout << e << " ";
	}
	cout << endl;

	//(5)initializer list
	list<int>l8({ 1,2,3,4,5,6 });
	list<int>l9 = { 7,8,9,10 };
	for (auto& e : l8)
	{
		cout << e << " ";
	}
	cout << endl;	

	for (auto& e : l9)
	{
		cout << e << " ";
	}
	cout << endl;

	//2.operator=
	//(1)copy
	list<int> l10 = {1,2,3,4,5,6,7,8,9,10};
	list<int> l11;
	l11 = l10;
	for (auto& e : l11)
	{
		cout << e << " ";
	}
	cout << endl;

	//(2)initializer list
	list<int> l12;
	l12 = { 10, 20, 30, 40 };
	for (auto& e : l12)
	{
		cout << e << " ";
	}
	cout << endl;
}

//Iterators
void test_list2()
{
	//1.begin() and end()
	list<int> l1 = { 1,2,4,5,6,7 };
	list<int>::iterator it1 = l1.begin();
	while (it1 != l1.end())
	{
		cout << *it1 << " ";
		it1++;
	}
	cout << endl;

	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int>::const_iterator it2 = ++l1.begin();
	while (it2 != l1.end())
	{
		cout << *it2 << " ";
		it2++;
	}
	cout << endl;

	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;

	//2. rbegin() and rend()
	list<int> l2 = { 10, 20, 30, 40, 50 };
	list<int>::reverse_iterator rit = l2.rbegin();
	while (rit != l2.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;

	//3.cbegin() and cend()
	list<int> l3 = { 1,2,4,5,6,7 };
	list<int>::const_iterator cit = ++l3.cbegin();
	while (cit != l3.cend())
	{
		cout << *cit << " ";
		cit++;
	}
	cout << endl;

	for (auto& e : l3)
	{
		cout << e << " ";
	}
	cout << endl;

	//4.crbegin() and crend() 略
}

//Capacity
void test_list3()
{
	//1.empty()
	list<int> l1;
	list<int> l2 = {1,2,3};
	cout << l1.empty() << endl;
	cout << l2.empty() << endl;

	//2.size()
	list<int> l3;
	list<int> l4 = { 1,2,3,4,5,6 };
	cout << l3.size() << endl;
	cout << l4.size() << endl;

	//3.max_size()--返回列表容器可以容纳的最大元素数。
}

//Element access
void test_list4()
{
	list<int> l1 = { 10,2,3,4,5,6 };
	const list<int> l2(10, 1);

	//1.front--返回链表首元素的引用
	cout << l1.front() << endl;
	cout << l2.front() << endl;

	//2.back--返回链表最后一个元素的引用
	cout << l1.back() << endl;
	cout << l2.back() << endl;
}

class A
{
public:
	A(int a1 = 1, int a2 = 1)
		:_a1(a1)
		,_a2(a2)
	{}
private:
	int _a1;
	int _a2;
};

//Modifiers
void test_list5()
{
	//1.assign--Assigns new contents to the list container, replacing its current contents, and modifying its size accordingly.
	//(1)range
	list<int> l1 = { 1,2,3,4,5 };
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << l1.size() << endl;

	l1.assign(++l1.begin(), --l1.end());
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << l1.size() << endl;

	//(2)fill
	l1.assign(7, 100);
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << l1.size() << endl;

	//(3)initializer list
	l1.assign({ 5,5,5,5,6,6,6,6 });
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << l1.size() << endl;

	//2.emplace_front() and push_front()
	list<int> l2(10, 1);
	l2.emplace_front(2);
	for (auto& e : l2)
	{
		cout << e << " ";
	}
	cout << endl;
	l2.push_front(3);
	for (auto& e : l2)
	{
		cout << e << " ";
	}
	cout << endl;

	//3.emplace_back() and push_back()
	list<int> l3(10, 2);
	l3.emplace_back(3);
	for (auto& e : l3)
	{
		cout << e << " ";
	}
	cout << endl;
	l3.push_back(4);
	for (auto& e : l3)
	{
		cout << e << " ";
	}
	cout << endl;

	//emplace_back()和push_back()的区别
	//如果链表中存储的数据是多参数的,则emplace_back中的形参可以写成多参数
	list<A> lA;
	A aa1 = { 1, 1 };
	lA.push_back(aa1);	//传对象
	lA.push_back(A(2, 2));	//匿名构造
	//lA.push_back(2, 2)	//push_back()不支持

	lA.emplace_back(2, 2);	//支持直接传构造A对象的参数


	//4.pop_front() and pop_back()
	list<int> l4 = { 1,2,3,4 };
	for (auto& e : l4)
	{
		cout << e << " ";
	}
	cout << endl;

	l4.pop_front();
	for (auto& e : l4)
	{
		cout << e << " ";
	}
	cout << endl;

	l4.pop_back();
	for (auto& e : l4)
	{
		cout << e << " ";
	}
	cout << endl;

	//5.emplace()
	list<int> l5(10, 0);
	l5.emplace(l5.begin(), 1);
	l5.emplace(l5.end(), 2);
	for (auto& e : l5)
	{
		cout << e << " ";
	}
	cout << endl;

	//6.insert()
	//(1)single element 和 emplace相同
	
	//(2)fill
	list<int> l6(5, 0);
	l6.insert(++l6.begin(), 7, 1);
	for (auto& e : l6)
	{
		cout << e << " ";
	}
	cout << endl;

	//(3)range
	list<int> l7(10, 5);
	l7.insert(--l7.end(), l6.begin(), l6.end());
	for (auto& e : l7)
	{
		cout << e << " ";
	}
	cout << endl;

	//(4)initializer list
	list<int> l8(10, 3);
	l8.insert(++l8.begin(), { 4,4,4,4 });
	for (auto& e : l8)
	{
		cout << e << " ";
	}
	cout << endl;

	//7.erase()
	list<int> l9({1,2,3,4,5});
	auto it = l9.begin();
	++it;
	++it;
	l9.erase(it);
	for (auto e : l9)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int> l10 = { 1,2,3,4,5 };
	l10.erase(++l10.begin(), --l10.end());
	for (auto e : l10)
	{
		cout << e << " ";
	}
	cout << endl;

	//8.swap()
	list<int> l11 = { 1,2,3 };
	list<int> l12 = { 4,5,6 };
	for (auto e : l11)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto e : l12)
	{
		cout << e << " ";
	}
	cout << endl;
	swap(l11, l12);
	for (auto e : l11)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto e : l12)
	{
		cout << e << " ";
	}
	cout << endl;

	//9.resize()
	list<int> l13(10, 1);
	cout << l13.size() << endl;

	l13.resize(15, 2);	//没有填入参数2,则用0填补
	for (auto e : l13)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << l13.size() << endl;

	l13.resize(5);
	for (auto e : l13)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << l13.size() << endl;

	//10.clear()
	list<int> l14(10, 1);
	cout << l14.size() << endl;
	l14.clear();
	cout << l14.size() << endl;
}

bool single_digit(const int& value)
{
	return (value < 10);
}
//Operations
void test_list6()
{
	//1.sort()--默认排升序
	//降序 - 仿函数
	greater<int> gt;
	list<int> l1 = { 3,612,7,12,68,43,7,345,234 };
	l1.sort();
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	l1.sort(gt);
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;

	list<char> l2 = {'x', 's', 's', 's', 'w', 'a'};
	l2.sort();
	for (auto& e : l2)
	{
		cout << e << " ";
	}
	cout << endl;

	//2.reverse()
	list<int> l3 = { 1,2,3,4,5,6,7,8 };
	l3.reverse();
	for (auto& e : l3)
	{
		cout << e << " ";
	}
	cout << endl;

	//3.unique--去重--条件:链表有序
	list<int> l4 = { 2,3,4,5,2,5,1,6 };
	for (auto& e : l4)
	{
		cout << e << " ";
	}
	cout << endl;
	l4.unique();
	for (auto& e : l4)
	{
		cout << e << " ";
	}
	cout << endl;
	l4.sort();
	l4.unique();
	for (auto& e : l4)
	{
		cout << e << " ";
	}
	cout << endl;

	//4.merge--合并两个链表(按大小顺序合并)--被合并的链表变为空
	list<int> l5 = { 1,2,3,4,5 };
	list<int> l6 = { 1,2,3,4,5 };
	l5.merge(l6);
	for (auto& e : l6)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto& e : l5)
	{
		cout << e << " ";
	}
	cout << endl;

	//5.remove--移除一个元素
	list<int> l7 = { 1,2,3,4,5,6 };
	l7.remove(4);
	for (auto& e : l7)
	{
		cout << e << " ";
	}
	cout << endl;

	//6.remove_if--条件移除,满足传入的条件即移除
	list<int> l8 = { 1,2,3,4,5,6,7,8,10,12 };

	l8.remove_if(single_digit);
	for (auto& e : l8)
	{
		cout << e << " ";
	}
	cout << endl;

	//6.splice--粘接--被粘接的元素转移到待粘接的链表中--可以在链表自己中进行转移
	//(1)entire list
	list<int> l9 = {1,2,3,4,5};
	list<int> l10 = { 6,7,8,9,10 };
	l9.splice(l9.end(),l10);
	for (auto& e : l10)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto& e : l9)
	{
		cout << e << " ";
	}
	cout << endl;

	//(2)single element
	list<int> l11 = { 1,2,3,4,5 };
	list<int> l12 = { 1,2,3,4,5 };
	l11.splice(l11.end(), l12, l12.begin());
	for (auto& e : l11)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto& e : l12)
	{
		cout << e << " ";
	}
	cout << endl;

	//(3)element range
	list<int> l13 = { 1,2,3,4,5 };
	list<int> l14 = { 1,2,3,4,5 };
	l13.splice(l13.end(), l14, ++l14.begin(), --l14.end());
	for (auto& e : l13)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto& e : l14)
	{
		cout << e << " ";
	}
	cout << endl;
}

//用算法库中的sort进行排序与list中的排序进行对比
void test_list7()
{
	srand(time(0));
	const int N = 1000000;

	list<int> lt1;
	vector<int> v;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		lt1.push_back(e);
		v.push_back(e);
	}

	int begin1 = clock();
	// 排序
	sort(v.begin(), v.end());
	int end1 = clock();

	int begin2 = clock();
	lt1.sort();
	int end2 = clock();

	printf("vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);

}

void test_list8()
{
	srand(time(0));
	const int N = 1000000;

	list<int> lt1;
	list<int> lt2;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		lt1.push_back(e);
		lt2.push_back(e);
	}

	int begin1 = clock();
	// 拷贝vector
	vector<int> v(lt2.begin(), lt2.end());

	// 排序
	sort(v.begin(), v.end());

	// 拷贝回lt2
	lt2.assign(v.begin(), v.end());

	int end1 = clock();

	int begin2 = clock();
	lt1.sort();
	int end2 = clock();

	printf("list copy vector sort copy list sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}
int main()
{
	//test_list1();
	//test_list2();
	//test_list3();
	//test_list4();
	//test_list5();
	//test_list6();
	//test_list7();
	test_list8();


	return 0;
}

3.迭代器的分类

3.1从功能上进行分类 

        (1)iterator -- 普通迭代器

        (2)reverse_iterator -- 反向迭代器

        (3)const_iterator -- const迭代器

        (4)const_reverse_iterator -- const反向迭代器

3.2从性质上进行分类

        迭代器从性质上进行分类是由于对应底层的数据结构进行分类的,例如像vector/string这样的容器,底层都是通过数组进行数据的存储,所有它们的迭代器支持随机访问。像list这样的迭代器,底层存储数据的结构是一个一个的节点,并不是一块连续的空间,所有它们不支持随机的空间访问,但是由于list是双向链表,所有它支持++,--进行访问。像forward_list这种单链表的结构,只能支持++并且不支持随机的数据访问。

        所以由于底层存储数据的结构不同,从性质上对迭代器进行分类分为下面几类:

        (1)单向迭代器(forward iterator):仅仅支持++操作

        例如:forward_list/unordered_map/unordered_set

        (2)双向迭代器(bidirectional iterator):支持++/--

        例如:list/map/set

        (3)随机迭代器(random access iterator):支持++/--/+/-

        例如:vector/string/deque

        注:上述三种迭代器是一种包含关系,双向迭代器是一种特殊的单向迭代器,随机迭代器是一种特殊的双向迭代器或者是一种特殊的单向迭代器。

4.list的结构

        链表结构中有一个头节点_head作为双向链表的哨兵位头节点。有一个_size记录链表中节点的个数。

template <class T>
//节点类--节点的结构
struct list_node
{
	T _data;
	list_node<T>* _next;
	list_node<T>* _prev;

	//构造函数
	list_node(const T& data = T())
		:_data(data)
		,_next(nullptr)
		,_prev(nullptr)
	{}
};

template <class T>
//链表类--链表的结构
class list
{
    typedef list_node<T> Node;
public:
    //...
private:
    Node* _head;
    size_t _size;
}

5.list的模拟实现 

        这里声明一下,下列实现的所以接口函数都写在list.h里面,其中进行解释的例子放在test.cpp中,因为接口函数现在list.h中的XiaoC这命名空间中,所以测试的函数在test.cpp中也写在XiaoC这个命名空间中,这样测试函数在进行调用list的接口函数时就不用写命名空间域了。

5.1默认成员函数(Member functions)

5.1.1构造函数(constructor)

5.1.1.1默认构造函数

        listn类实例化对象时,里面会有一个哨兵位的头节点,这里写一个创建初始化头节点的函数,用于后面list的构造。

		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

         默认构造函数即创建一个链表里面只有一个哨兵位头节点

		list()
		{
			empty_init();
		}
 5.1.1.2 initializer list构造函数

        该构造函数即为传入一个initializer_list<T>对象来构造出list,这里的initializer_list<T>对象可以类似理解为数组对象(比如 {1,2,3,4} )。这里调用的push_back()函数在后面进行实现。

		list(initializer_list<T> il)
		{
			empty_init();
			for (auto& e : il)
			{
				push_back(e);
			}
		}
5.1.1.3拷贝构造(copy)
		list(const list<T>& lt)
		{
			empty_init();
			//如果只是不加上上一句代码,这个地方生成的this没有头节点
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

5.1.2析构函数(destructor) 

        调用后面实现的clear()函数接口,先对链表中除了头节点的其他节点进行清空,然后再释放头节点,最有将_size置为0。

		~list()
		{
			//先清空链表中的所有节点
			clear();
			//释放头节点
			delete _head;
			_head = nullptr;
			_size = 0;
		}

5.1.3 赋值运算符重载(operator=)

        1.传统写法 -- 先清空原来的链表,然后再依次遍历进行尾插。


		list<T>& operator=(const list<T>& lt)
		{
			clear();
			for (auto& e : lt)
			{
				push_back(e);
			}

			return *this;
		}

        2.现代写法 -- 因为形参的改变不影响实参,所以通过传值传参传入一个list<T>对象,然后进行交换,这样不改变传入的对象,也进行了赋值运算。swap()函数在下面进行实现。

		list<T>& operator=(list<T> lt)
		{
			swap(lt);

			return *this;
		}

5.2list的迭代器(Iterator) 

         list的迭代器不同于之前vector和string迭代器,之前vector和string的迭代器都是使用原始指针实现的,迭代器就是指向存储元素的地址,而list的迭代器是指向节点的指针。并且为了实现普通迭代器和const迭代器,list迭代器的实现通过一个类模板进行封装。不然普通迭代器和const迭代器要分别写成两个相似的类,会造成代码的冗余。

//迭代器类 -- 迭代器是节点的指针
	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		//类里面重命名的两个类型
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self;

		//类里面的成员变量
		Node* _node;

		//构造函数+初始化列表
		list_iterator(Node* node)
			:_node(node)
		{}

		//解引用运算符重载(如果是原始指针作为迭代器的话,解引用是可以直接拿到其对应的数据的,但是这里的迭代器是一个节点指针,如果要拿到对应节点内部所存储的data需要进行重载)
		Ref operator*()
		{
			return _node->_data;
		}

		//迭代器模拟的是指针的行为
		//_node->_data是节点里面的数据,这里返回的是节点的指针,需要加一个取地址符号
		Ptr 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;
		}

		//迭代器进行遍历等操作需要比较运算符!=
		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
			return _node == s._node;
		}
	};

5.2.1迭代器类模板及类里面的成员变量

template<class T, class Ref, class Ptr>

        该模板的第一个类型参数T为存入节点中的数据类型,第二个类型参数是存入节点中数据的类型的引用,如果传入的是T&,即为普通引用,如果传入的是const T&则为const引用。第三个参数是存入节点中数据的指针,如果传入T*即为普通指针,传入const T*即为const指针。所以在list类模板中定义普通迭代器和const迭代器可以这么写:

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

        迭代器类模板中的成员变量就是一个节点的指针,只不过这里定义成了公有变量:

		Node* _node;

5.2.2迭代器类构造函数 

        传入一个节点的指针进行构造。

		list_iterator(Node* node)
			:_node(node)
		{}

5.2.3解引用重载

5.2.3.1 '*' 的重载

        之前的vector和string迭代器是用原生指针进行模拟的,所以它们的迭代器直接进行解引用就可以得到存储的数据。但是list迭代器是指向节点的指针,直接解引用得到的是节点,所以这里需要进行解引用运算符的重载,使对迭代器解引用之后得到的是节点中存储的数据。

		Ref operator*()
		{
			return _node->_data;
		}
5.2.3.2 ‘->’的重载 

        当list中存储的是自定义对象时,这里我们要访问自定义对象中的成员变量时需要通过重载'->'来进行提取。

		Ptr operator->()
		{
			return &_node->_data;
		}

        这里重载‘->’返回的存储数据的地址,并不是返回其中的值,这里是一个比较奇怪的地方,通过下列的例子来进行解释,解释的内容放在代码的注释当中了.

#include <iostream>
#include "list.h"
using namespace std;

namespace XiaoC
{
    struct AA
	{
		int _a1 = 1;
		int _a2 = 1;
	};

    void test_list()
    {
    list<AA> lta;
	lta.push_back(AA());
	lta.push_back(AA());
	lta.push_back(AA());
	lta.push_back(AA());

	list<AA>::iterator ita = lta.begin();
    while (ita != lta.end())
	{
		//这里用*解引用,返回的是AA类对象
		//cout << (*ita)._a1 << ":" << (*ita)._a2 << endl;

		cout << ita->_a1 << ":" << ita->_a2 << endl;
		//下面是上面行代码的原型,先是调用operator->()返回一个AA*的对象,在用->解引用
		//特殊处理:为了可读性省略了一个箭头
		//cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;
		ita++;
	}

	cout << endl;
    }
};

int main()
{
    XiaoC::test_list();
    return 0;
}

5.2.4迭代器++/--的实现

         这里主要和vector/string迭代器实现的区别是,list实现++/--就是把指向当前节点的指针移到指向后一个/前一个节点并返回迭代器对象.

//迭代器前置++,返回值还是迭代器,是下一个位置的迭代器
		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;
		}

 5.2.5迭代器的比较运算符

        这里使用迭代器进行比较大多是为了实现遍历操作,所以这里仅实现了operator!=()和operator==().通过比较节点的指针是否相同进行遍历.

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
			return _node == s._node;
		}

 5.2.6 begin()和end()

        下列代码是在list类模板中对迭代器的使用,这里需要强调的一点是,在begin()函数中,直接返回_head->_next是进行了一个隐式类型转换,因为C++中单参数的类模板支持隐式类型转换.

    //用一个模板实现普通迭代器和const迭代器,在这里写的时候写成如下
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;

		//2.1 begin()
		iterator begin()
		{
			//begin()指向的是链表中第一个有效的数据,即为头节点的下一个节点
			//iterator it(_head->_next);
			//return it;

			//匿名对象
			//return iterator(_head->_next);

			//隐式类型转换 -- _head->_next是Node*类型,迭代器iterator是list_iterator<T, T&, T*>类型,因为迭代器类里面只有一个Node*类型的成员变量,所有这里可以进行隐式类型转换
			return _head->_next;
		}

		const_iterator begin() const
		{
			return _head->_next;
		}

		//2.2 end()
		//end是最后一个数据的下一个位置 -- 哨兵位
		iterator end()
		{
			return _head;
		}

		const_iterator end() const
		{
			return _head;
		}

5.3list的空间操作(Capacity)

5.3.1empty()

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

5.3.2size() 

		size_t size() const
		{
			return _size;
		}

5.4list的修改操作(Modifiers)

        在Modifiers中push_back(),push_front(),pop_back(),pop_front()的传统写法以及insert(),erase()的写法仅仅是涉及到指针指向改变的问题,下列就不做过多的解释.

 5.4.1swap()

		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

5.4.2clear()

        这里调用的erase()函数接口,在删除之后返回指向被删除元素的下一个元素的迭代器,所以要通过一个迭代器对象进行接收来进行迭代器的更新.如果不接收,原来指向被删除元素节点的迭代器对象it,当节点被删除后,变成了一个野指针,会导致迭代器失效的问题.

		void clear()
		{
			auto it = begin();
			while(it != end())
			{
				it = erase(it);
			}
		}

5.4.3push_back()

		void push_back(const T& x)
		{
			//传统写法
			Node* newnode = new Node(x);
			newnode->_next = _head;
			newnode->_prev = _head->_prev;
			_head->_prev->_next = newnode;
			_head->_prev = newnode;

			++_size;
			
			//复用insert()
			//insert(end(), x);
		}

5.4.4push_front()

		//4.4 push_front()
		void push_front(const T& x)
		{
			//传统写法
			Node* newnode = new Node(x);
			newnode->_next = _head->_next;
			newnode->_prev = _head;
			_head->_next->_prev = newnode;
			_head->_next = newnode;
			++_size;

			//复用insert()
			//insert(begin(), x);
		}

5.4.5insert()

		iterator insert(iterator pos, const T& x)
		{
			//在pos位置之前插入数据
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			Node* newnode = new Node(x);
			newnode->_next = cur;
			newnode->_prev = prev;
			cur->_prev = newnode;
			prev->_next = newnode;

			++_size;

			return newnode;	//隐式转换
		}

5.4.6pop_back()

		void pop_back()
		{
			//删除尾节点
			//传统写法
			Node* tail = _head->_prev;
			Node* prev = tail->_prev;
			prev->_next = _head;
			_head->_prev = prev;

			delete tail;
			tail = nullptr;

			//erase(--end());
		}

5.4.7pop_front()

		void pop_front()
		{
			//删除头节点
			//传统写法
			Node* cur = _head->_next;
			_head->_next = cur->_next;
			cur->_next->_prev = _head;

			delete cur;
			cur = nullptr;

			//erase(begin());
		}

5.4.8erase()

		//删除pos位置的数据并返回下一个位置的迭代器
		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

			prev->_next = next;
			next->_prev = prev;
			delete pos._node;

			--_size;

			return next;
		}

6.参考代码 

6.1 list.h

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;

namespace XiaoC
{
	//节点类
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		//构造函数
		list_node(const T& data = T())
			:_data(data)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};

	//迭代器类 -- 迭代器是节点的指针
	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		//类里面重命名的两个类型
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self;

		//类里面的成员变量
		Node* _node;

		//构造函数+初始化列表
		list_iterator(Node* node)
			:_node(node)
		{}

		//解引用运算符重载(如果是原始指针作为迭代器的话,解引用是可以直接拿到其对应的数据的,但是这里的迭代器是一个节点指针,如果要拿到对应节点内部所存储的data需要进行重载)
		Ref operator*()
		{
			return _node->_data;
		}

		//迭代器模拟的是指针的行为
		//_node->_data是节点里面的数据,这里返回的是节点的指针,需要加一个取地址符号
		Ptr 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;
		}

		//迭代器进行遍历等操作需要比较运算符!=
		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
			return _node == s._node;
		}
	};

	//链表类(双向带头循环链表) -- 初始化时有一个哨兵位的头节点
	template <class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		//1. 默认成员函数
		//1.1 构造函数
		
		//list类实例化对象时,里面会有一个哨兵位的头节点,这里写一个创建初始化头节点的函数,用于后面list的构造
		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

		//1.1.1 默认构造函数
		list()
		{
			empty_init();
		}

		//1.1.2 initializer list
		list(initializer_list<T> il)
		{
			empty_init();
			for (auto& e : il)
			{
				push_back(e);
			}
		}

		//1.1.3 copy
		list(const list<T>& lt)
		{
			empty_init();
			//如果只是不加上上一句代码,这个地方生成的this没有头节点
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

		//1.2 析构函数
		~list()
		{
			//先清空链表中的所有节点
			clear();
			//释放头节点
			delete _head;
			_head = nullptr;
			_size = 0;
		}

		//1.3 赋值运算符重载
		//传统写法
		//list<T>& operator=(const list<T>& lt)
		//{
		//	clear();
		//	for (auto& e : lt)
		//	{
		//		push_back(e);
		//	}

		//	return *this;
		//}

		//现代写法
		list<T>& operator=(list<T> lt)
		{
			swap(lt);

			return *this;
		}

		//2. Iterator

		//用一个模板实现普通迭代器和const迭代器,在这里写的时候写成如下
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;

		//2.1 begin()
		iterator begin()
		{
			//begin()指向的是链表中第一个有效的数据,即为头节点的下一个节点
			//iterator it(_head->_next);
			//return it;

			//匿名对象
			//return iterator(_head->_next);

			//隐式类型转换 -- _head->_next是Node*类型,迭代器iterator是list_iterator<T, T&, T*>类型,因为迭代器类里面只有一个Node*类型的成员变量,所有这里可以进行隐式类型转换
			return _head->_next;
		}

		const_iterator begin() const
		{
			return _head->_next;
		}

		//2.2 end()
		//end是最后一个数据的下一个位置 -- 哨兵位
		iterator end()
		{
			return _head;
		}

		const_iterator end() const
		{
			return _head;
		}
		
		//3. Capacity
		//3.1 empty()
		bool empty() const
		{
			return _size == 0;
		}

		//3.2 size()
		size_t size() const
		{
			return _size;
		}
		
		//4. Modifiers
		//4.1 swap()
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		//4.2 clear()
		void clear()
		{
			auto it = begin();
			while(it != end())
			{
				it = erase(it);
			}
		}

		//4.3 push_back()
		void push_back(const T& x)
		{
			//传统写法
			Node* newnode = new Node(x);
			newnode->_next = _head;
			newnode->_prev = _head->_prev;
			_head->_prev->_next = newnode;
			_head->_prev = newnode;

			++_size;
			
			//复用insert()
			//insert(end(), x);
		}

		//4.4 push_front()
		void push_front(const T& x)
		{
			//传统写法
			Node* newnode = new Node(x);
			newnode->_next = _head->_next;
			newnode->_prev = _head;
			_head->_next->_prev = newnode;
			_head->_next = newnode;
			++_size;

			//复用insert()
			//insert(begin(), x);
		}

		//4.5 insert()
		iterator insert(iterator pos, const T& x)
		{
			//在pos位置之前插入数据
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			Node* newnode = new Node(x);
			newnode->_next = cur;
			newnode->_prev = prev;
			cur->_prev = newnode;
			prev->_next = newnode;

			++_size;

			return newnode;	//隐式转换
		}

		//4.6 pop_back()
		void pop_back()
		{
			//删除尾节点
			//传统写法
			Node* tail = _head->_prev;
			Node* prev = tail->_prev;
			prev->_next = _head;
			_head->_prev = prev;

			delete tail;
			tail = nullptr;

			//erase(--end());
		}

		//4.7 pop_front()
		void pop_front()
		{
			//删除头节点
			//传统写法
			Node* cur = _head->_next;
			_head->_next = cur->_next;
			cur->_next->_prev = _head;

			delete cur;
			cur = nullptr;

			//erase(begin());
		}

		//4.8 erase()
		//删除pos位置的数据并返回下一个位置的迭代器
		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

			prev->_next = next;
			next->_prev = prev;
			delete pos._node;

			--_size;

			return next;
		}

	private:
		//链表里面的结构是一个头节点和size
		Node* _head;
		size_t _size;
	};

	template<class Container>
	void print_container(const Container& v)
	{
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2180498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OSError: [WinError 126] 找不到指定的模块。 Error loading \torch\lib\fbgemm.dll“

遇到问题&#xff1a; 在使用torch 、 或者任何设计到torch的库中&#xff0c;只要导入torch就会报错 解决方案 https://blog.csdn.net/Changxing_J/article/details/140489278 https://blog.csdn.net/weixin_43591849/article/details/140715890&#xff08;最终这个解决&…

初识Linux · 进程终止

目录 前言&#xff1a; 进程终止在干什么 进程终止的3种情况 进程如何终止 前言&#xff1a; 由上文的地址空间的学习&#xff0c;我们已经知道了进程不是单纯的等于PCB 自己的代码和数据&#xff0c;进程实际上是等于PCB mm_struct(地址空间) 页表 自己的代码和数据。…

LLM 构建Data Multi-Agents 赋能数据分析平台的实践之⑥:NL2SQL技术探讨

一、概述 NL2SQL&#xff08;Natural Language to SQL&#xff09;是一种将自然语言转换为结构化查询语言的技术。它可以帮助用户通过使用自然语言来与数据库进行交互&#xff0c;而无需了解复杂的SQL语法。 NL2SQL技术的背景&#xff1a; 随着人工智能的发展&#xff0c;越…

prometheus + alertmanager + PrometheusAlert实现告警

prometheus 收集监控数据 alertmanager 制定告警路由 PrometheusAlert 连通告警webhook 一、prometheus配置 https://prometheus.io/download/ 1.1、prometheus安装 包的下载直接wget就行,放在data目录下,解压后在prometheus目录下创建config和rule目录 配置了热重启&#…

聊一聊 C#中有趣的 SourceGenerator生成器

一&#xff1a;背景 1. 讲故事 前些天在看 AOT的时候关注了下 源生成器&#xff0c;挺有意思的一个东西&#xff0c;今天写一篇文章简单的分享下。 二&#xff1a;源生成器探究之旅 1. 源生成器是什么 简单来说&#xff0c;源生成器是Roslyn编译器给程序员开的一道口子&am…

vxe-grid给单元格加上触发事件

效果&#xff1a;输入框的双击事件(其他事件可以由此替换) 代码 // gridTableOptions是每列的配置项 <vxe-grid v-bind"gridTableOptions" :data"goodsList" ref"xTable">// edit_spbh 是对应的样式名&#xff0c;是写在gridTableOption…

如何通过日志快速定位TTS的缓存放音文件(mod_cti基于FreeSWITCH)

文章目录 前言联系我们分析过程1. 测试话术&#xff0c;记录日志2. 关键词搜索 前言 顶顶通呼叫中心中间件在运行话术时&#xff0c;如果有通过TTS合成的语音&#xff0c;会被freeswitch缓存在目录中&#xff1a;/ddt/fs/storage/http_file_cache。 我们可以分析freeswitch日志…

学习Webpack中图片-JS-Vue-plugin

目录 图片文件资源模块类型 JS文件babel命令行使用babel-loaderbabel-preset Vue文件vue-loadervue/compiler-sfc pluginCleanWebpackPluginHtmlWebpackPluginDefinePlugin 图片文件 需要先在项目中使用图片&#xff0c;比较常见的使用图片的方式是两种&#xff1a; img元素&…

LeetCode 918. 环形子数组的最大和

原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 给定一个长度为 n 的环形整数数组 nums &#xff0c;返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c; nums[i] 的下一个元素是 nums[(i 1) % n…

基于STM32的智能室内空气质量监控系统

目录 引言项目背景环境准备 硬件准备软件安装与配置系统设计 系统架构关键技术代码示例 传感器数据采集与处理空气质量分析与报警显示与数据记录功能应用场景结论 1. 引言 智能室内空气质量监控系统用于实时监测环境中的空气质量&#xff0c;通过检测空气中的CO2、PM2.5、温…

软件测试学习笔记丨Pytest 学习指南

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/32336 基本介绍 pytest框架是一个成熟&#xff0c;全面的测试框架&#xff0c;具有非常丰富的第三方插件&#xff0c;并且可以自定义扩展 比如&#xff1a;pytest-selenium , pytest-html ,…

Git常用方法——详解

一、下载安装git git官网&#xff1a; Git - Downloads (git-scm.com) 下载安装Git&#xff08;超详细超简单&#xff09;_git下载-CSDN博客 二、克隆下载至本地 1、复制HTTPS链接 在gitee或者gitLab或者gitHub上复制HTTPS链接 2、打开Open Git Bash here 在本地想要新建文…

小程序原生-列表渲染

1. 列表渲染的基础用法 <!--渲染数组列表--> <view wx:for"{{numList}}" wx:key"*this" > 序号&#xff1a;{{index}} - 元素&#xff1a;{{item}}</view> <!--渲染对象属性--> <view wx:for"{{userInfo}}" wx:key&q…

怎么给视频加片头片尾和字幕

在这个视觉内容爆炸的时代&#xff0c;一段精心制作的视频不仅能吸引眼球&#xff0c;更能传达深刻的情感与信息。而一个引人入胜的片头、一个温馨感人的片尾&#xff0c;以及恰到好处的字幕&#xff0c;无疑是提升视频质感的关键。那么新人要怎么给视频加片头片尾和字幕效果呢…

2024年9月收评

金1是从2005年12月开始&#xff0c;到现在2024年5月&#xff0c;还差7个月整整20年。一共11轮。 这20年里&#xff0c;真正形成单边趋势&#xff0c;能较好获利或者说至少不亏损的一共有以下几次&#xff0c; 第1轮&#xff0c;第2轮&#xff0c;第7轮&#xff0c;第8轮&…

《程序猿之Redis缓存实战 · 集合类型》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

基于微信小程序的商品展示+ssm论文ppt源码调试讲解

2 系统开发环境 2.1微信开发者工具 微信开发者工具现在已经被小程序开发团队开发运行&#xff0c;目前微信开发者工具任然在不断的完善中&#xff0c;在开发小程序时经常要不断的更新。可以使用微信扫码登陆开发者工具&#xff0c;开发者工具将使用这个微信帐号的信息进行小程…

为VRoidStudio制作的vrm格式模型制作blendshape

零、效果展示 bs视频演示 一、准备相关插件 1、VRoidStudio&#xff08;免费&#xff09; 下载网址&#xff1a;https://vroid.com/en/studio 2、UniVRM&#xff08;免费&#xff09; 下载网址&#xff1a;https://github.com/vrm-c/UniVRM/releases 注意&#xff1a;unity…

Qt --- 常用控件的介绍---Widget属性介绍

一、控件概述 编程&#xff0c;讲究的是站在巨人的肩膀上&#xff0c;而不是从头发明轮子。一个图形化界面上的内容&#xff0c;不需要咱们全都从零区实现&#xff0c;Qt中已经提供了很多内置的控件了&#xff08;按钮&#xff0c;文本框&#xff0c;单选按钮&#xff0c;复选…

yolov8实例分割重要图片

训练分割要准备好数据集和分割预训练权重文件 下面这张图是数据集的格式 下面这张图配置数据集&#xff0c;下面names 要和labelme转txt里配置的一样 下面这张图进行训练&#xff0c;配置一些全局参数 &#xff0c;初始的yolov8s-seg.pt文件需要到github上yolov8开源项目里下 l…