C++:list(用法篇+模拟实现)

news2025/4/7 13:49:56

文章目录

  • 前言
  • 一、list 的用法
    • 1. list 简介
    • 2. 用法代码演示
      • 1)头/尾 插/删和迭代器遍历
      • 2)insert与erase
      • 3)排序sort相关
      • 4)其他相关
  • 二、list模拟实现
    • 1. 结点类模板list_node
    • 2. 定义迭代器
      • 1)为什么要专门封装一个迭代器?
      • 2)迭代器类的实现
        • (1)普通迭代器
          • 初始化
          • operator*与operator->
          • operator++
          • operator!=
        • (2)const迭代器——参数T以及为什么有三个参数?
        • (3)迭代器代码实现
    • 3. list类实现
      • 1)成员变量
      • 2)迭代器接口相关
      • 3)构造相关
      • 4)插入删除相关
        • (1)insert与erase及其迭代器失效问题
        • (2)头插/头删/尾插/尾删
  • 三、list与vector对比
  • 模拟实现list完整代码


前言

今天我们一起来看看C++中stl库里list是什么样子的,以及模拟实现list~

在这里插入图片描述


一、list 的用法

1. list 简介

在这里插入图片描述

第一个参数是模板类型,第二个参数是空间配置器我们先暂时不管他。

list的在stl中就是一个双向带头循环链表,它的使用比vector简单一些。

stl这些风格接口都是一样的,他的构造也是这样。
在这里插入图片描述

因为list是链式结构,因此他的访问和遍历不会用到operator[],而是通过迭代器来遍历,对于插入删除操作除了push_back,insert等等这些,还提供了头插头删这样的接口,因为他们不用进行数据的挪动。


2. 用法代码演示

1)头/尾 插/删和迭代器遍历

#include<list>

int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_front(30);
	lt.push_front(88);

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

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

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

	lt.pop_front();
	lt.pop_front();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

2)insert与erase

首先,list的底层空间不是连续的,不能直接通过偏移量访问指定位置(如lt.begin() + 5会报错)。其次,insert操作不会导致迭代器失效,因为list的插入操作不需要扩容,插入后迭代器仍指向有效的逻辑位置。相反,erase操作会导致迭代器失效,删除后需要用返回值更新迭代器以避免野指针。最后,在删除元素时(如删除偶数),应通过erase返回的迭代器更新循环变量,确保迭代器有效。

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

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

	//list的空间不是连续的,+5 并不能找到list中第5个位置,这样写编译器会报错的
	//lt.insert(lt.begin() + 5, 10);

	list<int>::iterator it = lt.begin();
	
	while (it != lt.end())
	{
		it++;
	}

	lt.insert(it, 89);

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

	//对于insert,迭代器不会失效,因为插入数据list不牵扯扩容,it所指向的逻辑位置也不发生改变
	//库里的没找到会返回last的位置

	it = find(lt.begin(), lt.end(), 3);
	if (it != lt.end())
	{
		lt.insert(it, 30);

		// insert以后,it不失效
		*it *= 100;
	}
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	//但是对于erase,使用过后迭代器会失效
	it = find(lt.begin(), lt.end(), 2);
	if (it != lt.end())
	{
		lt.erase(it);

		// erase以后,it失效,她的空间已经没了,就是一个野指针
		//*it *= 100;
	}
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	//要解决这个问题,可以拿iterator做返回值接收,更新it的值
	it = lt.begin();
	
	//还是我们的老朋友,删除list中偶数
	while (it != lt.end())
	{
		if (*it % 2 == 0)
		{
			//就是这里
			it = lt.erase(it);
		}
		else
			it++;

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

3)排序sort相关

由于list的迭代器是双向的,不能直接用标准库的sort(因为那是针对随机访问迭代器设计的快排),所以list自带了sort方法,它内部用的是归并排序。接着,代码还演示了两种反转方式:你可以用标准库的reverse函数,也可以直接用listreverse方法。二者效果相同。
在这里插入图片描述
在这里插入图片描述

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

	//库中的sort不可以,因为库中的排序是快排,迭代器的种类是随机,而这里迭代器的种类是双向
	//sort(lt.begin(), lt.end());

	//因此库中提供了一种底层为归并排序的排序
	lt.sort();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	//list中还有类似逆置,交换这样的,库中和这里的都可以用
	reverse(lt.begin(), lt.end());
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.reverse();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

