c++-list


前言


一、list介绍及使用

1、list介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

2、list使用

2.1 list构造函数的使用

在这里插入图片描述

void Test01()
{
	//使用explicit list (const allocator_type& alloc = allocator_type());构造空的lt1
	list<int> lt1;
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;

	//使用explicit list (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());
	//构造lt2,并且在lt2中放10个5.
	list<int> lt2(10, 5);
	for (auto e : lt2)
	{
		cout << e << " ";
	}
	cout << endl;

	//template <class InputIterator>
	//list(InputIterator first, InputIterator last,const allocator_type & alloc = allocator_type());
	//使用上面的模板生成的迭代器区间构造函数,来构造lt3。
	list<int> lt3(lt2.begin(), lt2.end()); 
	for (auto e : lt3)
	{
		cout << e << " ";
	}
	cout << endl;
	//使用上面的模板生成的迭代器区间构造函数,以数组为迭代器区间构造lt4
	int array[] = { 34,32,9,23 };
	list<int> lt4(array, array + sizeof(array) / sizeof(int));
	for (auto e : lt4)
	{
		cout << e << " ";
	}
	cout << endl;

	//使用list (const list& x);拷贝构造函数来构造lt4
	list<int> lt5(lt3);
	for (auto e : lt5)
	{
		cout << e << " ";
	}
	cout << endl;

	//使用迭代器来遍历lt4中的元素
	list<int>::iterator it = lt4.begin();
	while (it != lt4.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

}

在这里插入图片描述

2.2 list iterator的使用

在这里插入图片描述

void Test02()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));

	//使用正向迭代器正向遍历list中的元素
	list<int>::iterator it = lt1.begin();
	while (it != lt1.end())
	{
		//可以通过迭代器改变元素的值
		(*it) = (*it) * 2;
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//使用反向迭代器逆向遍历list中的元素
	list<int>::reverse_iterator rit = lt1.rbegin();
	while (rit != lt1.rend())
	{
		//可以通过迭代器改变元素的值
		(*rit) = (*rit) * 2;
		cout << *rit << " ";
		//对rit++,迭代器向前移动
		++rit;
	}
	cout << endl;

	//使用const修饰的正向迭代器正向遍历list中的元素
	const list<int> lt2(array, array + sizeof(array) / sizeof(array[0]));
	list<int>::const_iterator cit = lt2.cbegin();
	while (cit != lt2.cend())
	{
		//不可以通过迭代器改变元素的值
		//(*cit) = (*cit) * 2;
		cout << *cit << " ";
		++cit;
	}
	cout << endl;

	//使用const修饰的反向迭代器逆向遍历list中的元素
	list<int>::const_reverse_iterator crit = lt2.crbegin();
	while (crit != lt2.crend())
	{
		//不可以通过迭代器改变元素的值
		//(*crit) = (*crit) * 2;
		cout << *crit << " ";
		//对rit++,迭代器向前移动
		++crit;
	}
	cout << endl;
}


在这里插入图片描述

2.3 list capacity的使用

在这里插入图片描述

void Test03()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));
	list<int> lt2;
	cout << lt1.empty() << endl;
	cout << lt2.empty() << endl;
	cout << lt1.size() << endl;
	cout << lt2.size() << endl;
	cout << lt1.max_size() << endl;
	cout << lt2.max_size() << endl;

}

在这里插入图片描述

2.4 list modifiers的使用

在这里插入图片描述
list插入和删除

void Test04()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));
	lt1.push_front(0);
	lt1.push_back(10);
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;

	lt1.pop_front();
	lt1.pop_back();
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述
insert /erase

void Test05()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));
	// 获取链表中的第二个结点
	list<int>::iterator pos = ++lt1.begin();
	cout << *pos << endl;

	//在pos前插入值为4的元素
	lt1.insert(pos, 4);
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;

	//在pos前插入5个值为6的元素
	lt1.insert(pos, 5, 6);
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;

	//在pos前插入[v.begin(), v.end())区间的元素
	vector<int> v1(3,5);
	lt1.insert(pos, v1.begin(), v1.end());
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
	//可以看到上面的插入中,都是在元素2之前插入元素,说明在list中,insert之后迭代器没有失效。

	// 删除pos位置上的元素
	lt1.erase(pos);
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;

	//删除list中的[begin, end)区间中的元素,及删除list中的所有元素
	lt1.erase(lt1.begin(), lt1.end());
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述

