【C++进阶之路】list的基本使用和模拟实现

文章目录

  • 初步认识
    • ①定义
    • ②底层原理
    • ③迭代器的分类
  • 一、基本使用
    • 1.插入结点元素
    • 2.删除结点元素
    • 3.合并两个有序链表
    • 4.将一条链表的某一部分转移到另一条链表
    • 5.对链表排序并去重
    • 6.vector与list排序的比较
  • 二、模拟实现
    • ①要点说明
    • ②基本框架
    • ③迭代器
      • 构造函数
      • ++
      • - -
      • *
      • ->
      • list里的迭代器
    • ④insert
    • ⑤erase
    • ⑥push_back
    • ⑦push_front
    • ⑧pop_front
    • ⑨pop_back()
    • ⑩构造函数
    • ⑪clear
    • ⑫析构函数
    • ⑬赋值重载
  • 源码
  • 总结

初步认识

 在正式讲解之前,我们先简单的认识一下list

①定义

template < class T, class Alloc = allocator<T> > class list;

同样这里有两个模板参数,第一个是实例化的类型,第二个是空间配置器

②底层原理

库里的list是采用带头双向循环链表进行实现的。

  • forward_list是单链表

在这里插入图片描述

  • 带哨兵位的头结点,是为了给end()迭代器留位置。

③迭代器的分类

在这里插入图片描述

1.输入迭代器
2.输出迭代器
3.单向迭代器——forward_list,unorder_xxx
4.双向迭代器——list/map/set
5.随机访问迭代器——vector/string/deque

之间的关系:
在这里插入图片描述

  • 从左到右是被包含的关系。

在这里插入图片描述

一、基本使用

1.插入结点元素

	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_back(6);

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	
	list<int>::iterator it = lt.begin();
	for (size_t i = 0; i < 5; i++)
	{
		it++;
	}
	//在第五个位置的前面插入10
	//第五个位置——是下标为5,也就是第六个元素。
	lt.insert(it,10);


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

在这里插入图片描述

  • 说明:因为list不支持随机访问,因此没有+,+=,[] 运算符重载,但支持++,- -

  • 这里的迭代器,在插入之后,并没有失效,因为结点的插入是单个申请,单个释放的。不存在 扩容的现象。

2.删除结点元素

void test_list()
{
	list<int> lt;
	lt.push_back(2);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(90);
	lt.push_back(6);
	lt.push_back(7);


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

	//删除所有的偶数结点
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		if (*it % 2 == 0)
		{
			it = lt.erase(it);
		}
		else
		{
			it++;
		}
	}

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

}
  • erase在释放之后,迭代器是肯定失效的,因为其所指向的空间都失效了。因此库里采用返回值进行更新迭代器。

3.合并两个有序链表

  • 前提必须有序的。
void test_list()
{
	list<int> lt;
	lt.push_back(2);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);
	lt.push_back(7);


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

	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	lt1.push_back(9);

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

	//将lt1合并到lt上
	lt.merge(lt1);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

}

在这里插入图片描述

4.将一条链表的某一部分转移到另一条链表

接口:


void splice (iterator position, list& x);

void splice (iterator position, list& x, iterator i);

void splice (iterator position, list& x, iterator first, iterator last);

注意:迭代器/迭代器区间不能重合!

list<int> lt;
	lt.push_back(2);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);
	lt.push_back(7);


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

	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	lt1.push_back(9);

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

	//将lt1转移到到lt的前面
	lt.splice(lt.begin(),lt1);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	//将lt的后半部分转移到lt1上
	list<int>::iterator it = lt.begin();
	for (size_t i = 0; i < 5; i++)
	{
		it++;
	}
	lt1.splice(lt1.begin(), lt ,it, lt.end());

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

	//将lt1的第一个结点移到lt的最前面
	lt.splice(lt.begin(), lt1, lt1.begin());

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

5.对链表排序并去重

  • unique去重的前提是有序
	list<int> lt;
	lt.push_back(2);
	lt.push_back(1);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(2);
	lt.push_back(6);
	lt.push_back(4);
	
	lt.sort();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.unique();

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

6.vector与list排序的比较

void test_list2()
{
	list<int> lt;
	vector<int> v;
	//设置随机数起点
	srand((unsigned)time(nullptr));
	size_t datas_num = 1000000;
	for (size_t i = 0; i < datas_num; i++)
	{
		int n = rand();
		lt.push_back(n);
		v.push_back(n);
	}
	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();
	cout <<"vector:" << (end1 - begin1) << endl;

	int begin2 = clock();
	lt.sort();
	int end2 = clock();
	cout <<"list:" << (end2 - begin2) << endl;
}