当然,list中的排序我们是不建议用的,要对list中的数据进行排序,我们应该将其转化成vector,然后调用快排,最后在拷贝回去。

下面这是一段性能测试的代码:
可以看到,越大数据下快排性能越好
在这里插入图片描述

void test_op()
{
	srand(time(0));
	const int N = 100000;
	vector<int> v;
	v.reserve(N);
	list<int> lt1;
	list<int> lt2;

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

	// 拷贝到vector排序,排完以后再拷贝回来
	int begin1 = clock();
	// 先拷贝到vector
	for (auto e : lt1)
	{
		v.push_back(e);
	}

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

	// 拷贝回去
	size_t i = 0;
	for (auto& e : lt1)
	{
		e = v[i++];
	}

	int end1 = clock();

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

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

4)其他相关

第一是remove,它用来删除链表中指定的值,找到了就删,找不到就不做任何操作。这个操作比较简单直接。

第二个是unique,它用来去除链表中的重复元素,但通常需要先排序,这样可以确保去重更高效。unique只会保留相邻元素中的一个,所以排序是去重前的重要一步。

第三个是splice,它用来将一段结点转移到另一个结点的后面。splice有几种用法,可以转移整个list、单个元素或一段区间的元素。splice不会创建新元素,而是直接在链表节点间重新链接,效率很高。

void test04()
{
	int myints[] = { 17,89,7,14 };
	/*std::list<int> mylist(myints, myints + 4);

	//remove是删除特定的值,找到了就删除,没找到就什么也不干
	mylist.remove(890);

	for (auto e : mylist)
	{
		cout << e << " ";
	}
	cout << endl*/;

	//unique是去重,一般要先排序,这要去重效率高
	//mylist.sort();             //  2.72,  3.14, 12.15, 12.77, 12.77,
	 15.3,  72.25, 72.25, 73.0,  73.35

	//mylist.unique();           //  2.72,  3.14, 12.15, 12.77
	 15.3,  72.25, 73.0,  73.35



	std::list<int> mylist1, mylist2;
	std::list<int>::iterator it;

	// set some initial values:
	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

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

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

	it = mylist1.begin();
	++it;                         // points to 2

	//转移是从iterator的位置,转移另一个list (整个/第i个位置/一个迭代区间)
	// 全部转移
	mylist1.splice(it, mylist2);

	// 部分转移
	//mylist1.splice(it, mylist2, ++mylist2.begin());
	//mylist1.splice(it, mylist2, ++mylist2.begin(), mylist2.end());

	//转移自己
	//mylist1.splice(mylist1.begin(), mylist1, ++mylist1.begin(), mylist1.end());

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

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

二、list模拟实现

1. 结点类模板list_node

首先,要写出一个list,就需要先写出它的每一个结点的构成,这里的结构是我们之前学过的双向链表,为了泛用性,list的结点数据可以是任意类型的数据,因此我们需要写成类模板。

与双向链表一样,我们这里有三个变量分别是_next(后继), _prev(前驱), val(值),
并利用初始化模板将其初始化

template<class T>
struct list_node
{
	list_node<T>* _next;
	list_node<T>* _prev;
	T _val;

	list_node(const T& val = T())
		:_next(nullptr)
		,_prev(nullptr)
		,_val(val)
	{}
};

2. 定义迭代器

1)为什么要专门封装一个迭代器?

list不想string和vector,它们两个的底层是由数组实现的,它们的迭代器就是其原生指针,因为原生指针就可以满足迭代器的行为。

迭代器所需要做到的行为如下:
*:解引用获取迭代器对应的值,类似与指针解引用获得值
!=:如:判断迭代器到没到end()等应用
++/--:迭代器迭代到下一个地点

对于vector来说,他的迭代器可以完美符合以上几点,因为vector底层空间是连续的,是数组,就像这样:

在这里插入图片描述

但是,对于list还能这样吗?
答案是不能,因为list底层不是连续的,*it并不是对应的值,而是一个结点,++it并不能让it指向下一个结点,因为空间是不连续的,++后指到哪里谁也不知道。