resize/swap/clear

void Test06()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));

	list<int> lt2;
	//将lt2中包含5个3,
	lt2.resize(5, 3);
	//如果resize()中的第一个参数小于lt2的size时,则会将lt2的元素减少为n个,即将后面的元素都删除
	for (auto e : lt2)
	{
		cout << e << " ";
	}
	cout << endl;
	lt2.resize(2, 3);
	for (auto e : lt2)
	{
		cout << e << " ";
	}
	cout << endl;

	//交换lt1和lt2中的元素
	lt1.swap(lt2);
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto e : lt2)
	{
		cout << e << " ";
	}
	cout << endl;

	//将lt2中的元素清空
	lt2.clear();
	cout << lt2.size() << endl;
}

在这里插入图片描述

2.5 list使用算法库中的find模板生成find方法

我们可以看到list库中并没有提供find方法,所以当list想要使用find方法查找元素时,就需要使用算法库中的find模板生成find方法。
在这里插入图片描述

void Test07()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));
	//find返回的是指向元素的迭代器。如果没有查找到元素,find返回lt1.end()迭代器。
	list<int>::iterator pos = find(lt1.begin(), lt1.end(), 3);
	if (pos != lt1.end())
	{
		//在元素3前面插入30.
		lt1.insert(pos, 30);
	}
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;

}

在这里插入图片描述

2.6 list中的sort方法

我们看到算法库中也提供了sort函数模板,但是为什么list库中还要单独实现一个sort函数呢?
这是因为算法库中的sort函数模板中需要传入一个RandomAccessIterator迭代器,即随机迭代器。而我们看到list的迭代器为bidirectional iterator,即双向迭代器,而sort方法需要传入随机迭代器,所以list使用不了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们看到算法库中的reverse模板中需要一个BidirectionalIterator,即双向迭代器,所以list可以使用reverse函数模板。
在这里插入图片描述
迭代器功能分类:
(1). 单向 ++
(2). 双向 ++ –
(3). 随机 ++ – + -
如果函数模板中要求为随机迭代器,就只能传随机迭代器;如果要求为双向迭代器,则可以传双向迭代器和随机迭代器,因为随机迭代器是特殊的双向迭代器。如果要求为单向迭代器,则可以传单向迭代器、双向迭代器和随机迭代器。

虽然list中提供了sort函数,但是list自己提供的sort函数性能不行,一般都不会使用。
下面我们让list使用自己提供的sort和vector使用算法库提供的sort(快排)进行性能测试。可以看到在Release版本下vector使用算法库提供的sort函数的效率更高一些。

void Test08()
{
	srand(time(0));
	const int N = 100000;
	vector<int> v;
	v.reserve(N);

	list<int> lt1;

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

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

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

	cout << "vector sort: " << end1 - begin1 << endl;
	cout << "list sort: " << end2 - begin2 << endl;
}

在这里插入图片描述

所以当我们想要将list中的元素进行排序时,可以先将list中的元素都拷贝到vector中,然后vector使用算法库中的sort排序,排完序后再将元素拷贝到list中,这样来提升list排序的性能。可以看到在Release版本下,使用上面的方法来对list中的元素进行排序可以大大提升list排序的性能。

void Test09()
{
	srand(time(0));
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);

	list<int> lt1;
	list<int> lt2;
	for (int i = 0; i < N; ++i)
	{
		auto e = rand();
		lt1.push_back(e);
		lt2.push_back(e);
	}
	
	//将lt1拷贝到vector中排序
	int begin1 = clock();
	for (auto e : lt1)
	{
		v.push_back(e);
	}
	sort(v.begin(), v.end());
	//排完序后将元素再拷贝到lt1中
	size_t i = 0;
	for (auto& e : lt1)
	{
		e = v[i++];
	}
	int end1 = clock();

	//使用list中提供的sort进行排序
	int begin2 = clock();
	lt2.sort();
	int end2 = clock();

	cout << "vector sort: " << end1 - begin1 << endl;
	cout << "list sort: " << end2 - begin2 << endl;
}

