C++初阶—list类

news2025/3/4 5:10:39

第一章:list的介绍及使用

1.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来说这可能是一个重要的因素

1.2 list的使用

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。

1.2.1 list的构造

构造函数( (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)区间中的元素构造list

1.2.2 list iterator的使用

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

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

【注意】

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

1.2.3 list capacity

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

  

1.2.4 list element access

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

1.2.5 list modifiers

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

1.2.6 list的迭代器失效

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

Use of List.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>

using namespace std;

void test_list1() {
	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) //范围for
		cout << e << " ";
	cout << endl;
}

void test_list2() {
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	
	lt.reverse();//逆置
	for (auto e : lt)
		cout << e << " ";//5 4 3 2 1
	cout << endl;

	//sort(lt.begin(), lt.end());//报错
	(从访问使用的角度划分)迭代器功能上分类:正向/反向 及 正向/反向 + const
	(从容器底层实现的角度划分)迭代器性质上分类:
	单向 ++		    单链表/哈希表
	双向 ++/--		双向链表/红黑树(map和set)
	随机 ++/--/+/-  vector/string/deque
	算法库的sort要求传随机迭代器,而list的是双向迭代器。所以list有自己的sort
	
	//less<int> less_lt;//升序
	//lt.sort(less_lt);
	lt.sort();//sort默认升序
	for (auto e : lt)
		cout << e << " ";
	cout << endl;
	//降序
	//greater<int> gt;
	//lt.sort(gt);
	lt.sort(greater<int>());//实际中这种传匿名对象的方式更常用
	//数据量小可以用这个sort,数据量大效率不高。	
}

