【c++】list 模拟

news2024/12/27 13:22:02

 > 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能手撕list模拟

> 毒鸡汤:不为模糊不清的未来过分担忧,只为清清楚楚的现在奋发图强。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言

        前面我们已经学习了list的相关知识点,必然我们要简单的模拟一下,模拟list类比较复杂,里面掺杂了我们学习双链表的知识点,对模板的使用更加复杂,还有对c++类和对象的知识点使用起来更杂。对上面知识点忘了的小伙伴们大家先去巩固巩固,学习list那就更加轻松,那咱们走起。

 ⭐主体

这里我们就不分解成三文件啦,这里就创建两个文件List.h(头文件),Test.c(测试代码文件)

🌙list基本框架结构

list为任意位置插入删除的容器,底层为带头双向循环链表

模拟实现list,要实现下列三个类:

①、模拟实现结点类
②、模拟实现迭代器的类
③、模拟list主要功能的类


🌙list的结点的实现

list为任意位置插入删除的容器,底层为带头双向循环链表,所以每个结点都需有以下成员:

  1. 前驱指针
  2. 后继指针
  3. data值存放数据

结点类本质上是一个初始化函数,在list类中只需要一个构造函数就行。

// 初始化(结点类)
template<class T> // 模板
struct ListNode
{
	// 定义两个指针
	ListNode<T>* _next;  // 前驱指针 
	ListNode<T>* _prev;  // 后驱指针
	// 存储节点值
	T _data; 

	// 初始化列表(构造函数)
	ListNode(const T& x = T())
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
	{}
};

问题解剖:为什么是ListNode<T>???

首先,C++中用struct定义时可不加struct,重点是这里用了一个类模板,类模板的类名不是真正的类型且类模板不支持自动推类型,即ListNode不是真正的类型,定义变量时ListNode<T>这种才是真正的类型,也就是用类模板定义变量时必须 指定对应的类型 。 注:结构体模板或类模板在定义时可以不加 <T>,但使用时必须加 <T>。

🌙list的迭代器类的实现

  1. 链表的物理空间是不连续的,是通过结点的指针顺次链接
  2. 不能像先前的string和vector一样直接解引用去访问其数据,
  3. 结点的指针解引用还是结点,结点指针++还是结点指针,
  4. 在string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。

为了能让list像vector一样解引用后访问对应节点中的值,++访问到下一个数据,我们需要单独写一个迭代器类的接口实现,在其内部进行封装补齐相应的功能,而这就要借助运算符重载来完成。 

💫基本框架

框架结构:

template<class T,class Ref,class Ptr>
struct __list_iterator
{
    typedef __list_node<T> Node;
    typedef __list_iterator<T, Ref, Ptr> Self;
    Node* _node;
}

①、迭代器类模板为什么有三个参数?

  1. 普通迭代器的话,一个class T参数就够了,由于const迭代器原因,需要加两个参数
  2. 两个参数名Ref(reference:引用)和Ptr(pointer:指针)

②、迭代器类是什么?

  • 迭代器类就一个节点的指针变量_node,但是因为我们要运算符重载等一系列操作,不得不把list的迭代器写成类,完成那些操作,list的迭代器才能正确的++到下一位置,解引用访问节点的值

③、节点指针和迭代器的区别?

  1. Node* cur(节点指针)  和 iterator it(迭代器),它们指向同一个节点,它们的物理地址是一样的,但是它们的类型不同。
  2. *cur是一个指针的解引用,取到的值是节点。
  3. *it是去调用迭代器的operator*,返回值是节点中存的值。
  4. 类型决定了对空间的解释权。

💫构造函数

 迭代器的构造函数只需要一个指针构造就行:

// 构造函数(初始化)
_list_iterator(Node* x)
	:_node(x)
{}

 💫operator*

*it(调用的是函数,返回节点中的值):

// *it,返回节点的值
Ref operator*()
{
	return _node->_data;
}

