【C++初阶】第十篇:list模拟实现

news2024/10/7 8:30:13

文章目录

  • 一、list的模拟实现
    • 三个类及其成员函数接口总览
    • 结点类的模拟实现
    • 迭代器类的模拟实现
      • 迭代器类的模板参数说明
      • 迭代器operator->的重载
      • 迭代器模拟实现代码
    • list的模拟实现
      • 无参构造函数
      • 带参构造
      • 拷贝构造函数
      • 赋值运算符重载函数
      • 析构函数
      • begin和end
      • insert
      • erase
      • list的迭代器失效问题
      • push_back和pop_back
      • push_front和pop_front
      • clear
      • empty
      • size
      • swap
  • 二、list与vector之间的对比
    • vector优缺点:
    • list优缺点
  • 三、总结:模拟实现list的整体代码

一、list的模拟实现

三个类及其成员函数接口总览

namespace cl
{
	//模拟实现list当中的结点类
	template<class T>
	struct _list_node
	{
		//成员函数
		_list_node(const T& val = T()); //构造函数

		//成员变量
		T _val;                 //数据域
		_list_node<T>* _next;   //后继指针
		_list_node<T>* _prev;   //前驱指针
	};

	//模拟实现list迭代器类
	template<class T, class Ref, class Ptr>
	struct _list_iterator
	{
		typedef _list_node<T> node;
		typedef _list_iterator<T, Ref, Ptr> self;

		_list_iterator(node* pnode);  //构造函数

		//各种运算符重载函数
		self operator++();
		self operator--();
		self operator++(int);
		self operator--(int);
		bool operator==(const self& s) const;
		bool operator!=(const self& s) const;
		Ref operator*();
		Ptr operator->();

		//成员变量
		node* _pnode; //一个指向结点的指针
	};

	//模拟实现list
	template<class T>
	class list
	{
	public:
		typedef _list_node<T> node;
		typedef _list_iterator<T, T&, T*> iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;

		//默认成员函数
		list();
		list(const list<T>& lt);
		list<T>& operator=(const list<T>& lt);
		~list();

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		//访问容器相关函数
		T& front();
		T& back();
		const T& front() const;
		const T& back() const;

		//插入、删除函数
		void insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void push_back(const T& x);
		void pop_back();
		void push_front(const T& x);
		void pop_front();

		//其他函数
		size_t size() const;
		void resize(size_t n, const T& val = T());
		void clear();
		bool empty() const;
		void swap(list<T>& lt);

	private:
		node* _head; //指向链表头结点的指针
		size_t _size;//统计节点个数
	};
}

结点类的模拟实现

我们经常说list在底层实现时就是一个链表,更准确来说,list实际上是一个带头双向循环链表。
在这里插入图片描述
因此,我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址,于是该结点类的成员变量也就出来了(数据、前驱指针、后继指针)。

而对于该结点类的成员函数来说,我们只需实现一个构造函数即可。因为该结点类只需要根据数据来构造一个结点即可,而结点的释放则由list的析构函数来完成。

//结点类
template<class T>
struct list_node
{
	//成员变量
	list_node<T>* _next;//后继指针
	list_node<T>* _prev;//前驱指针
	T _data;//数据域
	
	//成员函数
	list_node(const T& val = T())//构造函数
		:_next(nullptr)
		, _prev(nullptr)
		, _data(val)
	{}
};

注意: 若构造结点时未传入数据,则默认以list容器所存储类型的默认构造函数所构造出来的值为传入数据。

迭代器类的模拟实现

迭代器类存在的意义:
之前模拟实现string和vector时都没有说要实现一个迭代器类,为什么实现list的时候就需要实现一个迭代器类了呢?

因为string和vector对象都将其数据存储在了一块连续的内存空间,我们通过指针进行自增、自减以及解引用等操作,就可以对相应位置的数据进行一系列操作,因此string和vector当中的迭代器就是原生指针。

但是对于list来说,其各个结点在内存当中的位置是随机的,并不是连续的,我们不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作。