void test_list3() { //验证list模板中sort的效率
	vecor排序比list快,数据量越大,两者差距越大
	//srand((int)time(0));
	//const int N = 1000000;

	//vector<int> v;
	//v.reserve(N);
	//list<int> lt1;

	//for (int i = 0; i < N; ++i) {
	//	auto e = rand();
	//	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);

	//比较:lt1直接排序,lt2数据拷贝到vector在排序
	srand((int)time(0));
	const int N = 1000000;

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

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

	// 排序
	int begin1 = clock();
	vector<int> v(lt2.begin(), lt2.end());//将lt2的数据拷贝到vector再排序
	sort(v.begin(), v.end());
	lt2.assign(v.begin(), v.end());//将vector的数据再拷贝回lt2
	int end1 = clock();

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

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

void test_list4() {
	//merge() 归并两个链表,取小的尾插。前提是两个链表有序

	//去重演示,需要保证有序
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(3);
	lt.push_back(3);
	lt.push_back(3);
	lt.push_back(5);
	lt.push_back(5);
	lt.push_back(3);
	for (auto e : lt)
		cout << e << " ";//1 2 3 4 3 3 3 5 5 3
	cout << endl;

	lt.sort();
	lt.unique();
	for (auto e : lt)
		cout << e << " ";//1 2 3 4 5
	cout << endl;

	lt.remove(3);//删除全部3
	//lt.remove(30);//删除的数据不存在,什么都不做
	for (auto e : lt)
		cout << e << " ";//1 2 4 5
	cout << endl;
}

void test_list5() {
	list<int> lt1, lt2;

	for (int i = 1; i <= 4; ++i)
		lt1.push_back(i);// lt1: 1 2 3 4

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

	list<int>::iterator it = lt1.begin();
	++it;//it指向2

	//lt1.splice(it, lt2);//1 10 20 30 2 3 4  //将整个lt2转移到lt1 第二节点的前面
	lt1:1 10 20 30 2 3 4   lt2为空,it依旧指向2

	//lt1.splice(it, lt2, ++lt2.begin());//1 20 2 3 4  //将lt2的第二节点转移到lt1的第二节点前面
	lt1.splice(it, lt2, ++lt2.begin(), lt2.end());//1 20 30 2 3 4  //将lt2从第二节点至末尾转移到lt1的第二节点前面
	for (auto e : lt1)
		cout << e << " ";
	cout << endl;
}

int main() {
	//test_list1();
	test_list2();
	//test_list3();
	//test_list4();
	//test_list5();

	return 0;
}

第二章:list的模拟实现

2.1 模拟实现list

List Class.h

#pragma once

namespace bit {
	//封装目的: 封装成 list_node 结构体是为了将链表节点与具体的操作逻辑分离,保持代码的清晰性和模块化。
	//在链表实现中多次使用这个结构体来管理节点,而不仅仅是在 list 类里,因此单独封装是合理的做法。
	template<class T>
	//(如果使用class封装就会变成私有,后期可能需要友元)
	struct list_node { //节点结构体。
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		list_node(const T& x = T())//如果T是自定义类型,则x会是该类型的默认构造对象,取决于该类型的默认构造函数如何定义。
			:_data(x), _next(nullptr), _prev(nullptr) {
		}
	};


	vector中原生指针符合迭代器需要的场景。
	但list不行,对list指针解引用得到的是一个节点结构体,而不是该节点储存的数据;且单纯的++也不支持这种操作
	所以需要对list的迭代器进行封装+运算符重载(即封装节点指针,可以参考日期类的++)
	将迭代器封装成一个独立的类,使得链表的遍历更加简洁和灵活。
	迭代器是 STL 容器设计中的重要概念,单独封装可以提高代码的复用性和可维护性。
	//template<class T>
	//struct __list_iterator { //迭代器
	//	typedef list_node<T> Node;
	//	typedef __list_iterator<T> self;
	//	Node* _node;

	//	__list_iterator(Node* node)
	//		:_node(node) {
	//	}

	//	//迭代器不需要析构函数,因为不需要释放资源(即节点)
	//	//迭代器的赋值和拷贝构造需不需要深拷贝?
	//	//不需要。迭代器通常只是一个轻量级的指针包装类,它指向的资源归容器管理,迭代器不负责资源的分配或释放。
	//	//深拷贝的必要性:深拷贝(Deep Copy)通常用于动态分配资源的类,如 new 申请的内存。
	//	//目的是避免多个对象共享同一块动态内存,导致释放时的重复释放或悬空指针问题。

	//	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; }
	//	T* operator->() { return &_node->_data; }//提供给自定义类型解引用。自定义类型解引用是 指针->形式,所以返回指针
	//	bool operator!= (const self& s) { return _node != s._node; }
	//	bool operator== (const self& s) { return _node == s._node; }
	//};

	const迭代器
	非const迭代器是可读可写,const迭代器是只读
	const T*(指向的内容不能修改)、T* const(指针本身不能修改,即不能指向其他内容)
	const迭代器依然要实现++等(即迭代器指针要改变指向),所以模拟的是第一种
	const迭代器不是非const迭代器加const修饰,那样会造成迭代器本身不能修改。const迭代器是一种单独的类型  
	//template<class T>
	//struct __list_const_iterator { //迭代器
	//	typedef list_node<T> Node;
	//	typedef __list_const_iterator<T> self;
	//	Node* _node;

	//	__list_const_iterator(Node* node)
	//		:_node(node) {
	//	}

	//	//self& operator++() const该写法不对,这里的const修饰指的是this指向的成员不能修改,
	//	//那么_node就不能被重新赋值,无法向下一节点移动
	//	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迭代器不能修改指向的内容,但本身可以移动。所以不能改为const T& operator*() const { return _node->_data; }
	//	const T& operator*() { return _node->_data; }
	//	const T* operator->() { return &_node->_data; }
	//  //迭代器通过上面两个运算符重载修改指向内容,所以const迭代器要对他们返回值进行const修饰防止修改
	//	bool operator!= (const self& s) { return _node != s._node; }
	//	bool operator== (const self& s) { return _node == s._node; }
	//};
	//非const迭代器和const迭代器仅仅只是operator*()和operator->()返回值不同,
	//又因为同一个类模板,实例化参数不同,就是完全不同的类型。所以两个迭代器通过模板参数列表可以合并为一个。
	
	//将非const和const迭代器合并为一个类模板
	//vector中原生指针符合迭代器需要的场景。
	//但list不行,对list指针解引用得到的是一个节点结构体,而不是该节点储存的数据
	//所以需要对list的迭代器进行封装+运算符重载(即封装节点指针,可以参考日期类的
	//将迭代器封装成一个独立的类,使得链表的遍历更加简洁和灵活。
	//迭代器是 STL 容器设计中的重要概念,单独封装可以提高代码的复用性和可维护性。
	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) {
		}

		//迭代器不需要析构函数,因为不需要释放资源(即节点)
		//迭代器的赋值和拷贝构造需不需要深拷贝?
		//不需要。迭代器通常只是一个轻量级的指针包装类,它指向的资源归容器管理,迭代器不负责资源的分配或释放。
		//深拷贝的必要性:深拷贝(Deep Copy)通常用于动态分配资源的类,如 new 申请的内存。
		//目的是避免多个对象共享同一块动态内存,导致释放时的重复释放或悬空指针问题。

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

		Ref operator*() { return _node->_data; }
		Ptr operator->() { return &_node->_data; }//提供给自定义类型解引用。自定义类型解引用是 指针->形式,所以返回指针
		//非const和const迭代器中只有上面这两个运算符重载的返回值不同
		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;
		//上方并没有实例化迭代器,而是根据list<T>模板的不同类型创建了适用于该类型的迭代器类型别名。这些别名只是便于使用而已。
		//对迭代器模板的重命名:因为迭代器模板内部因为需要同时兼容非const和const迭代器,所以参数表达更宽泛。
		//而list类模板内部知道需要使用哪几种迭代器,所以对类模板的参数进行了进一步的限定表达。
		//typedef __list_const_iterator<T> const_iterator;//非const和const迭代器合并为一个类模板,所以不需要这个

		//const_iterator begin() const { return const_iterator(_head->_next); }
		const_iterator begin() const { return _head->_next; }//支持隐式类型转换
		const_iterator end() const { return _head; }//支持隐式类型转换

		iterator begin() {
			//在return iterator(_head->_next); 这一行,_head->_next是list_node<T>*类型的指针,代表链表中第一个有效节点。
			//iterator(_head->_next) 会通过 __list_iterator 构造函数将这个指针传递给 _node 成员。
			//然后返回一个 iterator 对象,该对象持有一个指向节点的指针。
			//return iterator(_head->_next);
			return _head->_next;
		}

		iterator end() { return _head; }

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

		list() { empty_init(); } //构造函数本质是创建一个哨兵位节点

		//lt2(lt1)
		list(const list<T>& lt) { //lt是const对象,需要const迭代器
			empty_init();//创建(lt2的)哨兵位
			for (auto e : lt)//遍历lt1每个节点赋值给e
				push_back(e);//lt2尾插每个e节点
		}

		传统写法 lt3 = lt1 思路:释放lt3,将lt1的节点尾插到lt3
		//list<T>& operator=(const list<T>& lt) {
		//	if (this != &lt) {
		//		clear();
		//		for (auto e : lt)
		//			push_back(e);
		//	}
		//	return *this;
		//}
		//现代写法
		void swap(list<T>& lt) {
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}
		list<T>& operator=(list<T> lt) {
			swap(lt);
			return *this;
		}

		~list() {
			clear();
			delete _head;//清除哨兵位
			_head = nullptr;
		}

		void clear() {
			iterator it = begin();//begin指向哨兵位的下一节点,即有效数据的头节点
			while (it != end())
				it = erase(it);
		}

		//void push_back(const T& x) {
		//	Node* newnode = new Node(x);
		//	Node* tail = _head->_prev;
		//	tail->_next = newnode;
		//	newnode->_prev = tail;
		//	newnode->_next = _head;
		//	_head->_prev = newnode;
		//}
		//优化版本 - 复用insert
		void push_back(const T& x) { insert(end(), x); }//end指向哨兵位,在哨兵位之前插入
		void push_front(const T& x) { insert(begin(), x); }
		void pop_back() { erase(--end()); }//哨兵位前一节点就是尾结点
		void pop_front() { erase(begin()); }

		//list中insert之后迭代器理论上不会失效。因为不涉及扩容,且迭代器依旧指向原先位置。
		//但库里面有返回值,所以和库里保持一致。
		//pos是迭代器对象,现在需要使用的是节点指针,所以需要通过迭代器对象来访它自己的成员变量来获取节点指针。
		iterator insert(iterator pos, const T& x) { 
			Node* cur = pos._node;
			Node* newnode = new Node(x);
			Node* prev = cur->_prev;
			//prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			++_size;
			return iterator(newnode);
		}

		iterator erase(iterator pos) { //erase后迭代器失效,因为迭代器指向的节点被释放了,所以返回下一个节点的位置
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;
			delete cur;
			prev->_next = next;
			next->_prev = prev;
			--_size;
			return iterator(next);
		}

		size_t size() { return _size; }

	private:
		Node* _head;
		size_t _size;//增加该成员避免每次获取size都要遍历一遍链表
	};



	void test_list1() {
		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;
	}

	void test_list2() {
		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<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);
		lt2.push_back(50);
		lt1 = lt2;
		for (auto e : lt1)
			cout << e << " ";
		cout << endl;

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

	struct AA {
		AA(int a1 = 0, int a2 = 0)
			:_a1(a1), _a2(a2) {
		}
		int _a1;
		int _a2;
	};
	void test_list3() {
		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 << " ";//报错。
			//该*it(AA)是自定义对象,不支持流插入
			//第一种方式让AA支持流插入,但这种方式怎么分隔两个值有歧义,并且提供这种方式的流插入就不能变了(写死了)

			//cout << (*it)._a1 << " " << (*it)._a2 << endl;//_a1和_a2是公有的,可以直接访问
			cout << it->_a1 << " " << it->_a2 << endl;//迭代器模拟的是指针的行为,所以自定义类型指针解引用就是->
			cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << endl;
			//根据上面显示调用可知,本应该是it->->_a1。第一个箭头是运算符重载,返回一个指针,第二个箭头才是解引用。
			//但是这种写法可读性不好,所以编译器特殊处理,省略了一个箭头
			++it;
		}
		cout << endl;
	}

	该版本只能打印list<int>
	//void print_list(const list<int>& lt) {
	//	list<int>::const_iterator it = lt.begin();
	//	while (it != lt.end()) {
	//		cout << *it << " ";
	//		++it;
	//	}
	//	cout << endl;

	//	for (auto e : lt)
	//		cout << e << " ";
	//	cout << endl;
	//}
	该版本只能打印list
	template<class T>
	//template<typename T>
	//void print_list(const list<T>& lt) {
	//	//下方list<T>还没有实例化(list<int>才是实例化)
	//	//编译器不确定list<T>::const_iterator是静态成员变量还是内嵌类型
	//	//所以加typename告诉编译器这是一个类型,等类模板(list<T>)实例化再去类里面取
	//	typename list<T>::const_iterator it = lt.begin();
	//	while (it != lt.end()) {
	//		cout << *it << " ";
	//		++it;
	//	}
	//	cout << endl;
	//}
	该版本只能打印list
	//template<typename T>
	//void print_container(const list<T>& lt) {
	//	//下方list<T>还没有实例化(list<int>才是实例化)
	//	//编译器不确定list<T>::const_iterator是静态成员变量还是内嵌类型
	//	//所以加typename告诉编译器这是一个类型,等类模板(list<T>)实例化再去类里面取
	//	typename list<T>::const_iterator it = lt.begin();
	//	while (it != lt.end()) {
	//		cout << *it << " ";
	//		++it;
	//	}
	//	cout << endl;
	//}
	template<typename Container> //Container是一个自定义模板参数
	void print_container(const Container& con) {
		typename Container::const_iterator it = con.begin();//Container可以是list<T>、vector<T>
		while (it != con.end()) {
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
	void test_list4() {
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);
		print_container(lt);

		//vector的push_back涉及深拷贝问题,因为vector需要扩容,
		//当vector中元素拷贝到新空间时,vector中元素指向的内容也需要拷贝。但list不存在扩容问题。
		list<string> lt1;
		lt1.push_back("1111111111111");
		lt1.push_back("1111111111111");
		lt1.push_back("1111111111111");
		lt1.push_back("1111111111111");
		lt1.push_back("1111111111111");
		print_container(lt1);

		vector<string> v;
		v.push_back("2222222222222");
		v.push_back("2222222222222");
		v.push_back("2222222222222");
		v.push_back("2222222222222");
		print_container(v);
	}
}

