【C++】List -- 详解

news2024/12/27 15:32:27

一、list的介绍及使用

https://cplusplus.com/reference/list/list/?kw=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、list的使用

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

(1)list的构造

// list的构造
void TestList1()
{
    list<int> l1;                        // 构造空的l1
    list<int> l2(4, 100);                // l2中放4个值为100的元素
    list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
    list<int> l4(l3);                    // 用l3拷贝构造l4

    // 以数组为迭代器区间构造l5
    int array[] = {16, 2, 77, 29};
    list<int> l5(array, array + sizeof(array) / sizeof(int));

    // 列表格式初始化C++11
    list<int> l6{1, 2, 3, 4, 5};

    // 用迭代器方式打印l5中的元素
    list<int>::iterator it = l5.begin();
    while (it != l5.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // C++11范围for的方式遍历
    for (auto& e : l5)
    {
        cout << e << " ";
    }
    cout << endl;
}

(2)list iterator的使用

可以暂时将迭代器理解成一个指针,该指针指向 list 中的某个节点

// list迭代器的使用
void PrintList(const list<int>& l)
{
    // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
    for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
    {
        cout << *it << " ";
        // *it = 10; // 不能改变该值 -- 编译不通过
    }
    cout << endl;
}
void TestList2()
{
    int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
    list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    // 使用正向迭代器正向list中的元素
    // list<int>::iterator it = l.begin(); // C++98中语法
    auto it = l.begin();                   // C++11之后推荐写法
    while (it != l.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 使用反向迭代器逆向打印list中的元素
    // list<int>::reverse_iterator rit = l.rbegin();
    auto rit = l.rbegin();
    while (rit != l.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
}

注意遍历链表只能用迭代器范围for

【注意】
  1. begin 与 end 为正向迭代器,对迭代器执行++操作,迭代器移动
  2. rbegin(end) 与 rend(begin) 为反向迭代器,对迭代器执行++操作,迭代器移动

(3)list capacity


(4)list element access


(5)list modifiers

// list插入和删除
// push_back/pop_back/push_front/pop_front
void TestList3()
{
    int array[] = {1, 2, 3};
    list<int> L(array, array + sizeof(array) / sizeof(array[0]));
    
    L.push_back(4); // 在list的尾部插入4
    L.push_front(0); // 在list的头部插入0
    PrintList(L);
    
    L.pop_back(); // 删除list尾部节点
    L.pop_front(); // 删除list头部节点
    PrintList(L);
}
// insert /erase 
void TestList4()
{
    int array1[] = {1, 2, 3};
    list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));
   
    auto pos = ++L.begin(); // 获取链表中第二个节点
    cout << *pos << endl;
  
    L.insert(pos, 4); // 在pos前插入值为4的元素
    PrintList(L);
   
    L.insert(pos, 5, 5); // 在pos前插入5个值为5的元素
    PrintList(L);
   
    vector<int> v{7, 8, 9};
    L.insert(pos, v.begin(), v.end()); // 在pos前插入[v.begin(), v.end)区间中的元素
    PrintList(L);
   
    L.erase(pos); // 删除pos位置上的元素
    PrintList(L);
   
    L.erase(L.begin(), L.end()); // 删除list中[begin, end)区间中的元素,即删除list中的所有元素
    PrintList(L);
}
// resize/swap/clear
void TestList5()
{
    // 用数组来构造list
    int array1[] = {1, 2, 3};
    list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
    PrintList(l1);
   
    list<int> l2;
    l1.swap(l2); // 交换l1和l2中的元素
    PrintList(l1);
    PrintList(l2);
  
    l2.clear(); // 将l2中的元素清空
    cout << l2.size() << endl;
}
为什么 C++98 建议使用各自容器里的 swap,而不建议使用算法里的 swap?

可以看到算法里 swap 的 C++98 的实现,无论是 string、vector、list 使用它会涉及深拷贝问题,而且这里的深拷贝代价极大,需要深拷贝 3 次 —— 当 l1 和 l2 交换,这里会把 l1 拷贝构造一份 c,然后把 l2 赋值于 l1,c 赋值于 l2,完成交换。

而如果是容器里的 swap,需要交换 l1 和 l2,只需要头指针交换即可。假设是 vector,只要把 l1 和 l2 对应的 _start、_finish、_endofstorage 交换即可。相比算法里的 C++98 里的 swap,这里可以认为没有任何代价。


(6)list的迭代器失效 
先将迭代器暂时理解成类似于指针, 迭代器失效即迭代器所指向的节点无效,即该节点被删除了 。因为 list 的底层结构为带头结点的双向循环链表,因此在 list 中进行 插入 时是 不会 导致 list 的迭代器失效的,只有在 删除 时才 失效,并且 失效的只是指向被删除节点的迭代器 ,其他迭代器不会受到影响
void TestListIterator1()
{
    int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
    list<int> l(array, array+sizeof(array)/sizeof(array[0]));
    auto it = l.begin();
    while (it != l.end())
    {
        // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
        l.erase(it); 
        ++it;
    }
}

// 改正
void TestListIterator()
{
    int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
    list<int> l(array, array+sizeof(array)/sizeof(array[0]));
    auto it = l.begin();
    while (it != l.end())
    {
        l.erase(it++); // it = l.erase(it);
    }
}


⚪【补充】

容器迭代器的分类:

  1. 使用功能的角度可分为,(正向、反向) + const
  2. 容器底层结构的角度可分为,单向、双向、随机

比如单链表迭代器、哈希表迭代器就是单向,特征是能 ++,不能 --;双向链表迭代器、map 迭代器就是双向,特征是能 ++、–;string、vector、deque 迭代器就是随机迭代器,特征是能 ++、–、+、-,一般随机迭代器底层都是一个连续的空间。


二、list的模拟实现

1、模拟实现list

要模拟实现 list,必须要熟悉 list 的底层结构以及其接口的含义。
#pragma once

#include <iostream>
using namespace std;
#include <assert.h>

namespace xyl
{
	// List的节点类
	template<class T>
	struct ListNode
	{
		ListNode(const T& val = T())
			: _prev(nullptr)
			, _next(nullptr)
			, _val(val)
		{}

		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _val;
	};

	template<class T, class Ref, class Ptr>
	class ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr> Self;

		// Ref 和 Ptr 类型需要重定义下,实现反向迭代器的时候需要用到
	public:
		typedef Ref Ref;
		typedef Ptr Ptr;
	public:
		// 构造
		ListIterator(Node* node = nullptr)
			: _node(node)
		{}

		// 具有指针类似行为
		Ref operator*()
		{
			return _node->_val;
		}

		Ptr operator->()
		{
			return &(operator*());
		}

		// 迭代器支持移动
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		Self operator++(int)
		{
			Self temp(*this);
			_node = _node->_next;
			return temp;
		}

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

		Self operator--(int)
		{
			Self temp(*this);
			_node = _node->_prev;
			return temp;
		}

		// 迭代器支持比较
		bool operator!=(const Self& l)const
		{
			return _node != l._node;
		}

		bool operator==(const Self& l)const
		{
			return _node != l._node;
		}

		Node* _node;
	};

	template<class Iterator>
	class ReverseListIterator
	{
		// 注意:这里typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量
		// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
		// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
	public:
		typedef typename Iterator::Ref Ref;
		typedef typename Iterator::Ptr Ptr;
		typedef ReverseListIterator<Iterator> Self;
	public:
		// 构造
		ReverseListIterator(Iterator it)
			: _it(it)
		{}

		// 具有指针类似行为
		Ref operator*()
		{
			Iterator temp(_it);
			--temp;
			return *temp;
		}

		Ptr operator->()
		{
			return &(operator*());
		}

		// 迭代器支持移动
		Self& operator++()
		{
			--_it;
			return *this;
		}

		Self operator++(int)
		{
			Self temp(*this);
			--_it;
			return temp;
		}

		Self& operator--()
		{
			++_it;
			return *this;
		}

		Self operator--(int)
		{
			Self temp(*this);
			++_it;
			return temp;
		}

		// 迭代器支持比较
		bool operator!=(const Self& l)const
		{
			return _it != l._it;
		}

		bool operator==(const Self& l)const
		{
			return _it != l._it;
		}

		Iterator _it;
	};

	template<class T>
	class list
	{
		typedef ListNode<T> Node;

	public:
		// 正向迭代器
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T&> const_iterator;

		// 反向迭代器
		typedef ReverseListIterator<iterator> reverse_iterator;
		typedef ReverseListIterator<const_iterator> const_reverse_iterator;
	public:
		// List的构造
		list()
		{
			CreateHead();
		}

		list(int n, const T& value = T())
		{
			CreateHead();
			for (int i = 0; i < n; ++i)
				push_back(value);
		}

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

		list(const list<T>& l)
		{
			CreateHead();

			// 用l中的元素构造临时的temp,然后与当前对象交换
			list<T> temp(l.begin(), l.end());
			this->swap(temp);
		}

		list<T>& operator=(list<T> l)
		{
			this->swap(l);
			return *this;
		}

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

		// List的迭代器
		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);
		}

		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		const_reverse_iterator rbegin()const
		{
			return const_reverse_iterator(end());
		}

		const_reverse_iterator rend()const
		{
			return const_reverse_iterator(begin());
		}

		// List的容量相关
		size_t size()const
		{
			Node* cur = _head->_next;
			size_t count = 0;
			while (cur != _head)
			{
				count++;
				cur = cur->_next;
			}

			return count;
		}

		bool empty()const
		{
			return _head->_next == _head;
		}

		void resize(size_t newsize, const T& data = T())
		{
			size_t oldsize = size();
			if (newsize <= oldsize)
			{
				// 有效元素个数减少到newsize
				while (newsize < oldsize)
				{
					pop_back();
					oldsize--;
				}
			}
			else
			{
				while (oldsize < newsize)
				{
					push_back(data);
					oldsize++;
				}
			}
		}

		// List的元素访问操作
		// 注意:List不支持operator[]
		T& front()
		{
			return _head->_next->_val;
		}

		const T& front()const
		{
			return _head->_next->_val;
		}

		T& back()
		{
			return _head->_prev->_val;
		}

		const T& back()const
		{
			return _head->_prev->_val;
		}

		// List的插入
		void push_back(const T& val)
		{
			insert(end(), val);
		}

		// List的删除
		void pop_back()
		{
			erase(--end());
		}

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

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

		// 在pos位置前插入值为val的节点
		iterator insert(iterator pos, const T& val)
		{
			Node* pNewNode = new Node(val);
			Node* pCur = pos._node;
			// 先将新节点插入
			pNewNode->_prev = pCur->_prev;
			pNewNode->_next = pCur;
			pNewNode->_prev->_next = pNewNode;
			pCur->_prev = pNewNode;
			return iterator(pNewNode);
		}

		// 删除pos位置的节点,返回该节点的下一个位置
		iterator erase(iterator pos)
		{
			// 找到待删除的节点
			Node* pDel = pos._node;
			Node* pRet = pDel->_next;

			// 将该节点从链表中拆下来并删除
			pDel->_prev->_next = pDel->_next;
			pDel->_next->_prev = pDel->_prev;
			delete pDel;

			return iterator(pRet);
		}

		void clear()
		{
			Node* cur = _head->_next;

			// 采用头删除删除
			while (cur != _head)
			{
				_head->_next = cur->_next;
				delete cur;
				cur = _head->_next;
			}

			_head->_next = _head->_prev = _head;
		}

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

	private:
		void CreateHead()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}
	private:
		Node* _head;
	};
}