迭代器的意义就是,让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问

既然list的结点指针的行为不满足迭代器定义,那么我们可以对这个结点指针进行封装,对结点指针的各种运算符操作进行重载,使得我们可以用和string和vector当中的迭代器一样的方式使用list当中的迭代器。例如,当你使用list当中的迭代器进行自增操作时,实际上执行了p = p->next语句,只是你不知道而已。

总结: list迭代器类,实际上就是对结点指针进行了封装,对其各种运算符进行了重载,使得结点指针的各种行为看起来和普通指针一样。(例如,对结点指针自增就能指向下一个结点)

迭代器类的模板参数说明

这里我们所实现的迭代器类的模板参数列表当中为什么有三个模板参数?

template<class T, class Ref, class Ptr>

在list的模拟实现当中,我们typedef了两个迭代器类型,普通迭代器和const迭代器。

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

这里我们就可以看出,迭代器类的模板参数列表当中的Ref和Ptr分别代表的是引用类型和指针类型。

当我们使用普通迭代器时,编译器就会实例化出一个普通迭代器对象;当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象。

若该迭代器类不设计三个模板参数,那么就不能很好的区分普通迭代器和const迭代器。

迭代器operator->的重载

某些情景下,我们使用迭代器的时候可能会用到->运算符。

//*的重载:返回节点的数据
Ref operator*()
{
    return _pnode->_data;
}
//->的重载:返回数据的指针
T* operator->()
{
    return &_pnode->_data;
}

例如:
在这里插入图片描述
但是operator->使用T*做返回值类型,这样无论是普通迭代器和const迭代器都能修改,所以operator->的返回值类型应该改为泛型:

template <class T, class Ref,class Ptr>
Ptr operator->()
{
    return &_pnode->_data;
}
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

迭代器模拟实现代码

//用类封装迭代器
// 同一个类模板实例化出的两个类型
// typedef __list_iterator<T, T&, T*> iterator;
// typedef __list_iterator<T, const T&, const T*> const_iterator;
template<class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef list_node<T> node;
	node* _pnode;
	typedef __list_iterator<T, Ref, Ptr> Self; //self是当前迭代器对象的类型:
	//Ref就表示当我需要使用*it时,我们返回的是const T&,还是T&,即支持两种迭代器
	//Ptr支持我们像指针一样使用->
	
	//迭代器类实际上就是对结点指针进行了封装,
	//其成员变量就只有一个,那就是结点指针,其构造函数直接根据所给结点指针构造一个迭代器对象即可。
	__list_iterator(node* p)
		:_pnode(p)
	{}

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

	// iterator it
	// *it
	// ++it;
	//当我们使用解引用操作符时,是想得到该位置的数据内容。
	//因此,我们直接返回当前结点指针所指结点的数据即可,但是这里需要使用引用返回,因为解引用后可能需要对数据进行修改。
	Ref operator*()
	{
		return _pnode->_data;
	}

	// ++it
	//前置++原本的作用是将数据自增,然后返回自增后的数据。
	//我们的目的是让结点指针的行为看起来更像普通指针,
	//那么对于结点指针的前置++,我们就应该先让结点指针指向后一个结点,然后再返回“自增”后的结点指针即可。
	Self& operator++()
	{
		_pnode = _pnode->_next;
		return *this;
	}

	// it++
	//对于后置++,我们则应该先记录当前结点指针的指向,然后让结点指针指向后一个结点,最后返回“自增”前的结点指针即可。
	Self operator++(int)
	{
		Self tmp(*this);
		_pnode = _pnode->_next;		
		return tmp;
	}
	
	//--it
	//对于前置- -,我们应该先让结点指针指向前一个结点,然后再返回“自减”后的结点指针即可。
	Self& operator--()
	{
		_pnode = _pnode->_prev;
		return *this;
	}
	//it--
	//对于后置- -,我们则应该先记录当前结点指针的指向,然后让结点指针指向前一个结点,最后返回“自减”前的结点指针即可。
	Self operator--(int)
	{
		Self tmp(*this);
		_pnode = _pnode->_prev;
		return tmp;
	}
	//判断这两个迭代器当中的结点指针的指向是否不同即可。
	bool operator!=(const Self& it) const
	{
		return _pnode != it._pnode;
	}
	//当使用==运算符比较两个迭代器时,
	//我们实际上想知道的是这两个迭代器是否是同一个位置的迭代器,也就是说,我们判断这两个迭代器当中的结点指针的指向是否相同即可。
	bool operator==(const Self& it) const
	{
		return _pnode == it._pnode;
	}
};