Test.cpp

#define _CRT_SECURE_NO_WARNIN
#include <iostream>
#include <string>
#include <vector>

using namespace std;
#include "List Class.h"

int main() {
	//bit::test_list1();
	//bit::test_list2();
	//bit::test_list3();
	bit::test_list4();
	return 0;
}

第三章:list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

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

作业

1. 以下程序输出结果为( )

int main() {
	int ar[] = { 0,1, 2, 3, 4,  5, 6, 7, 8, 9 };
	int n = sizeof(ar) / sizeof(int);
	list<int> mylist(ar, ar + n);
	list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5);

	reverse(mylist.begin(), pos);
	reverse(pos, mylist.end());

	list<int>::const_reverse_iterator crit = mylist.crbegin();
	while (crit != mylist.crend()) {
		cout << *crit << " ";
		++crit;
	}
	cout << endl;
}

A.4 3 2 1 0 5 6 7 8 9
B.0 1 2 3 4 9 8 7 6 5
C.5 6 7 8 9 0 1 2 3 4
D.5 6 7 8 9 4 3 2 1 0

答案:C
分析 : list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5); //找到5的位置
reverse(mylist.begin(), pos);//逆置0 1 2 3 4 为 4 3 2 1 0
reverse(pos, mylist.end()); //逆置5 6 7 8 9 为 9 8 7 6 5
逆置完成之后其数据为:4 3 2 1 0 9 8 7 6 5
list<int>::const_reverse_iterator crit = mylist.crbegin(); //反向迭代器进行反向访问
while (crit != mylist.crend()) {}
所以答案为:5 6 7 8 9 0 1 2 3 4