在这里插入图片描述

  • 结果:list的排序有点挫,不过如果数据量比较小,可以使用list提供的排序,数据量比较大的话,那就将list的数据拷贝到vector,进行排序,再拷贝回去。
  • 注意:在debug下进行测试可能会得到不一样的结果,因为vector是使用递归的快排进行实现的,如果debug下添加的调试信息会使栈帧的开销较大,从而影响时间的效率。而list的底层原理是归并,虽然也是递归,但调试信息添加的可能较少,因此会较快。而realease版本是发型版本是做了优化的,性能更好。

二、模拟实现

①要点说明

  • 1.为了不与库里面的list冲突,我们需要命名空间对自己实现的类进行封装
  • 2.这里我们实现的框架是按照带头双向循环链表的数据结构进行实现的。
  • 3.为了理解,下面的接口是分开讲解的,最后我会给出源码

②基本框架

template<class  T>
struct list_node
{
	list_node(const T& val = T())
		:_val(val)
		,_next(nullptr)
		,_prev(nullptr)
	{}

	T _val;
	list_node* _next;
	list_node* _prev;
};

template<class T>
class list
{
	typedef list_node<T> Node;
public:
	
private:
	Node* _head;
	size_t _size;
};

③迭代器

  • 这是list的最关键的部分!

我们先来谈谈是如何实现的,迭代器必然是指向一个结点的指针,但是能完成++操作,以及解引用操作,那该怎么办呢?答案其实很简单——用类进行封装,通过运算符重载实现相应的功能

简单的迭代器框架:

template<class T>
struct list_iterator
{
	typedef list_iterator<T> iterator;	
	Node* _node;
};

构造函数

list_iterator(Node* val = nullptr)
	:_node(val)
{}
list_iterator(const iterator& lt)
	:_node(lt._node)
{}
  • 析构我们使用默认生成的即可,可千万不要写析构释放迭代器指向的空间,因为迭代器指向的空间属于list。应由list进行管理。

剩下的操作是,类里面进行重载++,-- ,*。

++

//前置
iterator& operator ++() 
{
	_node = _node->_next;
	return *this;
}
//后置
iterator operator ++(int)
{
	list_iterator tmp(_node);
	_node = _node->_next;
	return tmp;
}

- -

//前置
iterator& operator --()
{
	_node = _node->_prev;
	return *this;
}
//后置
iterator operator --(int)
{
	list_iterator tmp(_node);
	_node = _node->_prev;
	return tmp;
}

*

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

这样迭代器的基本功能就实现了,但是const的迭代器该如何实现呢?

是这样么?

typedef const list_iterator<T> const_iterator;

这样迭代器的成员变量不可被修改,即不可以++或者- -,不符合要求,我们想要的只是返回的值不可被修改

一般都会再拷贝一份进行,将拷贝的迭代器改改,但是我们还可以设置第二个模板参数,这样外面可以这样定义。

template<class Tclass Ref> struct list_iterator;
//里面的运算符重载——*
Ref operator *()
{
	return _node->_val;
}

//list里面可以这样定义

typedef list_iterator<T,const T&> const_iterator;
typedef list_iterator<T,T&> iterator;
  • 这样问题就解决了。

->

还有第二个问题,如果迭代器指向的是自定义类型,比如:

struct A
{
	A(int x = 0)
		:_a1(x)
	{}
	int _a1;
};

如果想要访问其成员,直接解引用是不行的。

我们可以这样:

iterator it = lt.begin();
(*it)._a1;

访问其元素。

我们还可以重载-> 进行访问:

T* operator ->()
{
	return &(_node->_val);
}
iterator it = lt.begin();
it->_a1;

这样其实省略了一个箭头,这省略的一个箭头其实是为了美观,编译器在处理时,会自动加上的。

如果是const对象呢?其实处理方式一样的,再加一个模板参数。

更新后的模板,和迭代器:

template<class T,class Ref,class Ptr> struct list_iterator;
Ptr operator ->()
{
	return &(_node->_val);
}
typedef list_iterator<T,const T&,const T*> const_iterator;
typedef list_iterator<T,T&,T*> iterator;

那完整版的迭代器即为:

template<class T,class Ref,class Ptr>
struct list_iterator
{
	typedef list_node<T> Node;
	typedef list_iterator<T, Ref, Ptr> self;
	typedef list_iterator<T, T&, T*> iterator;


	list_iterator(Node* val = nullptr)
		:_node(val)
	{}
	list_iterator(const iterator& lt)
		:_node(lt._node)
	{}


	//重载++
	self& operator ++() 
	{
		_node = _node->_next;
		return *this;
	}
	//后置++,为了区别需要重载一下,这里的参数实际上没啥用
	self operator ++(int)
	{
		list_iterator tmp(_node);
		_node = _node->_next;
		return tmp;
	}
	self& operator --()
	{
		_node = _node->_prev;
		return *this;
	}
	self operator --(int)
	{
		list_iterator tmp(_node);
		_node = _node->_prev;
		return tmp;
	}
	bool operator != (const self& it) const
	{
		return _node != it._node;
	}
	bool operator == (const self& it) const//const和非const都能用
	{
		return _node == it._node;
	}		
	Ptr operator ->()
	{
		return &(_node->_val);
	}
	Ref operator *()
	{
		return _node->_val;
	}
	Node* _node;
};

list里的迭代器

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

④insert

//在pos之前插入
void insert(iterator pos, const T& val)
{
	Node* newnode = new Node(val);
	Node* cur = pos._node;
	Node* prev = cur->_prev;

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

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


	_size++;
}

⑤erase

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

⑥push_back

void push_back(const T& val)
{
	//Node* tail = _head->_prev;
	//Node* newnode = new Node(val);

	//newnode->_next = _head;
	//newnode->_prev = tail;

	//_head->_prev = newnode;
	//tail->_next = newnode;
	insert(end(), val);
}

⑦push_front

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

⑧pop_front

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

⑨pop_back()

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

⑩构造函数

//默认构造函数
list()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}

//拷贝构造
list(const list<T>& lt)
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;

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

⑪clear

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

⑫析构函数

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

⑬赋值重载

void swap(list &tmp)
{
	std::swap(_head, tmp._head);
	std::swap(_size, tmp._size);
}
list& operator = (list tmp)
{
	swap(tmp);
	return *this;
}

剩余的接口,过于简单,这里就不再列出了,有兴趣请看源码。

源码

namespace my_list
{
	template<class  T>
	struct list_node
	{
		list_node(const T& val = T())
			:_val(val)
			,_next(nullptr)
			,_prev(nullptr)
		{}

		T _val;
		list_node* _next;
		list_node* _prev;
	};



	template<class T,class Ref,class Ptr>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> self;
		typedef list_iterator<T, T&, T*> iterator;


		list_iterator(Node* val = nullptr)
			:_node(val)
		{}
		list_iterator(const iterator& lt)
			:_node(lt._node)
		{}


		//重载++
		self& operator ++() 
		{
			_node = _node->_next;
			return *this;
		}
		//后置++,为了区别需要重载一下,这里的参数实际上没啥用
		self operator ++(int)
		{
			list_iterator tmp(_node);
			_node = _node->_next;
			return tmp;
		}
		self& operator --()
		{
			_node = _node->_prev;
			return *this;
		}
		self operator --(int)
		{
			list_iterator tmp(_node);
			_node = _node->_prev;
			return tmp;
		}
		bool operator != (const self& it) const
		{
			return _node != it._node;
		}
		bool operator == (const self& it) const//const和非const都能用
		{
			return _node == it._node;
		}		
		Ptr operator ->()
		{
			return &(_node->_val);
		}
		Ref operator *()
		{
			return _node->_val;
		}
		Node* _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()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		list(const list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

			for (auto e : lt)
			{
				push_back(e);
			}
		}
		~list()
		{
			clear();
			delete _head;
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
			//cout << _size << endl;
		}
		iterator begin() 
		{
			//return _head->_next;
			//隐式类型转换为list_itertor
			
			return _head->_next; 
		}
		const_iterator begin()const
		{
			//return _head->_next;
			//隐式类型转换为list_itertor

			return _head->_next;
		}
		iterator end()
		{
			return _head;
			//返回的是_head的拷贝,因此返回对象具有常属性
			
			//隐式类型转换为list_itertor
			//return itrator(_head->next); 
			//匿名对象
		}
		const_iterator end()const
		{
			return _head;
			//返回的是_head的拷贝,因此返回对象具有常属性

			//隐式类型转换为list_itertor
			//return itrator(_head->next); 
			//匿名对象
		}
		void push_back(const T& val)
		{
			//Node* tail = _head->_prev;
			//Node* newnode = new Node(val);

			//newnode->_next = _head;
			//newnode->_prev = tail;

			//_head->_prev = newnode;
			//tail->_next = newnode;
			insert(end(), val);
		}
		//在pos之前插入
		void insert(iterator pos, const T& val)
		{
			Node* newnode = new Node(val);
			Node* cur = pos._node;
			Node* prev = cur->_prev;

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

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


			_size++;
		}
		Node* 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;
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		void push_front(const T& val)
		{
			insert(begin(), val);
		}
		size_t size()
		{
			return _size;
		}
		bool empty()
		{
			return _size == 0;
		}
		void swap(list &tmp)
		{
			std::swap(_head, tmp._head);
			std::swap(_size, tmp._size);
		}
		list& operator = (list tmp)
		{
			swap(tmp);
			return *this;
		}