问题解剖:返回值为什么是 Ref ???

  1. Ref是模板参数,因为迭代器类的模板参数Ref传入的要么是T&要么是const T&
  2. 为了const迭代器和普通迭代器的同时实现,意义就是一个只读,一个可读可写 

注:比如之前讲的vector的迭代器,*it(假设it是迭代器变量)就是拿到对应的值,那么list的迭代器也要同理,解引用迭代器就是为了访问对应位置的值,那么list只要通过迭代器返回对应节点的值就好了(*it,我们是就想要对应的值)

 💫operator->

// 返回节点值
Ptr operator->()
{
	return &_node->data;
}

问题解剖:为什么需要operator->?

  •  list存了个自定义类型的Date类,程序错误,因为我们并没有重载Date类的operator<<

若是内置类型的话才可以正常输出,那写一个operator<<重载吗?不,因为你无法确定要用哪些类,也不能每个类都写operator<<,那怎么办?我们访问Date类本质是想访问它内置类型(int)的_year、_month和_day吧,那我们不妨写个专属于自定义类型的operator->(因为内置类型只需要*it就可以直接输出了,但自定义类型不可以直接输出)

  • 利用operator->直接访问类的成员变量,而内置类型可以直接输出

故从根源上解决问题: 在迭代器中实现个operator->:

(Ptr是迭代器的模板参数,我们用来作为T*或const T*的)​​​​​​​

 💫operator前置++--与后置++--

代码实现:

// ++it
self& operator++()
{
	_node = _node->_next;
	// 返回节点
	return *this;
}
// it++
self operator++(int)
{
	// 拷贝
	self tmp(*this);
	// 向后走一个节点
	_node = _node->_next;
	// 返回
	return tmp;
}

// --it
self& operator--()
{
	_node = _node->_prev;
	// 返回节点
	return *this;
}
// it--
self operator--(int)
{
	// 拷贝
	self tmp(*this);
	// 向前走一个节点
	_node = _node->prev;
	// 返回
	return tmp;
}

问题解剖:迭代器++对于list是什么意思???

  1. 迭代器++的意思就是想让其指向下一个节点
  2. --正好相反,为了区分前置和后置++(--),
  3. 我们会用函数重载,也就是多一个'没用的'参数:int,这个参数没什么用,只是为了区分++与--

💫operator==和operator!=

代码实现:

// 判断
bool operator!=(const self& s)
{
	return _node != s._node;
}
// 判断
bool operator==(const self& s)
{
	return _node == s._node;
}

问题解剖:两个迭代器怎么比较的???

  1. 迭代器中就一个成员变量_node,节点指针,只要比较当前的节点指针是否相同即可
  2. 这个操作在判断迭代器是否走到头有用(比如 it != s.end())

问题:迭代器的拷贝构造和赋值和析构函数需我们自己实现吗?

 答:不需要

  • 因为迭代器存的就是一个节点指针,而节点是属于链表list的,那么它的释放应由list来实现,那么迭代器的析构也无需我们自己写了。
  • 拷贝构造和赋值就是节点指针的拷贝和赋值,即使指向同一节点了,迭代器也不会析构,而list的析构到时候只会释放这个节点一次,不存在什么问题,即我们无需深拷贝,使用系统提供的浅拷贝即可。

🌙list类的实现

 在结点类和迭代器类都实现的前提下,就可实现list主要功能:增删等操作的实现。

💫基本框架

框架结构:

//3、链表类
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;
 
private:
    Node* _head;//头结点
}

问题解剖:const_iterator(const迭代器的介绍)

  1. 我们知道const_iterator在begin()和end()中的返回值是需要用到的
  2. 其主要作用就是当迭代器只读时使用
  3. 普通迭代器和const迭代器的实现区别仅仅在于内部成员函数的返回值不同
  4. 一个是引用class Ref(T&或const T&),一个是指针class Ptr(T*或const T*)
  5. 当Ref时const T&就是const迭代器的调用,当Ref时T& 时就是普通迭代器的调用