2. 以下代码实现了从表中删除重复项的功能,请选择其中空白行应填入的正确代码(   )

template<typename T>
void removeDuplicates(list<T>& aList) {
	T curValue;
	list<T>::iterator cur, p;
	cur = aList.begin();

	while (cur != aList.end()) {
		curValue = *cur;
		//空白行 1
		while (p != aList.end()) {
			if (*p == curValue)	
				//空白行 2			
			else			
				p++;			
		}
	}
}

A. p=cur+1;aList.erase(p++);
B.p=++cur; p == cur ? cur = p = aList.erase(p) : p = aList.erase(p);
C.p=cur+1;aList.erase(p);
D.p=++cur;aList.erase(p);

答案:B
分析:迭代p需要迭代不重复节点的下一节点,重要的是cur迭代器需要往下迭代,因此cur需要往前移动,二答案A C的cur都不会改变,空白行2是当需要找到重复值时进行节点删除,当删除时当前迭代器会失效,因此需要将迭代器p往后迭代,所以答案为 B

3. 对于list有迭代器it, 当erase(it)后,说法错误的是(    )

A.当前迭代器it失效
B.it前面的迭代器仍然有效
C.it后面的迭代器失效
D.it后面的迭代器仍然有效

答案:C
分析:删除节点后,只有指向当前节点的迭代器失效了,其前后的迭代器仍然有效,因为底层为不连续空间,只有被删除的节点才会失效, 所以答案为 C