	private:
		Node* _head;
		size_t _size;
	};
}

总结

 今天的分享就到这里了,如果觉得文章不错,点个赞鼓励一下吧!我们下篇文章再见

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGLM-6B+LangChain实战

目标&#xff1a;原始使用ChatGLM-6B可接受的文字长度有限&#xff0c;打算结合LangChain实现长文本生成摘要. 方法&#xff1a; step1&#xff1a;自定义一个GLM继承LangChain中的langchain.llms.base.LLM&#xff0c;load自己的模型. step2&#xff1a;使用LangChain的mapred…

vue 集成tinymce2实现图片,视频以及文件的上传

vue 集成tinymce2实现图片&#xff0c;视频以及文件的上传 1. 安装插件 &#xff08;1&#xff09;安装tinymce npm install tinymce -S &#xff08;2&#xff09;安装tinymce-vue npm install tinymce/tinymce-vue3.0.1 -S 2. 复制静态文件到public目录 资源下载路径&…

数据分析的iloc和loc功能

大家好&#xff0c;在处理大型数据集时&#xff0c;使用有效的数据操作和提取技术是必要的。Pandas数据分析库提供了强大的工具&#xff0c;用于处理结构化数据&#xff0c;包括使用iloc和loc函数访问和修改DataFrame元素的能力。在本文中&#xff0c;我们将探讨iloc和loc之间的…

JAVA集成国密SM2

JAVA集成国密SM2加解密 一、pom配置二、代码集成2.1、目录结构2.2、源码2.3、测试 三、相关链接 国密算法概述&#xff1a;https://blog.csdn.net/qq_38254635/article/details/131801527 SM2椭圆曲线公钥密码算法 为非对称加密&#xff0c;基于ECC。该算法已公开。由于该算法…

rabbitMQ杂记

消息队列应用场景 消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量 削锋等问题实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性&#xff1a; 解耦&#xff1a; 异步&#xff1a; 削峰&#xff1a; 常…

基于单片机的教室智能照明台灯控制系统的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;LCD1602液晶显示当前时间、年月日、时分秒、前教室人数&#xff1b;2路红外探头用来感应当前教室进出人数&#xff1b;按键可以设置当前时间、自动手动模式、开启和关闭教室灯光时间&#xff1b;在手动模式下&#xff0c;可以通过按…

Spring IoC及DI依赖注入

Spring 1.Spring的含义&#xff1a; Spring 可从狭义与广义两个角度看待 狭义的 Spring 是指 Spring 框架(Spring Fremework) 广义的 Spring 是指 Spring 生态体系 2.狭义的 Spring 框架 Spring 框架是企业开发复杂性的一站式解决方案 Spring 框架的核心是 IoC 容器和 AO…

透视表可视化简单案例

import pandas as pd import numpy as np import os basepath/Users/kangyongqing/Documents/kangyq/202307/标准版学期制C2/pathos.path.join(basepath,02freetime.csv) dtpd.read_csv(path,dtype{shifen:object}) print(dt.head()) import matplotlib.pyplot as pltfor i in …

Python探索金融数据进行时间序列分析和预测

大家好&#xff0c;时间序列分析是一种基于历史数据和趋势分析进行预测的统计技术。它在金融和经济领域非常普遍&#xff0c;因为它可以准确预测趋势并做出明智的决策。本文将使用Python来探索经济和金融数据&#xff0c;执行统计分析&#xff0c;并创建时间序列预测。 我们将…