💫构造函数

框架结构:

// 初始化函数
void empty_init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
}

// 构造函数
list() // 1.
{
	empty_init();
}

解释:我们开辟一个头结点,这里就是一个循环双向链表。

💫begin和end

代码实现:

// 找头,这里有哨兵位
iterator begin()
{
	return _head->_next;
}
// 找尾,哨兵位就是尾
iterator end()
{
	return _head;
}

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

💫拷贝构造

代码实现:

传统写法:

//拷贝构造:传统写法
list(const list<T>& lt)
{
    _head = new Node;//开辟一样的头结点
    _head->_next = _head;
    _head->_prev = _head;
    //1、用迭代器遍历
    /*const_iterator it = lt.begin();
    while (it != lt.end())
    {
        push_back(*it);
        ++it;
    }*/
 
    //2、范围for遍历
    //遍历lt1,把lt1的元素push_back到lt2里头
    for (auto e : lt)
    {
        push_back(e);//自动开辟新空间,完成深拷贝
    }
}

解释:list的拷贝构造跟vector不同,它的拷贝是拷贝一个个节点(因为不连续的物理空间), 那么我们可以用迭代器拿到list的一个个节点【上面是传统写法】

现代写法:

template<class InputIterator>
list(InputIterator first, InputIterator last) {
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;

	while (first != last) {
		push_back(*first);
		first++;
	}
}
/* 拷贝构造(现代写法):L2(L1) */
list(const list<T>& L) {
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;

	list<T> tmp(L.begin(), L.end());
	swap(_head, tmp._head);  // 交换两个结点的指针
}

💫clear函数

代码实现:

// 销毁函数
void clear()
{
	// 找头
	iterator it = begin();
	// 循环
	while (it != end())
	{
		it = erase(it);
	}
}

问题解剖:it = erase(it)什么意思???

防止迭代器失效,因为erase返回的是被删除位置的下一位置,比如删除pos位置的,pos位置被删除后,这个位置的迭代器就失效了,那就无法通过++找到后面的位置了,所以我们通过erase返回值来更新一下迭代器位置,即更新到被删除位置的下一位置

💫operator=

代码实现:

        
	//赋值运算符重载(传统写法)
	//lt1 = lt3
	//list<T>& operator=(const list<T>& lt)
	//{
	//	if (this != &lt)
	//	{
	//		clear();//先释放lt1
	//		for (auto e : lt)
	//			push_back(e);
	//	}
	//	return *this;
	//}
 
	//赋值运算符重载(现代写法)
    // 赋值运算重载
    list<T>& operator=(list<T> lt)
    {
	    swap(lt);
	    return *this;
    }

注释:套用传值传参拷贝构造完成深拷贝

💫析构函数

代码实现:

// 析构函数
~list()
{
	clear();

	delete _head;//删除除头结点以外的节点
	_head = nullptr;//删去哨兵位头结点

}

💫insert函数

代码实现:

// insert插入函数
iterator insert(iterator pos, const T& x)
{
	Node* cur = pos._node; // 迭代器pos处的结点指针
	Node* prev = cur->_prev;
	Node* newnode = new Node(x); // 创建新的结点 

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

	//return iterator(newnode);
	return newnode;
}

问题解剖:返回参数为什么是iterator???本质是为了防止迭代器失效问题

注:insert指的是插入到指定位置的前面

💫push_back 和 push_front

代码实现:

// 调用插入函数,尾插
void push_back(const T& x)
{

	insert(end(), x);
}
// 调用插入函数,头插
void push_front(const T& x)
{
	insert(begin(), x);
}

这里的尾插和头插直接调用插入函数就可以了。

💫erase函数

代码实现:

//erase
iterator erase(iterator pos)
{
	assert(pos != end());
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
	//prev cur next
	//链接prev和next
	prev->_next = next;
	next->_prev = prev;
	//delete删去结点,因为每一个节点都是动态开辟出来的
	delete cur;
	//返回被删除元素后一个元素的迭代器位置
	//return next;
	return iterator(next);
}

问题剖析:返回值问题???

erase的返回值,返回的是被删除位置的下一位置,库里面这么规定的,本质是因为删除元素一定会导致此位置的迭代器失效,故返回被删除位置的下一次位置可以更新迭代器。

💫pop_back 和 pop_front

代码实现:

// 调用删除函数,尾删
void pop_back()
{
	erase(--end());
}
// 调用删除函数,头删
void pop_front()
{
	erase(begin());
}

🌙完整源代码

list.h:

#pragma once

#include<assert.h>
#include<iostream>

namespace lyk
{
	// 初始化
	template<class T> // 模板
	struct ListNode
	{
	// 定义两个指针
	ListNode<T>* _next;  // 前驱指针 
	ListNode<T>* _prev;  // 后驱指针
	// 存储节点值
	T _data; 

	// 初始化列表(构造函数)
	ListNode(const T& x = T())
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
	{}
	};

	// list迭代器
	template<class T,class Ref,class Ptr> // 第二个参数是引用,第三个参数是指针,防止迭代器失效
	struct _list_iterator
	{
		typedef ListNode<T> Node;
		typedef _list_iterator<T, Ref, Ptr> self;
		Node* _node; // 定义节点

		// 构造函数(初始化)
		_list_iterator(Node* x)
			:_node(x)
		{}

		// ++it
		self& operator++()
		{
			_node = _node->_next;
			// 返回节点
			return *this;
		}
		// it++
		self operator++(int)
		{
			// 拷贝
			self tmp(*this);
			// 向后走一个节点
			_node = _node->_next;
			// 返回
			return tmp;
		}

		// --it
		self& operator--()
		{
			_node = _node->_prev;
			// 返回节点
			return *this;
		}
		// it--
		self operator--(int)
		{
			// 拷贝
			self tmp(*this);
			// 向前走一个节点
			_node = _node->prev;
			// 返回
			return tmp;
		}

		// *it,返回节点的值
		Ref operator*()
		{
			return _node->_data;
		}

		// 返回节点值
		Ptr operator->()
		{
			return &_node->data;
		}

		// 判断
		bool operator!=(const self& s)
		{
			return _node != s._node;
		}
		// 判断
		bool operator==(const self& s)
		{
			return _node == s._node;
		}
	};

	// list的实现(包含各种函数实现)
	template<class T>
	class list
	{
		typedef ListNode<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;
		}
		// 找尾,哨兵位就是尾
		iterator end()
		{
			return _head;
		}

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

		// 初始化函数
		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		// 构造函数
		list() // 1.
		{
			empty_init();
		}
		list(list<T>& It) // 2.拷贝构造 传统写法
		{
			empty_init();

			// 尾插
			for (const auto& x : It)
			{
				push_back(x);
			}
		}
		/* 拷贝构造(现代写法):L2(L1) */
		list(const list<T>& L) {
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;

			list<T> tmp(L.begin(), L.end());
			swap(_head, tmp._head);  // 交换两个结点的指针
		}

		// 销毁函数
		void clear()
		{
			// 找头
			iterator it = begin();
			// 循环
			while (it != end())
			{
				it = erase(it);
			}
		}

		// 析构函数
		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		// 交换
		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}

		// 赋值运算重载
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		// 调用插入函数,尾插
		void push_back(const T& x)
		{

			insert(end(), x);
		}
		// 调用插入函数,头插
		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		// 调用删除函数,尾删
		void pop_back()
		{
			erase(--end());
		}
		// 调用删除函数,头删
		void pop_front()
		{
			erase(begin());
		}

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

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

			//return iterator(newnode);
			return newnode;
		}

		// erase删除函数
		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;

			return next;
		}

	private:
		Node* _head;  // 头节点
	};

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

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

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


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

		lt.push_back(5);
		lt.push_front(0);

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

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

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

		lt.clear();

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

		lt.push_back(10);
		lt.push_back(20);

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

	void test_list3()
	{
		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> copy(lt);
		for (auto e : copy)
		{
			cout << e << " ";
		}
		cout << endl;

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

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

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

			cout << *it << " ";
			++it;
		}
		cout << endl;

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

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

		print_list(lt);


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

			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}

 test.cpp:

#define _CRT_SECURE_NO_WARNINGS 1

// 包含头文件
#include<iostream>
#include<list>
#include<vector>
#include<algorithm>
using namespace std;

#include"List.h"

int main()
{

	lyk::test_list4();

	return 0;
}

  🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

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

相关文章

html从零开始8:css3新特性、动画、媒体查询、雪碧图、字体图标【搬代码】

css3新特性 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, …

vue导出word文档(图文示例)

第076个 查看专栏目录: VUE 本文章目录 示例说明示例效果图导出的文件效果截图示例源代码参数说明&#xff1a;重要提示&#xff1a;API 参考网址 示例说明 在Vue中导出Word文档&#xff0c;可以使用第三方库file-saver和html-docx-js。首先需要安装这两个库&#xff1a; npm …

Java微服务学习Day2

文章目录 Nacos配置管理统一配置管理配置热更新![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c8a2d17baeef411980b44b432eb9692a.png)配置共享搭建Nacos集群 Feign远程调用介绍自定义配置性能优化最佳实践 Gateway服务网关介绍搭建网关服务路由断言工厂路由过滤器…

Python hash函数

在Python编程中&#xff0c;hash()函数是一个重要的内置函数&#xff0c;用于计算对象的哈希值。哈希值是一种由固定长度的字符串表示的数据摘要&#xff0c;通常用于在散列表中快速查找、比较对象或数据完整性验证等场景。本文将深入探讨Python中的hash()函数&#xff0c;包括…

【Linux网络编程五】Tcp套接字编程(四个版本服务器编写)