在这里插入图片描述

二、list模拟实现

1、查看list源码的大致实现思路

我们知道list的底层实现是一个带头结点的双向循环链表。
在这里插入图片描述
可以看到list的源码中先定义了一个__list_node结构体来表示链表的结点。然后又定义了一个__list_iterator结构体来实现list的迭代器。最后又定义了一个list类来表示list。
在这里插入图片描述
在这里插入图片描述

2、模拟实现list

我们模拟实现list也按照list库中的办法来实现。
所以我们先定义一个struct list_node结构体来表示链表的结点。因为list_node里面的内容都是公开可访问的,所以我们使用struct来定义list_node,而不是使用class来定义list_node。并且因为该链表的结点中的_next和_prev指针域和_data数据域以后可能存任意类型的数据,所以我们使用模板来定义list_node这个结构体。
在这里插入图片描述
接下来我们再来定义list类,因为list类中的一些成员变量或成员函数是私有的,所以我们使用class来定义list。因为list中以后可能存任意类型的数据,所以我们实现list类时也需要用到模板,直接实现list模板类即可。
我们在list类中定义了一个_head成员变量,这个就是list的头结点,并且我们将头结点的_next指针域和_prev指针域都指向自己,表示该list为空的状态。
在这里插入图片描述

接下来我们实现list类的成员函数push_back,但是在实现这个函数之前我们需要先将list_node结构体的构造函数写好,这样才可以在list中添加新结点时直接将新结点的数据初始化。
在这里插入图片描述
然后再实现push_back函数,因为list是双向循环链表,所以在该链表的尾部插入新结点,其实就是在头结点之前插入新结点。
在这里插入图片描述
我们测试发现push_back方法成功插入新结点。
在这里插入图片描述
在这里插入图片描述
接下来我们再实现list类的迭代器。我们在前面看list源码知道了list的迭代器实现是又创建了一个__list_iterator结构体来实现,所以我们也再创建一个struct __list_iterator结构体用来实现list的迭代器。因为迭代器在使用时需要++向后移动,还需要*来得到数据,所以我们需要实现struct __list_iterator的一些操作符的重载函数。这样当迭代器进行++时,迭代器就会指向下一个结点,当解引用迭代器时,就将迭代器指向的结点的_data返回。判断迭代器是否相等就是判断两个迭代器指向的结点是否为同一个。这个迭代器就是自定义类型对原生指针的封装,模拟指针的行为,即该结构体的构造函数接收一个node * 的参数,因为node里面不是只有一个数据,需要将node的迭代器使用自定义类型对原生指针进行封装,即该迭代器内部封装了node * 类型的指针。
在这里插入图片描述

然后我们在list类中将__list_iterator< T >重命名为iterator,然后在begin函数中返回_head->_next结点的指针,因为list为双向循环链表,所以begin就是头结点的下一个结点,而end为有效数据的下一个结点,所以end就指向_head头结点。这时我们就可以使用list的迭代器来遍历list中的元素了,也可以使用范围for来遍历list的元素。
在这里插入图片描述
在这里插入图片描述
在测试代码中我们发现it = lt1.begin()调用了__list_iterator的拷贝构造函数,并且该拷贝构造函数为浅拷贝,但是为什么没有出错呢?是因为在__list_iterator结构体中没有实现析构函数,因为该类只负责提供iterator,即只是提供一个类似于指针的迭代器,在__list_iterator中并没有动态申请空间创建对象,而是只封装了对node * 指针的一些操作,而释放node结点的事需要list类中实现。
在这里插入图片描述
当我们实现了struct __list_iterator结构体中的前置++运算符重载函数后,我们继续将后置++运算符重载函数和前置 --和后置 --等的运算符重载函数也一并实现了。
在这里插入图片描述
上面的迭代器是没有被const修饰的迭代器,当我们在下面的情况中使用迭代器时就会出现错误。这是因为发生了权限放大,list类的成员函数begin中的this没有被const修饰,而lt被const修饰了,所以发生了权限放大。
在这里插入图片描述
所以我们在list类中又写了begin和end函数的const修饰的版本,但是我们知道const修饰的迭代器是不可以改变list中元素的值的,而我们实现的const修饰的迭代器却可以改变list中元素的值,这是因为我们使用const修饰begin和end函数,其实const修饰的是this指针,即相当于iterator begin(const list* this),所以此时this指针指向的list的_head不能改变,但是在begin中并没有改变_head的值,而是将_head和_head->_next当作参数传给了__list_iterator结构体的构造函数然后创建了__list_iterator类型的对象。然后我们看到在__list_iterator中的 * 操作符的重载函数返回的是一个T类型的引用,所以才可以使用*it修改_data的值。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