因此现有的迭代器的行为不符合我们的预期,我们期望的是迭代器表层使用的方式是一致的,因此我们写一个类,封装迭代器,用运算符重载来改变迭代器的行为,使他符合我们的预期。
在这里插入图片描述


2)迭代器类的实现

(1)普通迭代器
初始化
Node* _node;
__list_iterator(Node* node)
	:_node(node)
{}

operator*与operator->

我们先不关心它的返回值,我们先来让他符合我们的预期。

我们想让*返回的不是它的结点,而是结点中的值。
我们想让->返回的是T类型值(结构体)的地址。
注意:
在这里插入图片描述

Ref operator* ()
{
	return _node->_val;
}

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

operator++

我们想让operator++能到下一个结点的位置,--同理,因此需要让_node = _node->next,而不是指针地址的++,因为结点与结点之间空间是不连续的。

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

operator!=

我们想让迭代器的对应的结点与结点比较,而不是迭代器本身。

bool operator!= (const self& it)
{
	return _node != it._node;
}

bool operator== (const self& it)
{
	return _node == it._node;
}

(2)const迭代器——参数T以及为什么有三个参数?

在学习list迭代器的封装时,我们发现了一个很神奇的现象,就是迭代器的模板参数居然有3个!!!

这是为什么呢?
在这里插入图片描述

T在这个地方我们都能理解,因为迭代器的类型就是T,也就是我们list的类型,那为什么还要这个RefPtr呢?

其实原因都是const迭代器搞的鬼。

我们在实现const的版本的时候,遇到一些非常坑的问题。

首先,这样设计const迭代器可以吗?
// typedef const __list_iterator<T> const_iterator;
答案是不可以,因为这样设计const迭代器是不行的,因为const迭代器期望指向内容不能修改
这样设计是迭代器本身不能修改,我们迭代器本身是要++的,不能改变的是它的值。

第二个问题,我们发现,const迭代器与普通迭代器的不同仅仅是因为迭代器所指向的值不能修改,那const迭代器不就是重载operator*的时候加一个const吗?
//typedef __list_const_iterator const_iterator;
但是问题是,这样设计太冗余了,有大量重复的代码,改变的仅仅是operator*前加const以及函数名字。

因此我们的解决方法是:增加一个模板参数Ref,它的作用是typedef定义const_iterator的时候,传得参数有一个是const与普通迭代器不一样,而operator*的返回值就使用Ref.
而Ref就是T的引用,与返回值保持一致.

像这样:
在这里插入图片描述


那么,第三个参数Ptr是什么呢?

类似于operator*,在对自定义类型解引用的方式还有->

比如我们下面有一个类:

class A
{
public:
	A(int a = 0, int b = 0)
		: _a(a)
		, _b(b)
	{}

	int _a;
	int _b;
};

我们想对这个类用迭代器遍历它的值有两种方式:

  1. 通过重载的operator*解引用,然后.出A类的_a和_b.
  2. 通过operator->直接访问到_a和_b.

在这里插入图片描述

而对于const对象,我们就不能直接调用operator->,因为会造成权限放大,所以我们引入了第三个模板参数Ptr表示T类型的指针,用它来区别const对象和普通对象。
因此operator->的返回值就成了Ptr
在这里插入图片描述

实际上,传不同的模板参数,就是生成了不同的模板,我们这样写其实就是让编译器帮我们写了之前那个冗余的写法,是我们代码更简洁,可读性更高~


(3)迭代器代码实现
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)
	{}

	Ref operator* ()
	{
		return _node->_val;
	}

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

	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& it)
	{
		return _node != it._node;
	}

	bool operator== (const self& it)
	{
		return _node == it._node;
	}

};

3. list类实现

1)成员变量

对于每一个链表,我们需要直到它的哨兵位,还有它的大小。

private:
	Node* _head;
	size_t _size;

2)迭代器接口相关

前面我们定义了迭代器类,现在我们要用它获取begin()与end()。

对于带头双向循环链表来说,begin()是第一个位置的元素,也就是哨兵位的下一个,end()是最后一个元素的下一个位置,也就是又回到了哨兵位。
在这里插入图片描述

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

iterator begin()
{
	//这里做了隐式类型转换
	return _head->_next;
	//也可以写成这样
	//return iterator(head->_next);
}