4. 下面程序的输出结果正确的是( )

int main() {
	int array[] = { 1, 2, 3, 4, 0, 5, 6, 7, 8, 9 };
	int n = sizeof(array) / sizeof(int);

	list<int> mylist(array, array + n);
	auto it = mylist.begin();

	while (it != mylist.end()) {
		if (*it != 0)
			cout << *it << " ";
		else
			it = mylist.erase(it);
		++it;
	}
	return 0;
}

A.1 2 3 4 5 6 7 8 9
B. 1 2 3 4 6 7 8 9
C.程序运行崩溃
D.1 2 3 4 0 5 6 7 8 9

答案:B
分析:程序在使用迭代器取值时,如果不等于0就进行打印,为0时不打印并删除当前节点,所以答案为 B

5. 下面有关vector和list的区别,描述正确的是(    )

A.两者在尾部插入的效率一样高
B.两者在头部插入的效率一样高
C.两者都提供了push_back和push_front方法
D.两者都提供了迭代器,且迭代器都支持随机访问

答案:A
A.vector在尾部插入数据不需要移动数据,list为双向循环链表也很容易找到尾部,因此两者在尾部插入数据效率相同
B.vector头部插入效率极其低,需要移动大量数据
C.vector由于在头部插入数据效率很低,所以没有提供push_front方法
D.list不支持随机访问

6. 下面有关vector和list的区别,描述错误的是(    )

A.vector拥有一段连续的内存空间,因此支持随机存取,如果需要高效的随机读取,应该使用vector
B.list拥有一段不连续的内存空间,如果需要大量的插入和删除,应该使用list
C.vector<int>::iterator支持“+”、“+=”、“<”等操作符
D.list<int>::iterator则不支持“+”、“+=”、“<”等操作符运算,但是支持了[]运算符