// 对模拟实现的list进行测试
// 正向打印链表
template<class T>
void PrintList(const xyl::list<T>& l)
{
	auto it = l.begin();
	while (it != l.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

// 测试List的构造
void TestBiteList1()
{
	xyl::list<int> l1;
	xyl::list<int> l2(10, 5);
	PrintList(l2);

	int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
	xyl::list<int> l3(array, array + sizeof(array) / sizeof(array[0]));
	PrintList(l3);

	xyl::list<int> l4(l3);
	PrintList(l4);

	l1 = l4;
	PrintList(l1);
}

// PushBack()/PopBack()/PushFront()/PopFront()
void TestBiteList2()
{
	// 测试PushBack
	xyl::list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	PrintList(l);

	// 测试PopBack
	l.pop_back();
	l.pop_back();
	PrintList(l);

	l.pop_back();
	cout << l.size() << endl;

	// 测试PushFront
	l.push_front(1);
	l.push_front(2);
	l.push_front(3);
	PrintList(l);

	// 测试PopFront
	l.pop_front();
	l.pop_front();
	PrintList(l);

	l.pop_front();
	cout << l.size() << endl;
}

// 测试insert和erase
void TestBiteList3()
{
	int array[] = {1, 2, 3, 4, 5};
	xyl::list<int> l(array, array + sizeof(array) / sizeof(array[0]));

	auto pos = l.begin();
	l.insert(l.begin(), 0);
	PrintList(l);

	++pos;
	l.insert(pos, 2);
	PrintList(l);

	l.erase(l.begin());
	l.erase(pos);
	PrintList(l);

	// pos指向的节点已经被删除,pos迭代器失效
	cout << *pos << endl;

	auto it = l.begin();
	while (it != l.end())
	{
		it = l.erase(it);
	}
	cout << l.size() << endl;
}

// 测试反向迭代器
void TestBiteList4()
{
	int array[] = {1, 2, 3, 4, 5};
	xyl::list<int> l(array, array + sizeof(array) / sizeof(array[0]));

	auto rit = l.rbegin();
	while (rit != l.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	const xyl::list<int> cl(l);
	auto crit = l.rbegin();
	while (crit != l.rend())
	{
		cout << *crit << " ";
		++crit;
	}
	cout << endl;
}

【List 的迭代器】
迭代器有两种实现方式,具体应根据容器底层数据结构实现:

  1. 原生态指针比如:vector。
  2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同。

因此在自定义的类中必须实现以下方法:

  1. 指针可以解引用,迭代器的类中必须重载 operator*()
  2. 指针可以通过 -> 访问其所指空间成员,迭代器类中必须重载 oprator->()
  3. 指针可以 ++ 向后移动,迭代器类中必须重载 operator++() 与 operator++(int)
  4. 至于operator--() / operator--(int) 释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是 forward_list 就不需要重载 -- 。
  5. 迭代器需要进行是否相等的比较,因此还需要重载 operator==() 与 operator!=()

2、list的反向迭代器 

通过前面我们可以知道,反向迭代器的 ++ 就是正向迭代器的 --,反向迭代器的 -- 就是正向迭代器的 ++,因此反向迭代器的实现可以借助正向迭代器,即: 反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。
template<class Iterator>
class ReverseListIterator
{
    // 注意:这里typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量
    // 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
    // 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
public:
	typedef typename Iterator::Ref Ref;
    typedef typename Iterator::Ptr Ptr;
	typedef ReverseListIterator<Iterator> Self;
public:
	// 构造
	ReverseListIterator(Iterator it)
		: _it(it)
	{}

	// 具有指针类似行为
	Ref operator*()
	{
		Iterator temp(_it);
		--temp;
		return *temp;
	}

	Ptr operator->()
	{
		return &(operator*());
	}

	// 迭代器支持移动
	Self& operator++()
	{
		--_it;
		return *this;
	}

	Self operator++(int)
	{
		Self temp(*this);
		--_it;
		return temp;
	}

	Self& operator--()
	{
		++_it;
		return *this;
	}

	Self operator--(int)
	{
		Self temp(*this);
		++_it;
		return temp;
	}

	// 迭代器支持比较
	bool operator!=(const Self& l)const
	{
		return _it != l._it;
	}

	bool operator==(const Self& l)const
	{
		return _it != l._it;
	}

	Iterator _it;
};

三、listvector的对比

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

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

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

相关文章

Windows10点击开始菜单没反应的四种解决方法

在Windows10电脑中&#xff0c;用户点击开始菜单却出现了没反映的情况&#xff0c;这样用户就无法通过开始菜单来展开操作哦&#xff0c;会给用户的正常操作带来一定程序的影响&#xff0c;下面小编给大家带来四种简单的解决方法&#xff0c;帮助大家轻松恢复Windows10电脑开始…

Selenium 高级定位 CSS

一、CSS选择器概念 CSS拥有自己的语法规则和表达式 CSS通常分为相对定位和绝对定位 CSS常和XPATH一起用于UI自动化测试 二、CSS相对定位使用场景 支持web场景支持app端的webview 三、CSS语法实战 3.1、CSS相对定位的优点 可维护性强语法简洁可以解决各种复杂的定位场景 # …

ARMv7-A 那些事 - 6.常用汇编指令

By: Ailson Jack Date: 2023.10.07 个人博客&#xff1a;http://www.only2fire.com/ 本文在我博客的地址是&#xff1a;http://www.only2fire.com/archives/158.html&#xff0c;排版更好&#xff0c;便于学习&#xff0c;也可以去我博客逛逛&#xff0c;兴许有你想要的内容呢。…

【二叉树练习题】

欢迎来到我的&#xff1a;世界 希望作者的文章对你有所帮助&#xff0c;有不足的地方还请指正&#xff0c;大家一起学习交流 ! 目录 前言初阶题二叉树的节点个数二叉树的叶子节点个数二叉树第k层节点个数二叉树查找值为x的节点 进阶题完全二叉树的节点个数翻转二叉树检验两个树…

修炼k8s+flink+hdfs+dlink(一:安装dlink)

一&#xff1a;mysql初始化。 mysql -uroot -p123456 create database dinky; grant all privileges on dinky.* to dinky% identified by dinky with grant option; flush privileges;二&#xff1a;上传dinky。 上传至目录/opt/app/dlink tar -zxvf dlink-release-0.7.4.t…

医学访问学者面试技巧

医学访问学者面试是一个非常重要的环节&#xff0c;它决定了你是否能够获得这个宝贵的机会去国外的大学或研究机构学习和研究。在这篇文章中&#xff0c;知识人网小编将分享一些关于医学访问学者面试的技巧&#xff0c;帮助你在面试中表现出色。 1. 准备充分 在参加医学访问学…

Multisim:JFET混频器设计(含完整程序)

目录 前言实验内容一、先看作业题目要求二、作业正文IntroductionPre-lab work3.13.2 Experiment Work4.1(2)circuit setup4.1(3)add 12V DC4.1(4)set input x1 and x24.1(5)4.1(6)4.1(7)4.2(1)(2)4.2(3)4.2(4)4.3(1)(2)4.3(3) Conclusion 三、资源包内容 前言 花了好大心血完成…

【线性代数及其应用 —— 第一章 线性代数中的线性方程组】-1.线性方程组

所有笔记请看&#xff1a; 博客学习目录_Howe_xixi的博客-CSDN博客https://blog.csdn.net/weixin_44362628/article/details/126020573?spm1001.2014.3001.5502思维导图如下&#xff1a; 内容笔记如下&#xff1a;

中国高清行政、地形、旅游、人文地图全集(地理干货,覆盖34个省市自治区)

中国高清行政、地形、旅游、人文地图全集&#xff08;地理干货&#xff0c;覆盖34个省市自治区&#xff09;&#xff1a; 中国的高原、平原、盆地和丘陵 四大高原&#xff1a;青藏高原位于中国西南部&#xff0c;平均海拔在4000米以上&#xff0c;是中国最大、世界最高的大高原…

安卓手机如何下载谷歌应用商店(play.google.com)里的app,无需下载google play

不需要梯子&#xff01; 不需要下载google play&#xff01; 不需要考虑机型&#xff01; 1、首先使用电脑打开网站 https://apps.evozi.com/ 点击 apk downloader 进入一个新的界面 2、电脑打开谷歌商店 https://play.google.com/store/apps 搜索任意软件&#xff0c;比如 …

300元开放式耳机推荐哪款好用一点、最便宜的开放式耳机

对于音乐爱好者来说&#xff0c;一款出色的耳机是必不可少的&#xff0c;开放式耳机以其独特的音场表现和舒适的佩戴感受&#xff0c;成为许多人钟爱的选择。如果你正在寻找一个300元价位好用的开放式耳机&#xff0c;今天就给大家推荐几款&#xff0c;希望能帮助到你~ 1、西圣…

python入门篇07-数据容器(序列 集合 字典,json初识)基础(下)

全文目录,一步到位 1.前言简介1.1 专栏传送门1.1.1 上文传送门 2. python基础使用2.1 序列2.1.1 序列定义2.1.2 序列参数解释2.1.3 列表list切片2.1.4 元组tuple切片2.1.5 字符串str切片 2.2 集合定义2.2.1 set集合-基本语法2.2.2 set集合-添加元素.add()2.2.3 set集合- 移除元…

bochs 对 Linux0.11 进行调试 (TODO: 后面可以考虑集成 vscode+gdb+qemu)

我在阅读 Linux0.11 源码时&#xff0c;对一个指令 LDS 感到困惑。 看了下 intel 指令集手册&#xff0c;能猜到 LDS 的功能&#xff0c;但不确定。 于是决定搭建调试环境&#xff0c;看看 LDS 的功能是否真如自己猜测。 首先 make debug 运行 qemu-Linux0.11&#xff0c;命…

死磕CMS垃圾回收器

为什么会有这篇文章&#xff1f; **校招垃圾回收面试重灾区。**本人秋招面试过大大小小的公司&#xff0c;发现几乎所有公司在面试的时候&#xff0c;一旦涉及到JVM只是&#xff0c;CMS和G1是常问点&#xff0c;而且CMS问的尤其多。所以本文会对根据网上常见的资料做一个整合并…

Qt扫盲-QTreeView 理论总结

QTreeView 理论使用总结 一、概述二、快捷键绑定三、提高性能四、简单实例1. 设计与概念2. TreeItem类定义3. TreeItem类的实现4. TreeModel类定义5. TreeModel类实现6. 在模型中设置数据 一、概述 QTreeView实现了 model 中item的树形表示。这个类用于提供标准的层次列表&…

Python3.x安装Pandas教程

python3.x 安装pandas总是会出现一些乱七八糟的问题&#xff0c;那现在就给你们讲述一种超级简单的安装方法&#xff0c;非常简单。 1&#xff0c;检查自己的python版本&#xff0c;我的是python3.4 32位的 2&#xff0c;https://www.lfd.uci.edu/~gohlke/pythonlibs/ 进入此网…

IIS部署Flask

启用 CGI 安装wfastcgi pip install wfastcgi 启用 wfastcgi 首先以管理员身份运行wfastcgi-enable来在IIS上启用wfastcgi&#xff0c;这个命令位于c:\python_dir\scripts&#xff0c;也就是你需要确保此目录在系统的PATH里&#xff0c;或者你需要cd到这个目录后再执行。 #…

np.clip()函数用法

代码&#xff1a; import numpy as npdelta np.clip(1.2, a_min0, a_max1)print(delta) 结果&#xff1a;

防御安全第五次作业

1. 什么是数据认证&#xff0c;有什么作用&#xff0c;有哪些实现的技术手段&#xff1f; 数据认证是指保证数据的真实性、完整性和可信度&#xff0c;以确保数据不被篡改或伪造。其作用包括但不限于&#xff1a; 保护关键数据不被恶意篡改或损坏 提供数据来源的可靠性和安全性…

ToBeWritten之记录狩猎过程

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 转移发布平台通知&#xff1a;将不再在CSDN博客发布新文章&#xff0c;敬…