【Linux网络编程五】Tcp套接字编程(四个版本服务器编写&#xff09; [Tcp套接字编程]一.服务器端进程&#xff1a;1.创建套接字2.绑定网络信息3.设置监听状态4.获取新连接5.根据新连接进行通信 二.客户端进程&#xff1a;1.创建套接字2.连接服务器套接字3.连接成功后进行通信 三…

2.15练习

选择题 1. B2. B3. C4. D5. A6. B7. C8. B9. D10. B11. A12. B 填空题 1. a b c 2.string s: I like it. String t:A like it. 3. 30 10 30 n Learning

云计算基础-虚拟机迁移原理

什么是虚拟机迁移 虚拟机迁移是指将正在运行的虚拟机实例从一个物理服务器&#xff08;或主机&#xff09;迁移到另一个物理服务器&#xff08;或主机&#xff09;的过程&#xff0c;而不会中断虚拟机的运行。 虚拟机拟机迁移分类虚 热迁移&#xff1a;开机状态下迁移 冷迁…

“分布式透明化”在杭州银行核心系统上线之思考

导读 随着金融行业数字化转型的需求&#xff0c;银行核心系统的升级改造成为重要议题。杭州银行成功上线以 TiDB 为底层数据库的新一代核心业务系统&#xff0c;该实践采用应用与基础设施解耦、分布式透明化的设计开发理念&#xff0c;推动银行核心系统的整体升级。 本文聚焦…

H12-821_44

44.如图所示的网络,R1设备配置路由渗透,那么R1设备把Level-2的LSP发送给R3,使R3可以获知全网路由。 A.正确 B.错误 答案&#xff1a;B 注释&#xff1a; 感觉题目描述有两个问题&#xff1a; 1. R3是Level-1-2路由器&#xff0c;本来就可以学习到Level-2的路由。题目中的R3应该…

第七篇【传奇开心果系列】Python微项目技术点案例示例:数据可视化界面图形化经典案例

传奇开心果微博系列 系列微博目录Python微项目技术点案例示例系列 微博目录一、微项目开发背景和项目目标&#xff1a;二、雏形示例代码三、扩展思路介绍四、数据输入示例代码五、数据分析示例代码六、排名统计示例代码七、数据导入导出示例代码八、主题定制示例代码九、数据过…

微信网页版能够使用(会顶掉微信app的登陆)

一、文件结构 新建目录chrome新建icons&#xff0c;其中图片你自己找吧新建文件manifest.json新建文件wx-rules.json 二、文件内容 对应的png你们自己改下 1、manifest.json {"manifest_version": 3,"name": "wechat-need-web","author…

[BUUCTF]-PWN:ciscn_2019_es_1解析(tcachebin duf)

查看保护 再查看ida 大致为alloc创建堆块&#xff0c;free释放堆块&#xff0c;show输出堆块内容 但是要注意一点free没有清空堆块指针 完整exp&#xff1a; from pwn import* from LibcSearcher import* pprocess(./es1) premote(node5.buuoj.cn,26841)def alloc(size,cont…

基于剪贴板的文件传输方案

文章目录 背景原理步骤获取待上传文件的十六进制数据格式转换输出 背景 某次误删了环境上的C库之后想到的 什么都不可用了&#xff0c;但当前的ssh连接还在&#xff0c;echo命令和重定向符还可以使用 这就催生了我的想法&#xff1a;直接用echo -en “\xab\xcd” > file这样…

DataX源码分析-插件机制

系列文章目录 一、DataX详解和架构介绍 二、DataX源码分析 JobContainer 三、DataX源码分析 TaskGroupContainer 四、DataX源码分析 TaskExecutor 五、DataX源码分析 reader 六、DataX源码分析 writer 七、DataX源码分析 Channel 八、DataX源码分析-插件机制 文章目录 系列文章…

HTML | DOM | 网页前端 | 常见HTML标签总结

文章目录 1.前端开发简单分类2.前端开发环境配置3.HTML的简单介绍4.常用的HTML标签介绍 1.前端开发简单分类 前端开发&#xff0c;这里是一个广义的概念&#xff0c;不单指网页开发&#xff0c;它的常见分类 网页开发&#xff1a;前端开发的主要领域&#xff0c;使用HTML、CS…

活用 Composition API 核心函数,打造卓越应用(下)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

C#根据权重抽取随机数

&#xff08;游戏中一个很常见的简单功能&#xff0c;比如抽卡抽奖抽道具&#xff0c;或者一个怪物有多种攻击动作&#xff0c;按不同的权重随机出个攻击动作等等……&#xff09; 假如有三种物品 A、B、C&#xff0c;对应的权重分别是A&#xff08;50&#xff09;&#xff0c…

yolov8源码解读Detect层

yolov8源码解读Detect层 Detect层解读网络各层解读及detect层后的处理 关于网络的backbone,head&#xff0c;以及detect层后处理&#xff0c;可以参考文章结尾博主的文章。 Detect层解读 先贴一下全部代码,下面一一解读。 class Detect(nn.Module):"""YOLOv8 …

【开源】JAVA+Vue.js实现大学计算机课程管理平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 实验课程档案模块2.2 实验资源模块2.3 学生实验模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 实验课程档案表3.2.2 实验资源表3.2.3 学生实验表 四、系统展示五、核心代码5.1 一键生成实验5.2 提交实验5.3 批阅实…

如何选择一个适合自己的赛道

&#xff08;点击即可收听&#xff09; 最开始一定要先做好定位&#xff0c;也就是你做短视频的目的是什么&#xff1f;当然对大多数人来说&#xff0c;终极目的肯定是赚钱&#xff0c;但赚钱的速度是由定位决定的 如果你资金比较充裕&#xff0c;不急于赚钱&#xff0c;就可以…