那么如果我们像下面一样使用typedef const iterator const_iterator;来重命名一个const_iterator呢?
这样其实更错误,因为这样定义了const_iterator之后,迭代器就不能++了,因为iterator被const修饰了,即T* const it,it为指针,it的内容不可以修改了,那么it迭代器就不可以向后移动了,而const T* it中,it的值可以修改,但是*it的内容不可以修改。

在这里插入图片描述
所以我们再根据__list_iterator结构体实现一个__list_const_iterator结构体,然后在这个结构体的*操作符重载函数中返回一个const T&的引用当作返回值,这样当使用const_iterator迭代器时就不可以通过迭代器修改list的元素的值了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们发现上面的__list_iterator和__list_const_iterator结构体中就只有operator*()函数的返回值不同,所以我们可以将这两个结构体合并为一个。我们通过模板参数来实现。我们给__list_iterator结构体的模板中新加一个Ref参数,当迭代器iterator不被const修饰时,就传入T&,当迭代器iterator被const修饰时,就传入const T&。
通过上面的操作我们知道了:
(1). 迭代器要么就是原生指针。
(2). 迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

下面我们自己实现一个AA结构体,然后使用list来存储AA结构体类型的数据,然后我们使用迭代器遍历list中的AA对象,并且使用cout<<来打印,但是因为我们没有在AA结构体中实现<<操作符的重载函数,所以我们没有办法直接打印AA的内容。我们可以通过解引用迭代器,然后使用.来访问AA的成员变量。
在这里插入图片描述
在这里插入图片描述
既然我们知道迭代器返回的其实就是一个指针,那么我们为什么不直接使用->符号来访问AA的成员变量呢,而是要先解引用迭代器,然后再使用.来访问AA的成员变量。这是因为我们只实现了迭代器结构体__list_iterator的*操作符的重载函数,而没有实现该结构体的->操作符的重载函数。所以下面我们来实现->的重载函数。
在这里插入图片描述
在这里插入图片描述
在使用->符访问AA的成员变量时,其实应该是两个->,因为第一个->是调用->符的重载函数,第二个->是通过地址访问_a1,但是为了增强可读性,省略了一个。
在这里插入图片描述
我们发现const_iterator迭代器使用->操作符的重载函数可以修改list中元素的值,这是肯定不正确的,所以接下来我们实现const版本的->操作符重载函数,禁止const_iterator迭代器使用->操作符的重载函数修改list中元素的值,所以又要加一个模板参数。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此时我们就可以看到const_iterator迭代器使用->操作符的重载函数不能修改list中元素的值了。
在这里插入图片描述
下面我们来实现insert和erase函数。
insert为在当前位置的前面插入元素,所以我们可以这样实现。当实现了insert后,头插尾插就可以复用insert函数来实现了。并且我们看到库里面的insert函数返回了一个迭代器,该迭代器指向刚被插入的元素。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
下面我们来实现erase函数。然后我们再复用erase函数来实现头删和尾删。我们看到在库里面的erase函数也返回了一个迭代器,并且该迭代器指向被删除的结点的下一个结点。
我们需要注意的是在erase之后迭代器必定失效,因为pos指向的结点已经被释放了。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们在上面相当于写了三个类,这三个类都是使用模板来实现的。而类名加模板参数才是真正的类型,即list< int >才是真正的类型,其中list为类名,int为模板参数。
我们模拟实现的list迭代器创建了一个__list_iterator结构体来实现,我们可以查看VS下的vector的迭代器类型,因为VS下vector的迭代器不是靠原生指针实现的,我们可以看到VS下的vector迭代器的类型更加复杂。
在这里插入图片描述
在这里插入图片描述
下面我们来实现list类的析构函数,析构函数就是将list创建的list结点全部都释放。在实现析构函数之前我们可以先实现clear函数,clear函数也是释放list创建的全部结点,只不过clear函数不会释放头结点。
下面是复用erase函数实现的clear函数,将除了头结点的其它结点全部释放。