iterator end()
{
	//这里做了隐式类型转换
	return _head;
	//也可以写成这样
	//return iterator(head);
}

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

const const_iterator end() const
{
	return _head;
}

3)构造相关

构造函数:

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

	_size = 0;
}

list()
{
	empty_init();
}

拷贝构造:
这里依然沿用vector那里拷贝构造的思想,复用push_back来做到深拷贝。

list(const list<T>& lt)
{
	empty_init();

	for (auto& e : lt)
	{
		push_back(e);
	}
}

赋值重载
使用现代写法。

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

T& operator=(list<T> lt)
{
	swap(lt);
	return *this;
}

析构函数
复用clear,再把头结点清理掉。

对于clear来说,vector的空间要释放必须完全释放,所以它只清理值,不回收空间,而list结点之间不连续,因此可以直接回收空间,当然,哨兵位是不动的。

~list()
{
	clear();

	delete _head;
	_head = nullptr;
}

void clear()
{
	iterator it = begin();

	while (it != end())
	{
		it = erase(it);
	}

	_size = 0;
}

4)插入删除相关

(1)insert与erase及其迭代器失效问题

insert任意位置插入
逻辑完全就是双链表的逻辑,先开辟出新结点空间,在改变对应的指向。

insert无法对pos进行断言,哪里插入都可以。

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

	prev->_next = newnode;
	newnode->_next = cur;

	cur->_prev = newnode;
	newnode->_prev = prev;

	++_size;

	return newnode;
}

因为list空间是不连续的,因此这里也不用扩容,对于insert来说,迭代器不失效。


erase删除任意位置的数据

erase需要对pos位置进行断言,pos不能改变我们的头结点,而end()就是头结点。

iterator erase(iterator pos)
{
	assert(pos != end());

	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;

	prev->_next = next;
	next->_prev = prev;

	delete cur;
	--_size;

	return next;
}
因为删除了这一个结点,而迭代器又指向这个结点空间,因此erase后迭代器会失效,是铁铁的野指针!

(2)头插/头删/尾插/尾删

逻辑与vector一样,直接复用insert与erase就好。

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;*/

	insert(end(), x);
}

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

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

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

三、list与vector对比

下面是 vectorlist 的对比表格,涵盖了它们的主要特性和性能:

特性vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率 O(1)不支持随机访问,访问某个元素效率 O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度 O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为 O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针,对原生态指针(节点指针)进行封装迭代器封装了节点指针
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

总结

  • vector 更适合需要频繁随机访问和对存储效率要求较高的场景,但在插入和删除时性能较低。
  • list 更适合需要频繁插入和删除操作的场景,尤其是在中间位置进行操作时效率高,但不支持随机访问。

模拟实现list完整代码

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

namespace jyf
{

	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _val;

		list_node(const T& val = T())
			:_next(nullptr)
			,_prev(nullptr)
			,_val(val)
		{}
	};

	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)
		{}

		Ref operator* ()
		{
			return _node->_val;
		}

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

		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& it)
		{
			return _node != it._node;
		}

		bool operator== (const self& it)
		{
			return _node == it._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()
		{
			//这里做了隐式类型转换
			return _head->_next;
			//也可以写成这样
			//return iterator(head->_next);
		}

		iterator end()
		{
			//这里做了隐式类型转换
			return _head;
			//也可以写成这样
			//return iterator(head);
		}

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

		const const_iterator end() const
		{
			return _head;
		}


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

			_size = 0;
		}

		list()
		{
			empty_init();
		}

		list(const list<T>& lt)
		{
			empty_init();

			for (auto& e : lt)
			{
				push_back(e);
			}
		}

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

		T& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();

			while (it != end())
			{
				it = erase(it);
			}

			_size = 0;
		}

		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;*/

			insert(end(), x);
		}

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

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

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

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

			prev->_next = newnode;
			newnode->_next = cur;

			cur->_prev = newnode;
			newnode->_prev = prev;

			++_size;

			return newnode;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;

			delete cur;
			--_size;

			return next;
		}

		size_t size()
		{
			return _size;
		}

	private:
		Node* _head;
		size_t _size;
	};

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

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

		list<int>::iterator it = lt.begin();

		while (it != lt.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

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

	class A
	{
	public:
		A(int a = 0, int b = 0)
			: _a(a)
			, _b(b)
		{}

		int _a;
		int _b;
	};

	void test_list02()
	{
		list<A> lt;
		lt.push_back(A(1, 1));
		lt.push_back(A(2, 2));
		lt.push_back(A(3, 3));
		lt.push_back(A(4, 4));

		list<A>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << (*it)._a << " " << (*it)._b << endl;
			++it;
		}
		cout << endl;

		it = lt.begin();
		while (it != lt.end())
		{
			cout << it->_a << " " << it->_b << endl;
			++it;
		}
		cout << endl;


	}

	void test_list03()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_front(5);
		lt.push_front(6);
		lt.push_front(7);
		lt.push_front(8);
		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		lt.pop_front();
		lt.pop_back();

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

		lt.clear();
		lt.push_back(10);
		lt.push_back(20);
		lt.push_back(30);
		lt.push_back(40);
		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		cout << lt.size() << endl;
	}

	void test_list04()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

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

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

		list<int> lt2;
		lt2.push_back(10);
		lt2.push_back(20);
		lt2.push_back(30);
		lt2.push_back(40);

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

		lt1 = lt2;

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