55. 跳跃游戏

题目链接&#xff1a;力扣 解题思路&#xff1a; 贪心&#xff0c;因为题目只需要判断能够到达最后一个下标&#xff0c;所以可以从前往后遍历&#xff0c;使用maxEnd保存已经遍历过的位置能够跳跃达到的最大下标&#xff0c;如果maxEnd大于等于nums.length-1&#xff0c;则返回…

系统学习Linux-Rsync远程数据同步服务(三)

一、概述 rsync是linux 下一个远程数据同步工具 他可通过LAN/WAN快速同步多台主机间的文件和目录&#xff0c;并适当利用rsync 算法减少数据的传输 会对比两个文件的不同部分&#xff0c;传输差异部分&#xff0c;因此传输速度相当快 rsync可拷贝、显示目录属性&#xff0c…

ChatGPT:利用人工智能助推教育创新

当前&#xff0c;世界正需要一个更加开放的、更加个性化的学习空间&#xff0c;学生的个性发展和生存发展应该被关注和尊重&#xff0c;课程应该引导学生掌握有用的东西&#xff0c;学生之间的差距应该被正视&#xff0c;教育成功的标准也要被重新定义。过去&#xff0c;我们总…

N天爆肝数据库——MySQL(5)

本文主要对索引进行了讲解 这里写目录标题 本文主要对索引进行了讲解索引概述介绍优缺点索引结构二叉树红黑树B-Tree(多路平衡查找树)BTreeBTree与B-Tree区别: HashHash索引特点 为什么InnoDB存储引擎选择使用BTree索引结构&#xff1f;索引分类在InnoDB存储引擎中&#xff0c;…

基于微信小程序的求职招聘系统设计与实现(Java+spring boot+MySQL+微信小程序)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于微信小程序的求职招聘系统设计与实现&#xff08;Javaspring bootMySQL微信小程序&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java s…

(五)「消息队列」之 RabbitMQ 主题(使用 .NET 客户端)

0、引言 先决条件 本教程假设 RabbitMQ 已安装并且正在 本地主机 的标准端口&#xff08;5672&#xff09;上运行。如果您使用了不同的主机、端口或凭证&#xff0c;则要求调整连接设置。 获取帮助 如果您在阅读本教程时遇到问题&#xff0c;可以通过邮件列表或者 RabbitMQ 社区…

【C++】list的使用及底层实现原理

本篇文章对list的使用进行了举例讲解。同时也对底层实现进行了讲解。底层的实现关键在于迭代器的实现。希望本篇文章会对你有所帮助。 文章目录 一、list的使用 1、1 list的介绍 1、2 list的使用 1、2、1 list的常规使用 1、2、2 list的sort讲解 二、list的底层实现 2、1 初构…

windows环境hadoop报错‘D:\Program‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

Hadoop版本为2.7.3&#xff0c;在环境配置好后&#xff0c;检查hadoop安装版本&#xff0c;报如标题所示错误&#xff0c;尝试网上主流的几种方法均无效。 错误&#xff1a;windows环境hadoop报错’D:\Program’ 不是内部或外部命令,也不是可运行的程序 或批处理文件。 错误方…

perl输出中文乱码【win10】

perl输出中文乱码 运行的时候输出的内容变成了中文乱码&#xff0c;原因首先来查找一下自己的perl的模块里面是否有Encode-CN。请运行打开你的cmd并输入perldoc -l Encode::CN 如果出现了地址 则就是有&#xff0c;如果没有需要进行该模块的安装。 安装方式有很多种&#xff0…

STM32F407-- DMA使用

目录 1. DMA结构体 STM32F103&#xff1a; STM32F407&#xff1a; 2. F4系列实现存储器到存储器数据传输 1&#xff09;结构体配置&初始化 2&#xff09;主函数 补充知识点&#xff1a;关于变量存储的位置&#xff0c;关于内部存储器一般存储什么内容 3. F4系列实现…

机器学习 day26(多标签分类,Adam算法,卷积层)

1. 多标签分类 多标签分类&#xff1a;对于单个输入特征&#xff0c;输出多个不同的标签y多类分类&#xff1a;对于单个输入特征&#xff0c;输出单个标签y&#xff0c;但y的可能结果有多个 2. 为多标签分类构建神经网络模型 我们可以构建三个不同的神经网络模型来分别预测…