答案:D
A.如果想大量随机读取数据操作,vector是首选的容器
B.如果想大量的插入和删除数据,list效率较高,是首选
C.由于vector底层是连续空间,其迭代器就是相应类型的指针,所以支持对应的操作
D.list迭代器不支持[]运算符

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

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

相关文章

【子网掩码计算器:Python + Tkinter 实现】

子网掩码计算器&#xff1a;Python Tkinter 实现 引言代码功能概述代码实现思路1. 界面设计2. 功能实现3. 事件处理 子网掩码计算器实现步骤1. 导入必要的库2. 定义主窗口类 SubnetCalculatorApp3. 创建菜单栏4. 创建界面组件5. 判断 IP 地址类别6. 计算子网信息7. 其他功能函…

入门基础项目(SpringBoot+Vue)

文章目录 1. css布局相关2. JS3. Vue 脚手架搭建4. ElementUI4.1 引入ElementUI4.2 首页4.2.1 整体框架4.2.2 Aside-logo4.2.3 Aside-菜单4.2.4 Header-左侧4.2.5 Header-右侧4.2.6 iconfont 自定义图标4.2.7 完整代码 4.3 封装前后端交互工具 axios4.3.1 安装 axios4.3.2 /src…

车载以太网-基于linux的ICMP协议