到这里模拟实现list就结束啦~

谢谢大家!

在这里插入图片描述

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

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

相关文章

【uniapp】设置公共样式,实现公共背景等

目录 1、 全局渐变背景色 2.1 创建common目录 2.2 在common下新建style和images等目录 2.3 在style下新建common-style.scss 2.4 common-style输入全局渐变颜色 2.5 引入样式 2.6 业务页面引入 2.7 展示 2、全局字体颜色 2.1 新建base-style.scss文件 2.2 设置base-…

STM32的GPIO寄存器描述

寄存器&#xff1a; 软件控制硬件(在程序中操作对应控制器)&#xff0c;通过寄存器&#xff0c;就是 寄存器(可以存放数据)&#xff0c;但是其中的数据具有特定的硬件含义(查看芯片手册)&#xff0c;设置寄存器的值&#xff0c;对应的控制器就执行对应的工作。相当于寄存器就是…

IntelliJ IDEA中配置scala

1.IDEA中 配置 maven 左上角 file -> Setting 选择(或直接搜maven) Build, Execution,Deployment -> Build Toos -> Maven Maven home path 选择 maven 安装目录&#xff08;bin的上层目录&#xff09; 示例&#xff1a; D:\maven\apache-maven-3.8.6 User settings…

2024.10月11日--- SpringMVC拦截器

拦截器 1 回顾过滤器&#xff1a; Servlet规范中的三大接口&#xff1a;Servlet接口&#xff0c;Filter接口、Listener接口。 过滤器接口&#xff0c;是Servlet2.3版本以来&#xff0c;定义的一种小型的&#xff0c;可插拔的Web组件&#xff0c;可以用来拦截和处理Servlet容…