在这里插入图片描述
还可以使用下面的方法来实现clear,我们在上面说了erase之后it迭代器会失效,那么为什么在这里it迭代器没有失效呢?
这是因为后置++中,我们使用传值返回,所以erase(it++)其实delete释放的是it的拷贝,即erase中delete的是tmp迭代器指向的node结点,而此时it迭代器已经指向下一个元素结点了。
在这里插入图片描述
在这里插入图片描述
当实现了clear函数后,此时list中就只有头结点了,所以我们实现list类的析构函数是可以复用clear函数,然后再将list的头结点delete就可以了。
在这里插入图片描述
下面我们再来实现迭代器区间构造函数,因为迭代器区间构造函数就不会调用list的默认构造函数将_head头结点进行初始化了,所以我们在迭代器区间构造函数中还要将_head头结点进行初始化,这里我们封装一个empty_init函数用来初始化_head头结点,我们在list的默认构造函数和迭代器区间构造函数中都调用这个empty_init函数。
在这里插入图片描述
下面我们来实现list类的拷贝构造函数,因为有时候会调用拷贝构造函数来构造一个新list对象,所以拷贝构造函数也需要调用empty_init函数来将_head头结点进行初始化。
在这里插入图片描述
如果在拷贝构造函数中没有初始化头结点,则就会出现异常。这是因为lt2对象就是调用拷贝构造函数来创建的,而拷贝构造函数中并没有进行_head头结点的初始化,即根本没有申请头结点的空间,所以头结点的的next和prev都不可访问,因为根本没有申请空间。
在这里插入图片描述
在这里插入图片描述
上面我们使用正常的方法来实现拷贝构造函数,下面我们使用一种比较简便的方法来实现拷贝构造函数。
调用迭代器区间构造函数创建一个临时对象tmp,此时tmp对象就是lt对象的副本,然后我们调用swap函数交换this和tmp的_head头结点,然后this就变为了lt对象的副本了,而此时tmp指向的_head头结点就是this原来的数据,而因为tmp为临时对象,所以出了拷贝构造函数tmp对象就会调用析构函数而销毁了。然后我们测试发现程序又出现了异常,这还是因为拷贝构造函数没有初始化_head头结点。
在这里插入图片描述
在这里插入图片描述
当我们将头结点初始化之后,发现程序就可以正常运行了。
在这里插入图片描述
下面我们再来使用同样的方法实现赋值运算符重载函数,需要注意的是赋值运算符重载函数的形参不能为引用,如果为引用的话就会出现问题。下面我们先写正确的赋值运算符重载函数。
因为赋值运算符重载函数重载函数的形参为传值传参,所以在lt2=lt1时调用赋值运算符重载函数时会先调用拷贝构造函数创建临时对象lt,而lt就是拷贝lt1对象创建的,然后调用swap函数将this指向的对象的头结点和lt对象的头结点进行交换,则此时this指向的对象就和lt1对象的内容一致了,而lt临时对象的内容就是lt2对象原来的内容,因为lt对象为临时对象,所以当出了赋值运算符重载函数时,lt对象就会调用析构函数销毁,即lt对象的析构函数就将lt2对象原来的结点都销毁了。