对于车载以太网-ICMP的技术要求: /** ICMP报文格式解析* -----------------* ICMP协议用于网络诊断和错误报告,常见应用包括Ping测试。* ICMP报文结构包括:IP头部、ICMP头部和ICMP数据部分。* 下面详细介绍每个部分的结构、字段的作用以及如何解析它们。* * ICMP头部结构:*…

虚拟机快照与linux的目录结构

虚拟机快照是对虚拟机某一时刻状态的完整捕获&#xff0c;包括内存、磁盘、配置及虚拟硬件状态等&#xff0c;保存为独立文件。 其作用主要有数据备份恢复、方便系统测试实验、用于灾难恢复以及数据对比分析。具有快速创建和恢复、占用空间小、可多个快照并存的特点。在管理维…

Unity小功能实现:鼠标点击移动物体

1、功能描述 当玩家点击鼠标时&#xff0c;场景中的物体会移动到鼠标点击的位置。这个功能可以用于控制角色移动、放置物体等场景。 2、实现步骤 创建Unity项目&#xff1a;首先&#xff0c;打开Unity并创建一个新的3D项目。 添加3D物体&#xff1a;在场景中创建一个3D物体&am…

算法题笔记(自用)——Python

目录 一. 进制&位运算&ASCAII 二. format格式化输出 1. 基本用法 2. 位置参数 3. 格式化数字 4. 对齐和填充 5. 格式化二进制、八进制、十六进制 6. 格式化百分比 7. 格式化科学计数法 8. 格式化字符串字面量&#xff08;f-string&#xff09; 三. 字符串 使…

爬虫系列之【数据解析之JSON】《三》

目录 前置知识 一、 json.loads()&#xff1a;JSON 转 Python 数据 二、json.dump()&#xff1a;python数据 转 json 并写入文件 三、json.loads() &#xff1a;json 转 python数据 四、json.load() &#xff1a;json 转 python数据&#xff08;在文件操作中更方便&#xf…

了解Java集合的概念和体系:Collection<T>、Collections与Stream的使用

学习目标 本文知识是对集合层级的介绍&#xff0c;应用开发中实际使用的是他们的子级&#xff0c;感兴趣的小伙伴或者想深入了解有关Java集合知识的朋友可以选择阅读&#xff01; Stream的方法使用使用部分代码块内大多有两种实现方式&#xff0c;是为了更好的理解方法底层的代…

进程优先级和进程切换 ─── linux第12课

目录 Z(zombie)-僵尸进程 僵尸进程危害 孤儿进程 ​编辑 进程优先级 查看系统进程 用top命令更改已存在进程的nice&#xff1a; 其他概念 进程切换 进程如何切换 进程的调度 进程 内核数据结构 代码和数据 创建进程时 ,先创建内核数据结构 再加载代码和数据 进程退…

光速解决phpstudy无法启动MySQL服务

问题描述 在初步安装使用phpstudy时&#xff0c;会出现mysql服务启动失败的情况&#xff0c;具体表现为点击一键启动后&#xff0c;mysql启动又立即停止 原因及解决方法&#xff1a; phpstudy数据库无法启动有以下几个原因&#xff0c;可以看看自己是第几种&#xff1a; 服务名…

txt 转 json 使用python语言

需求: 把如下的txt文档转成json输出 代码 import jsondef txt_to_json(input_file, output_file):data_list []with open(input_file, r, encodingutf-8) as f:for line in f:# 分割数据并去除换行符parts line.strip().split(,)print(f"{parts}")print(type(par…

Cargo, the Rust package manager, is not installed or is not on PATH.

今天在Windows操作系统上通过pip 安装jupyter的时候遇到这个报错&#xff0c;Cargo, the Rust package manager, is not installed or is not on PATH.。 解决办法 官网&#xff1a;https://rustup.rs/# 下载&#xff1a;https://win.rustup.rs/x86_64 安装完成之后&#xff0c…

JavaWeb个人笔记

技术栈 前端 : HTML CSS JavaScript ES6 Nodejs npm vite vue3 router pinia axios element-plus 后端&#xff1a;HTTP xml Tomcat Servlet Request Response Cookie Sesssion Filter Listener MySQL JDBC Druid Jackson lombok jwt . HTML <!DOCTYPE html> 文档声…

http报文的content-type参数和spring mvc传参问题

很早之前博主聊过HTTP的报文结构以及其中和传参相关的重要参数content-type还有spring mvc&#xff0c;以前的三篇文章&#xff1a; HTTP与HTTPS协议详解&#xff1a;基础与安全机制-CSDN博客 详解Http的Content-Type_content-type application-CSDN博客 如何在Spring Boot中…

14. LangChain项目实战1——基于公司制度RAG回答机器人

教学视频&#xff1a; 12. 基于Gradio搭建基于公司制度RAG_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV11VXRYTErZ/ 环境配置&#xff1a; python版本&#xff1a;3.10.8 服务器&#xff1a;Ubuntu 依赖包requirements.txt文件内容&#xff1a; aiofiles23.2.1 …

FPGA开发,使用Deepseek V3还是R1(6):以滤波器为例

以下都是Deepseek生成的答案 FPGA开发&#xff0c;使用Deepseek V3还是R1&#xff08;1&#xff09;&#xff1a;应用场景 FPGA开发&#xff0c;使用Deepseek V3还是R1&#xff08;2&#xff09;&#xff1a;V3和R1的区别 FPGA开发&#xff0c;使用Deepseek V3还是R1&#x…

JVM常用概念之垃圾回收设计与停顿

在我们应用程序运行期间&#xff0c;我们是需要尽可能避免垃圾回收。 图1&#xff1a;不同垃圾回收器的设计&#xff08;黄色代表STW&#xff0c;绿色代表并发&#xff09; 实验 计算机配置 Hardware Overview:Model Name: MacBook ProModel Identifier: MacBookPro14,2Pro…

uniapp-原生android插件开发摘要

uni-app在App侧的原生扩展插件&#xff0c;支持使用java、object-c等原生语言编写&#xff0c;从HBuilderX 3.6起&#xff0c;新增支持了使用uts来开发原生插件。 基础项目 UniPlugin-Hello-AS工程请在App离线SDK中查找 基础项目(App离线SDK)已经配置好了自定义插件所需要的…

派可数据BI接入DeepSeek,开启智能数据分析新纪元

派可数据BI产品完成接入DeepSeek&#xff0c;此次接入标志着派可数据BI在智能数据分析领域迈出了重要一步&#xff0c;将为用户带来更智能、更高效、更便捷的数据分析体验。 派可数据BI作为国内领先的商业智能解决方案提供商&#xff0c;一直致力于为用户提供高效、稳定易扩展…

M4 Mac mini运行DeepSeek-R1模型

前言 最近DeepSeek大模型很火&#xff0c;实际工作中也有使用&#xff0c;很多人觉得需要很好的显卡才能跑起来&#xff0c;至少显存需要很高&#xff0c;但实际上一般的核显机器也能跑起来&#xff0c;只不过内存要求要大&#xff0c;对于个人而言&#xff0c;实际上Mac M芯片…