基于Springboot+Vue的租房管理系统 (含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统根…

yjs机器学习常见算法01——KNN(K—近邻算法)

1.K—近邻算法 的含义&#xff1a; 简单来说就是通过你的邻居的“类别”&#xff0c;来推测你的“类别” 定义&#xff1a;如果一个样本在特征空间中的k个最相似&#xff08;即特征空间中最临近&#xff09;的样本中大多数属于某一类别&#xff0c;则该样本也属于这个类别。 2.…

Linux系统:本机(物理主机)访问不了虚拟机中的apache服务问题的解决方案

学习目标&#xff1a; 提示&#xff1a;本文主要讲述-本机(物理主机)访问不了虚拟机中的apache服务情况下的解决方案 Linux系统&#xff1a;Ubuntu 23.04&#xff1b; 文中提到的“本机”&#xff1a;代表&#xff0c;宿主机&#xff0c;物理主机&#xff1b; 首先&#xff0c…

SAM 2视觉大模型:图像和视频一键抠图,本地部署整合包

在人工智能和计算机视觉领域&#xff0c;图像和视频的分割技术一直是研究的热点。最近&#xff0c;Meta公司&#xff08;原Facebook&#xff09;推出了一款名为Segment Anything Model 2&#xff08;简称SAM 2&#xff09;的新型AI模型&#xff0c;它在图像和视频分割领域取得了…

layui table 自定义表头

自定义表头-查询 js/css静态文件引用 <!-- 引入 layui.css --> <link href"//unpkg.com/layui2.9.16/dist/css/layui.css" rel"stylesheet"> <!-- 引入 layui.js --> <script src"//unpkg.com/layui2.9.16/dist/layui.js"…

【C++打怪之路Lv9】-- vector

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;重生之我在学Linux&#xff0c;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持…

Flink系统架构和应用部署方式

目录 概述 Flink集群架构组件 Client JobManager TaskManagers 算子、Task和Subtask三者关系 概念 算子和Task的关系 Task和SubTask的关系 算子和SubTask的关系 样例 Task Slots和资源的关系 Flink应用程序部署 Flink Session 集群 Flink Job 集群 Flink Appli…

「Java服务」快速接入SkyWalking方法指南

一、背景 背景&#xff1a;Apache SkyWalking 是一个开源的分布式应用性能监控&#xff08;APM&#xff09;系统&#xff0c;主要用于监控微服务、云原生和容器化应用的性能。接入SkyWalking可以排查以智能投放服务为主的服务响应问题 技术架构 SkyWalking 的核心架构包括以…

[含文档+PPT+源码等]精品基于ssm实现的原生微信小程序线上养花系统的设计与实现

基于SSM&#xff08;Spring、SpringMVC、MyBatis&#xff09;实现的原生微信小程序线上养花系统的设计与实现背景&#xff0c;可以从以下几个方面进行阐述&#xff1a; 一、选题背景 随着人们生活水平的提高和环境保护意识的增强&#xff0c;养花已经成为一种流行的休闲活动。…

UE5 猎户座漂浮小岛 04 声音 材质

UE5 猎户座漂浮小岛 04 声音 材质 1.声音 1.1 导入 wav格式 1.2 循环播放 1.3 mp3转wav 1.4 新手包素材&#xff08;火焰 &#xff09; particle&#xff1a;颗粒 2.材质 2.1 基本颜色 M_Yellow 2.2 混合模式与双面材质 2.3 金属感、高光、粗糙度 M_AluminumAlloy 2.4 自…

【JAVA毕业设计】基于Vue和SpringBoot的课程管理平台

本文项目编号 T 006 &#xff0c;文末自助获取源码 \color{red}{T006&#xff0c;文末自助获取源码} T006&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析5.4 数据库设计 六、…

CyberRt实践之Hello Apollo(Apollo 9.0版本)

apollo9.0环境安装参考官方网站 apollo.baidu.com/community/Apollo-Homepage-Document?docBYFxAcGcC4HpYIbgPYBtXIHQCMEEsATAV0wGNkBbWA5UyRFdZWVBEAU0hFgoIH0adPgCY%2BADwCiAVnEAhAILiAnABZxEgOzK1Y%2BQA51M3ROUnJBsbK2WZoyUdkBhcXoAMhlwDFlARnUXZdzE9AGY%2BbFINADYpUhCEFW…

(Java企业 / 公司项目)阿里云aliyun-对象存储OSS详细从开通到配置(微服务架构选用)

OSS配置文档 注册阿里云账号 https://www.aliyun.com/ 注册成功登录阿里云。 配置bucket 进入控制台&#xff1a; 搜索OSS 点击上图中控制台“对象存储OSS”&#xff0c;立即创建Bucket: 点击“立即创建”&#xff0c;填写bucket的信息&#xff0c;如下图&#xff1a; 注意…

机器学习拟合过程

import numpy as np import matplotlib.pyplot as plt# 步骤1: 生成模拟数据 np.random.seed(0) X 2 * np.random.rand(100, 1) y 4 3 * X 2 * X**2 np.random.randn(100, 1)# 步骤2: 定义线性模型 (我们从随机权重开始) w np.random.randn(2, 1) b np.random.randn(1)#…

C++11中的原子操作及其底层缓存一致性

C中的原子变量&#xff08;atomic variables&#xff09;是一种并发编程中用于保证数据一致性和线程安全的机制。在多线程环境下&#xff0c;当多个线程同时访问或修改同一个变量时&#xff0c;可能会产生竞争条件&#xff08;race condition&#xff09;&#xff0c;导致未定义…