在这里插入图片描述
在这里插入图片描述
下面我们来看为什么赋值运算符重载函数的形参不可以使用引用。我们可以看到此时lt2=lt1调用赋值运算符重载函数后,发现并不是将lt1对象的内容赋值给lt2对象了,而是交换了lt2对象和lt1对象的内容。这是因为当赋值运算符重载函数的形参为引用时,就不会调用拷贝构造函数来创建临时变量lt,所以此时swap(lt)函数其实就是交换了lt2对象和lt1对象的内容。
在这里插入图片描述
在这里插入图片描述
但是如果当我们在赋值运算符重载函数中使用下面的方法时,就可以将赋值运算符重载函数的参数设为传引用传参了,即当需要交换时,我们先创建一个临时对象tmp,并且这个临时对象tmp拷贝lt1的内容,即为lt1的副本,然后我们使用swap函数交换tmp和lt2对象的头结点,这样lt2就为lt1的副本了,即和lt1的内容一样了。
在这里插入图片描述
在这里插入图片描述

下面为list类模拟实现的代码。

#pragma once
#include<iostream>
#include<assert.h>
#include<stdlib.h>
#include<vector>
using namespace std;
namespace dong
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

		//因为T可能为任意类型,所以我们创建一个T类型的匿名对象,然后调用该类型的默认构造参数对该匿名对象进行初始化。
		list_node(const T& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{

		}
	};

	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* n)
			:_node(n)
		{

		}

		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& s)
		{
			return _node != s._node;
		}
		bool operator!=(const self& s)
		{
			return _node != s._node;
		}
	};

	/*template<class T>
	struct __list_const_iterator
	{
		typedef list_node<T> node;
		typedef __list_const_iterator<T> self;
		node* _node;

		__list_const_iterator(node* n)
			:_node(n)
		{

		}

		const T& operator*()
		{
			return _node->_data;
		}

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_data;
			return tmp;
		}
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
		bool operator==(const self& s)
		{
			return _node != s._node;
		}
		bool operator!=(const self& s)
		{
			return _node != s._node;
		}
	};*/

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		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;*/
			//上面的代码会先调用__list_iterator的构造函数,然后return返回时又调用__list_iterator的拷贝构造函数。

			//所以我们返回一个匿名对象,编译器会进行优化,将构造+拷贝构造优化为 -> 构造
			return iterator(_head->_next);
		}
		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}
		void empty_init()
		{
			//初始化头结点
			_head = new list_node<T>;
			_head->_next = _head;
			_head->_prev = _head;
		}
		list()
		{
			empty_init();
		}
		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			//先初始化头结点
			empty_init();
			while(first != last)
			{
				push_back(*first);
				++first;
			}
		}

		//原始方法
		//list(const list<T>& lt)
		//{
		//	empty_init();
		//	for (auto e : lt)
		//	{
		//		push_back(e);
		//	}
		//}

		//下面使用现代方法实现拷贝构造函数
		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}
		list(const list<T>& lt)
		{
			empty_init();
			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void push_back(const T& x)
		{
			先找到尾结点,即头结点的上一个结点
			//node* tail = _head->_prev;
			创建新结点,并且调用list_node的构造函数对该结点进行初始化
			//node* new_node = new node(x);

			在头结点前插入新结点
			//tail->_next = new_node;
			//new_node->_prev = tail;
			//_head->_prev = new_node;
			//new_node->_next = _head;

			//end()就是有效结点的下一个结点,所以在end()前插入一个结点就是在list最后插入一个结点。
			insert(end(), x);
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		iterator insert(iterator pos, const T& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;
			node* new_node = new node(x);

			prev->_next = new_node;
			new_node->_prev = prev;
			new_node->_next = cur;
			cur->_prev = new_node;

			return iterator(new_node);
		}

		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;
			return iterator(next);
		}

		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				//erase每次会返回指向下一个结点的迭代器,所以每次都会更新it迭代器。
				//it = erase(it);

				//还可以使用这种方法来实现clear
				erase(it++);
			}
		}

		list<T>& operator=(list<T>& lt)
		{
			swap(lt);
			return *this;
		}
	private:
		//list的头结点
		list_node<T>* _head;
	};


	void Test01()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		list<int>::iterator it = lt1.begin();
		while (it != lt1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

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

	void print_list(const list<int>& lt)
	{
		list<int>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			//(*it) *= 2;
			cout << *it << " ";
			++it;
		}
		cout << endl;


	}

	struct AA
	{
		int _a1;
		int _a2;
		AA(int a1 = 0, int a2 = 0)
			:_a1(a1)
			, _a2(a2)
		{

		}

	};

	void Test02()
	{
		list<AA> lt;
		lt.push_back(AA(1, 1));
		lt.push_back(AA(2, 2));
		lt.push_back(AA(3, 3));

		list<AA>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << *it << " ";
			//cout << (*it)._a1 << ":" << (*it)._a2 << endl;
			cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;
			//本来应该是两个->,但是为了增强可读性,省略了一个->
			cout << it->_a1 << ":" << it->_a2 << endl;

			++it;
		}
		cout << endl;

	}


	void print_list01(const list<AA>& lt)
	{
		list<AA>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			//(*it) *= 2;
			cout << it->_a1 << " " << it->_a2 << endl;
			++it;
		}
		cout << endl;
	}


	void Test03()
	{
		list<AA> lt;
		lt.push_back(AA(1, 1));
		lt.push_back(AA(2, 2));
		lt.push_back(AA(3, 3));
		print_list01(lt);
	}

	void Test04()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		lt1.insert(++(lt1.begin()), 5);

		lt1.push_back(10);
		lt1.push_front(0);
		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;

		lt1.erase(++(lt1.begin()));
		lt1.pop_back();
		lt1.pop_front();
		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void Test05()
	{
		char array[] = "abcdef";
		list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));

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

	}

	void Test06()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		lt1.push_back(5);
		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;

		list<int> lt2(lt1);
		for (auto e : lt2)
		{
			cout << e << " ";
		}
		cout << endl;
		
	}

	void Test07()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		lt1.push_back(5);

		list<int> lt2;
		lt2.push_back(10);
		lt2.push_back(20);
		lt2.push_back(30);
		cout << "lt2=lt1前,lt1和lt2的值" << endl;
		cout << "lt1:";
		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;
		cout << "lt2:";
		for (auto e : lt2)
		{
			cout << e << " ";
		}
		cout << endl;
		lt2 = lt1;
		
		cout << "lt2=lt1后,lt1和lt2的值" << endl;
		cout << "lt1:";
		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;
		cout << "lt2:";
		for (auto e : lt2)
		{
			cout << e << " ";
		}
		cout << endl;
	}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STL是指标准模板库(Standard Template Library),它是C++语言的一部分,提供了一系列的模板类和函数,用于支持通用的数据结构和算法。STL的目标是提供高效、可重用和可扩展的组件,以便开发人员能够更轻松地编写高质量的代码。STL包含了许多常见的数据结构,如vector、list、set、map等,以及各种算法,比如排序、查找、遍历等。通过使用STL,开发人员可以更加高效地处理各种数据结构和算法的问题,提高代码的开发效率和质量。 在STL中,我们可以使用各种容器来存储和管理数据。例如,我们可以使用std::map来创建一个键值对的映射,其中每个键都有一个与之相关联的值。下面是一个示例代码,展示了如何创建和使用一个std::map对象: std::map<std::string, int> disMap() { std::map<std::string, int> tempMap{ {"C语言教程",10},{"STL教程",20} }; return tempMap; } std::map<std::string, int> newMap(disMap()); 在这个示例中,disMap()函数创建了一个临时的std::map对象,并初始化了其中的一些键值对。然后,使用移动构造函数将这个临时对象移动到了一个新的std::map对象newMap中。最终,我们可以通过newMap对象来访问和操作这些键值对。 综上所述,STL是C++中的标准模板库,提供了一系列的模板类和函数,用于支持通用的数据结构和算法。STL的使用可以提高代码的开发效率和质量,并且通过各种容器和算法,可以方便地处理各种数据结构和算法的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++ STL详解超全总结(快速入门STL)](https://blog.csdn.net/qq_50285142/article/details/114026148)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【C++实验】阅读STL源码并分析](https://blog.csdn.net/qq_35760825/article/details/125311509)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值