list的模拟实现

无参构造函数

list是一个带头双向循环链表,在构造一个list对象时,直接申请一个头结点,并让其前驱指针和后继指针都指向自己即可。

//构造函数
list()
{
	_head = new node(T()); //申请一个头结点,给一个缺省值,调用结点类的默认构造
	_head->_next = _head;//头结点的后继指针指向自己
	_head->_prev = _head; //头结点的前驱指针指向自己

	_size = 0;//结点个数为0
}

带参构造

list还提供一个迭代区间构造,首先创建一个头节点,然后循环遍历依次尾插到后面即可

template <class InputIterator>  
list(InputIterator first, InputIterator last)
{
	_head = new node(T()); //申请一个头结点,给一个缺省值,调用结点类的默认构造
	_head->_next = _head;//头结点的后继指针指向自己
	_head->_prev = _head; //头结点的前驱指针指向自己
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

拷贝构造函数

拷贝构造函数就是根据所给list容器,拷贝构造出一个对象。对于拷贝构造函数,我们先申请一个头结点,并让其前驱指针和后继指针都指向自己,然后将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面即可。

//拷贝构造函数
list(const list<T>& lt)
{
	_head = new node(T()); //申请一个头结点,给一个缺省值,调用结点类的默认构造
	_head->_next = _head; //头结点的后继指针指向自己
	_head->_prev = _head; //头结点的前驱指针指向自己
	for (const auto& e : lt)
	{
		push_back(e); //将容器lt当中的数据一个个尾插到新构造的容器后面
	}
}

赋值运算符重载函数

对于赋值运算符的重载,这里提供两种写法:

写法一:传统写法
这是比较容易理解的一种写法,先调用clear函数将原容器清空,然后将容器lt当中的数据,通过遍历的方式一个个尾插到清空后的容器当中即可。

//传统写法
list<T>& operator=(const list<T>& lt)
{
	if (this != &lt) //避免自己给自己赋值
	{
		clear(); //清空容器
		for (const auto& e : lt)
		{
			push_back(e); //将容器lt当中的数据一个个尾插到链表后面
		}
	}
	return *this; //支持连续赋值
}

写法二:现代写法
现代写法的代码量较少,首先利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数构造出来一个list对象,然后调用swap函数将原容器与该list对象进行交换即可。

//现代写法
list<T>& operator=(list<T> lt) //编译器接收右值的时候自动调用其拷贝构造函数
{
	swap(lt); //交换这两个对象
	return *this; //支持连续赋值
}

这样做相当于将应该用clear清理的数据,通过交换函数交给了容器lt,而当该赋值运算符重载函数调用结束时,容器lt会自动销毁,并调用其析构函数进行清理。

析构函数

对对象进行析构时,首先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空即可

//析构函数
~list()
{
	clear(); //清理容器
	delete _head; //释放头结点
	_head = nullptr; //头指针置空
}

begin和end

首先我们应该明确的是:begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。

对于list这个带头双向循环链表来说,其第一个有效数据的迭代器就是使用头结点后一个结点的地址构造出来的迭代器,而其最后一个有效数据的下一个位置的迭代器就是使用头结点的地址构造出来的迭代器。(最后一个结点的下一个结点就是头结点)

iterator begin()
{
	//返回使用头结点后一个结点的地址构造出来的普通迭代器
	return iterator(_head->_next);
}
iterator end()
{
	//返回使用头结点的地址构造出来的普通迭代器
	return iterator(_head);
}

上方是普通的迭代器的begin和end函数,可读可写,我们还需实现const迭代器,只允许读操作

const_iterator begin() const
{
	//返回使用头结点后一个结点的地址构造出来的const迭代器
	return const_iterator(_head->_next);
}
const_iterator end() const
{
	//返回使用头结点的地址构造出来的普通const迭代器
	return const_iterator(_head);
}

insert

insert函数可以在所给迭代器之前插入一个新结点。

先根据所给迭代器得到该位置处的结点指针cur,然后通过cur指针找到前一个位置的结点指针prev,接着根据所给数据x构造一个待插入结点,之后再建立新结点与cur之间的双向关系,最后建立新结点与prev之间的双向关系即可。

iterator insert(iterator pos, const T& x)
{
	//创建一个新节点
	node* newnode = new node(x);//根据所给数据x构造一个待插入结点
	
	node* cur = pos._pnode;//迭代器pos处的结点指针
	node* prev = cur->_prev;//迭代器pos前一个位置的结点指针

	// prev newnode cur
	//建立newnode与prev之间的双向关系
	prev->_next = newnode;
	newnode->_prev = prev;
	//建立newnode与cur之间的双向关系
	newnode->_next = cur;
	cur->_prev = newnode;

	++_size;

	return iterator(newnode);
}

erase

erase函数可以删除所给迭代器位置的结点。

先根据所给迭代器得到该位置处的结点指针cur,然后通过cur指针找到前一个位置的结点指针prev,以及后一个位置的结点指针next,紧接着释放cur结点,最后建立prev和next之间的双向关系即可。

iterator erase(iterator pos)
{
	assert(pos != end());//删除的结点不能是头结点

	node* prev = pos._pnode->_prev;//迭代器pos前一个位置的结点指针
	node* next = pos._pnode->_next;//迭代器pos后一个位置的结点指针

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

	delete pos._pnode;
	--_size;

	return iterator(next);//返回所给迭代器pos的下一个迭代器
}

list的迭代器失效问题

  • insert,迭代器不失效
  • earse失效(因为删除pos节点后,pos所指向的空间已经不在了,所以需要返回pos节点的下一个节点)

push_back和pop_back

push_back和pop_back函数分别用于list的尾插和尾删,在已经实现了insert和erase函数的情况下,我们可以通过复用函数来实现push_back和pop_back函数。

push_back函数就是在头结点前插入结点,而pop_back就是删除头结点的前一个结点。

void push_back(const T& x)
{
	//规范写法
	//node* newnode = new node(x);
	//node* tail = _head->_prev;
	 _head         tail   newnode
	//tail->_next = newnode;
	//newnode->_prev = tail;
	//newnode->_next = _head;
	//_head->_prev = newnode;
	
	//复用insert
	insert(end(), x);
}
void pop_back()
{
	erase(--end());//删除头结点的前一个结点
}

push_front和pop_front

当然,用于头插和头删的push_front和pop_front函数也可以复用insert和erase函数来实现。

push_front函数就是在第一个有效结点前插入结点,而pop_front就是删除第一个有效结点。

//头插
void push_front(const T& x)
{
	insert(begin(), x); //在第一个有效结点前插入结点
}
//头删
void pop_front()
{
	erase(begin()); //删除第一个有效结点
}

clear

clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点即可。

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

empty

empty函数用于判断容器是否为空,因为我们list的成员变量中有一个_size,所以我们只需判断_size是否为0即可

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

size

size函数用于统计容器中数据个数,我们直接返回_size即可

size_t size() const
{
	return _size;
}

swap

swap函数用于交换两个容器,list容器当中存储的实际上就只有链表的头指针,我们将这两个容器当中的头指针交换即可

void swap(list<T>& lt)
{
	std::swap(_head, lt._head); //交换两个容器当中的头指针即可
	std::swap(_size, lt._size);
}

二、list与vector之间的对比

vector优缺点:

优点:
1、支持下标的随机访问;

2、尾插尾删效率高(当然扩容的那一次尾插会较慢);

3、CPU高速缓存命中高(数据从缓存加载至CPU中,会加载连续的一段数据,vector因为结构连续,高速缓存命中高)。

缺点
1、头部或中间插入删除数据效率低(O(N))
2、 扩容有消耗,还存在一定的空间浪费

vector迭代器失效问题:

insert/erase均失效。(如果string的insert和erase形参是迭代器,那么也会失效,但是大部分接口是下标传参,不考虑失效问题,只有几个接口是迭代器传参,需要注意迭代器失效问题)

list优缺点

优点
1、按需申请释放,无需扩容;
2、任意位置插入删除时间O(1);(这里说的是插入删除,不要加上查找的时间)

缺点
1、不支持下标的随机访问
2、CPU高速缓存命中率低;

list迭代器失效问题:

insert不失效,erase失效。

三、总结:模拟实现list的整体代码

namespace wyt
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

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

	// 同一个类模板实例化出的两个类型
	// typedef __list_iterator<T, T&, T*> iterator;
	// typedef __list_iterator<T, const T&, const T*> const_iterator;
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> Self;
		node* _pnode;

		__list_iterator(node* p)
			:_pnode(p)
		{}


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

		// iterator it
		// *it
		// ++it;
		Ref operator*()
		{
			return _pnode->_data;
		}

		// const iterator cit
		// *cit
		// ++cit 这样的话,可以解引用,但是不能++
		/*const T& operator*() const
		{
		return _pnode->_data;
		}*/

		// ++it
		Self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}

		// it++
		Self operator++(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}

		Self& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}

		bool operator!=(const Self& it) const
		{
			return _pnode != it._pnode;
		}

		bool operator==(const Self& it) const
		{
			return _pnode == it._pnode;
		}
	};

	// 跟普通迭代器的区别:遍历,不能用*it修改数据
	/*template<class T>
	struct __list_const_iterator
	{
		typedef list_node<T> node;
		node* _pnode;

		__list_const_iterator(node* p)
			:_pnode(p)
		{}

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

		__list_const_iterator<T>& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}

		__list_const_iterator<T>& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}

		bool operator!=(const __list_const_iterator<T>& it)
		{
			return _pnode != it._pnode;
		}
	};*/

	//vector<int>
	//vector<string>
	//vector<vector<int>>

	// 类名  类型
	// 普通类  类名 等价于 类型
	// 类模板  类名  不等价于  类型
	// 如:list模板 类名list  类型list<T> 
	// 类模板里面可以用类名代表类型,但是建议不要那么用
	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		//typedef __list_const_iterator<T> const_iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

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

		const_iterator end() const
		{
			return const_iterator(_head);
		}

		iterator begin()
		{
			return iterator(_head->_next);
		}

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

		void empty_initialize()
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;

			_size = 0;
		}

		list()
		{
			empty_initialize();
		}

		 lt2(lt1)
		//list(const list<T>& lt)
		//{
		//	empty_initialize();

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

		 lt1 = lt3
		//list<T>& operator=(const list<T>& lt)
		//{
		//	if (this != &lt)
		//	{
		//		clear();
		//		for (const auto& e : lt)
		//		{
		//			push_back(e);
		//		}
		//	}

		//	return *this;
		//}

		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_initialize();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

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

		// 现在写法
		// lt2(lt1)
		list(const list<T>& lt)
			//list(const list& lt) // 不建议
		{
			empty_initialize();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		// lt3 = lt1
		list<T>& operator=(list<T> lt)
			//list& operator=(list lt) // 不建议
		{
			swap(lt);
			return *this;
		}

		size_t size() const
		{
			return _size;
		}

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

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

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

		void push_back(const T& x)
		{
			//node* newnode = new node(x);
			//node* tail = _head->_prev;
			 _head         tail   newnode
			//tail->_next = newnode;
			//newnode->_prev = tail;
			//newnode->_next = _head;
			//_head->_prev = newnode;

			insert(end(), x);
		}

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

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

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

		iterator insert(iterator pos, const T& x)
		{
			node* newnode = new node(x);
			node* cur = pos._pnode;
			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)
		{
			assert(pos != end());

			node* prev = pos._pnode->_prev;
			node* next = pos._pnode->_next;

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

			delete pos._pnode;
			--_size;

			return iterator(next);
		}

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

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

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

相关文章

WordPress添加阿里云OSS对象云储存配置教程

背景&#xff1a;随着页面文章增多&#xff0c;内置图片存储拖连网站响应速度&#xff0c;这里对我来说主要是想提升速度 目的&#xff1a;使用第三方云存储作为图片外存储(图床)&#xff0c;这样处理可以为服务器节省很多磁盘空间&#xff0c;在网站搬家的时候减少文件迁移的工…

【数据结构】堆(笔记总结)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;数据结构 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&…

MySQL--数据库基础--0406

目录 1.什么是数据库&#xff1f; 2. 基本使用 2.1 连接服务器 2.2 数据库的操作在Linux中的体现 2.3 使用案例 3.服务器&#xff0c;数据库&#xff0c;表关系 4.数据逻辑存储 5.SQL的分类 6.存储引擎 1.什么是数据库&#xff1f; 数据库和文件 文件或者数据库&…

OK-MX93开发板-实现Web页面无线点灯

上篇文章&#xff1a;i.MX9352——介绍一款多核异构开发板&#xff0c;介绍了OK-MX9352开发板的基础硬件功能。 本篇来使用OK-MX9352开发板&#xff0c;通过Web界面进行点灯测试&#xff0c;最终的效果如下&#xff1a; 在进行代码编写之前&#xff0c;先在Ubuntu虚拟机上把这…

对比损失Contrastive Loss(CVPR 2006)原理解析

paper&#xff1a;http://yann.lecun.com/exdb/publis/pdf/hadsell-chopra-lecun-06.pdf 本文提出的对比损失contrastive loss被广泛应用于自监督模型中&#xff0c;但最初对比损失是作为一个特征降维方法而提出的。 摘要 降维是学习一种映射关系&#xff0c;通过这种映射关…

day10 线程池及gdb调试多线程

目录 线程池的概念 概念&#xff1a; 必要性&#xff1a; 线程池的基本结构&#xff1a; 线程池的实现 完整代码 线程的GDB调试 线程池的概念 概念&#xff1a; 通俗的讲就是一个线程的池子&#xff0c;可以循环的完成任务的一组线程集合&#xff1b; 必要性&#xff…

【软件工程】为什么要选择软件工程专业?

个人主页&#xff1a;【&#x1f60a;个人主页】 文章目录前言软件工程&#x1f4bb;&#x1f4bb;&#x1f4bb;就业岗位&#x1f468;‍&#x1f4bb;&#x1f468;‍&#x1f4bb;&#x1f468;‍&#x1f4bb;就业前景&#x1f6e9;️&#x1f6e9;️&#x1f6e9;️工作环…

趣谈之什么是 API 货币化?

本文介绍了 API 货币化和 APISIX 实现 API 货币化方法。 作者刘维&#xff0c;API7.ai 技术工程师&#xff0c;Apache APISIX Contributor 原文链接 什么是 API 货币化 想象你开发并部署了一个服务&#xff0c;能够搜集你所在城市所有超市的打折和优惠信息&#xff0c;其他的…

C生万物 | 校招热门考点 —— 结构体内存对齐

文章目录一、前言结构体偏移量计算&#xff1a;offsetof二、规则介绍例题的分解与细说三、习题演练1、练习①2、练习②四、为什么存在内存对齐?1、平台原因(移植原因)2、性能原因五、如何修改默认对齐数六、实战演练✍一道百度笔试题&#xff1a; offsetof 宏的实现&#x1f4…

深度学习基础篇之深度神经网络(DNN)

神经网络不应该看做是一个算法&#xff0c;应该看做是一个特征挖掘方法。在实际的业界发展过程中&#xff0c;数据的作用往往大于模型&#xff0c;当我们把数据的隐藏特征提取出来之后&#xff0c;用很简单的模型也能预测的很好。 神经网络模型由生物神经中得到启发。在生物神…

【Linux】Makefile的简述

目录 前言&#xff1a; 一、Makefile的规则 二、Makefile的函数语法 &#xff08;1&#xff09;通配符pattern ​&#xff08;2&#xff09; 删除clean ​&#xff08;3&#xff09; 立即变量、延时变量 &#xff08;4&#xff09; Makefile常用函数 3-1.Makefile要达到…

第11章_常用类和基础API

第11章_常用类和基础API 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. 字符串相关类之不可变字符序列&#xff1a;String 1.1 String的特性 java.lang.String 类代表字符串…

vue之--使用TypeScript

搭配 TypeScript 使用 Vue​ 像 TypeScript 这样的类型系统可以在编译时通过静态分析检测出很多常见错误。这减少了生产环境中的运行时错误&#xff0c;也让我们在重构大型项目的时候更有信心。通过 IDE 中基于类型的自动补全&#xff0c;TypeScript 还改善了开发体验和效率。…

2023年美赛春季赛 Y题详细思路

由于今年各种各样的原因&#xff0c;导致美赛头一次&#xff0c;据说也将是最后一次&#xff0c;临时调整&#xff0c;加设春季赛。这对于急需建模奖项的大家来说是一个很好的机会。无论怎样的原因&#xff0c;今年美赛我们可能有所遗憾。但&#xff0c;春季赛也许就是弥补遗憾…

HTML4.1表单标签

input&#xff08;登录、注册、搜索功能&#xff09; type属性值类型称号展示类型text文本框placeholder占位符password密码框placeholder占位符radio单选框name checked&#xff08;默认选中&#xff09;同类型单选checkbox复选框 checked file 文件选择multiple多文件选择s…

分类预测 | MATLAB实现CNN-GRU-Attention多输入分类预测

分类预测 | MATLAB实现CNN-GRU-Attention多输入分类预测 目录分类预测 | MATLAB实现CNN-GRU-Attention多输入分类预测分类效果模型描述程序设计参考资料分类效果 模型描述 Matlab实现CNN-GRU-Attention多变量分类预测 1.data为数据集&#xff0c;格式为excel&#xff0c;12个输…

集合-LinkedList

LinkedList LinkedList的概述 LinkedList的底层使用双向链表实现。 链表是一种线性数据结构&#xff0c;其中每个元素都是一个单独的对象&#xff0c;包含一个指向列表中下一个节点的引用。 它可以用于实现各种抽象数据类型&#xff0c;例如列表、堆栈、队列等。 LinkedLis…

安装Nginx——docker安装

使用docker安装Nginx 1.开启docker systemctl start docker docker search nginx[rootlocalhost ~]# systemctl start docker //开启docker [rootlocalhost ~]# docker search nginx //搜素镜像 2. docker pull nginxdocker imagesdocker run -…

如何将录音转成文字?

在现代社会中&#xff0c;随着语音技术的不断发展和普及&#xff0c;将录音转换为文字变得越来越容易。无论是为了记录会议、面试、讲座、采访&#xff0c;还是为了记录个人的思考和想法&#xff0c;将录音转换为文字是一种方便快捷的方式。本文将介绍几种将录音转换为文字的方…

计算机系统结构教程-第七章-储存系统笔记(1)

7.1储存系统的层次结构 概述 理想的储存器&#xff1a;容量大、速度快、价格低。 但以上三种要求在实际应用中是相互矛盾&#xff1a; &#xff08;1&#xff09;速度快&#xff0c;价格高。 &#xff08;2&#xff09;容量大&#xff0c;价格低。 &#xff08;3&#xf…