关于标准库中的list(涉及STL的精华-迭代器的底层)

news2024/11/18 16:43:45

目录

关于list

list常见接口实现

          STL的精华之迭代器


关于list

list的文档介绍

  • 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  • 2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  • 3. listforward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  • 4. 与其他的序列式容器相比(arrayvectordeque)list通常在任意位置进行插入、移除元素的执行效率更好。
  • 5. 与其他序列式容器相比,listforward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素。

常见接口:


list常见接口实现(参考部分g++版本下stl底层源码)

namespace dw
{
	template<class T>
	struct list_node //链表节点
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;
	};

	//迭代器实现

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		//代码实现


	private:
		node* _head;
	};

	//------------------------------------------------
	void list_test()
	{
		;
	}

}

1.注意:类名不是类型,建议声明类型的时候都加上模板参数

举例来说这里 list_node 类名  list_node<T> 类型

如果不加上模板参数运行程序会报错

typedef list_node<T> node;
typedef list_node node;

2.注意:这里使用的 struct  定义类,struct 定义的类默认访问权限是公开。

构造函数

无参

		void empty_init() //初始化头节点
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}

push_back

		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;

		}

注意,这里测试运行会发现:

原因出在:

node* newnode = new node(x);

所以关于链表节点 list_node 的这个类也需要实现构造函数

		list_node(const T& x = T()) //这里是匿名对象调用构造函数
			:_prev(nullptr)
			,_next(nullptr)
			,_data(x)
		{}


   

 STL的精华之迭代器

         这样就完成了需要的准备工作,接下来就可以进行神奇的迭代器部分了,看完大呼 - 还可以这样玩。

         上面的代码可以通过测试以及调试观看到具体状态,那么怎么进行成员访问?

         我们知道,vector 迭代器使用的是原生指针(g++版本) vector相关迭代器的实现,因为 vector 可以看作是一块连续的物理空间,我们通过下标就可以访问到下一个元素

       但是链表可以这样玩吗?肯定是不可以的!所以,这里 list 迭代器的实现不能使用原生指针,而是需要一个类去进行封装。

iterator

//迭代器实现
	template<class T>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

		self& operator++() 
		{
			_node =  _node->_next;
			return *this;
		}

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

	};

注意:c++一般不喜欢内部类,所以一般都使用自定义类型

继而我们需要在 list 类进行 typedef 

typedef __list_iterator<T> iterator;

begin()

begin()是第一个节点的位置

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

		

end()

end()是头节点的位置

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

注:这里和 begin( ) 的写法其实没什么不同,本质上是运用了匿名对象。

  •         其实这里可以看到,虽然vector,list 表面上都是 iterator,但是底层却千差万别,这些都源自于底层的封装

  •         并且,可以明显的感觉到迭代器很好体现了类的设计价值。如果一个内置类型无法满足我们的需求,那么我们可以使用一个自定义类型去封装,然后重载运算符,继而改变它的行为

  •         比如说这里的 iterator 是一个节点的指针,++* 不满足我们的需求,我们可以去进行封装, 用类去封装一个node* 重载++, * 运算符的函数

  •         重载运算符函数的具体实现以及行为完全由我们自己来定义,这是自定义类型+运算符重载+类的定义等等的价值

        其次,关于下面这句代码,我们并没有实现拷贝构造,编译器会默认生成,默认生成的拷贝构造是浅拷贝,那么,这里可以使用浅拷贝嘛?为什么没报错?

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

//首先:这里是需要浅拷贝的,因为这里拷贝的是指向节点的指针

//其次,为什么没报错 ? 是因为迭代器没有写析构函数,那么为什么没写 ? 

//是因为迭代器不需要释放节点。更深层次一些,为什么不需要释放节点 ?

//虽然这里迭代器有指向节点的指针,但是并不支持释放节点,因为释放节点是链表的行为

//链表会有析构函数释放节点,也可以简单把这里迭代器理解为工具,可以支持读或者写

//但是只有使用权限,并没有归属权限,所以这里浅拷贝也就没有问题了。

然后现在就可以丰富一些 迭代器 __list_iterator 这个类,后置 ++ ,--,等等

template<class T>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator<T> self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

		self& operator++()
		{
			_node =  _node->_next;
			return *this;
		}

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

		self& operator++(int) //编译器会默认传一个整型,进行占位,更前置进行区分
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

		self& operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

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

	};

const_iterator

接下来是关于 const 修饰的迭代器,如果是下面这样环境,该怎么办?

这里的迭代器就需要使用const 进行修饰了,那么请问这样写法可不可以?

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

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

这里运行编译通过,但是存在一个问题,明明是const修饰,为什么还能构造迭代器?
因为这里的const修饰的是*this 也就是指向的内容,*this 是这个节点的指针,const修饰的是这个指针的本身不能被改变,也就是_head不能被改变,但是可以拷贝。


结果发现并不符合我们的预期,因为这里我们期望迭代器被 const 修饰之后内容不可修改。这里不仅可读,并且可写,显然程序是有些不正确的。

所以这里的写法是我们需要再完成一个类,也就是 __list_const_iterator

    template<class T>
	struct __list_const_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_const_iterator<T> self;

		node* _node;

		__list_const_iterator(node* x)
			:_node(x)
		{}

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

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

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

		self& operator++(int) 
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

		self& operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

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

	};

注意:c++一般不喜欢内部类,所以一般都使用自定义类型

继而我们需要在 list 类进行 typedef 

typedef __list_const_iterator<T> const_iterator;

begin()

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

end()

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

代码程序正常运行

但是观察上面这两个类我们会发现,__list_iterator  __list_const_iterator

他们的区别只在于重载运算符解引用的实现不同,更细节一点只是解引用的返回值不同

__list_iterator 

 __list_const_iterator

        但是却写了两个类,这样会显得代码很臃肿,会让人觉得一模一样的代码为什么要写两遍,所以这里提出了一个新的语法知识 -  -  - 添加模板参数

代码如下:

    template<class T, class Ref>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator<T, Ref> self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

		Ref operator*()
		{
			return _node->_data;
		}

		self& operator++()
		{
			_node =  _node->_next;
			return *this;
		}

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

		self& operator++(int) //编译器会默认传一个整型,进行占位,更前置进行区分
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

		self& operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

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

	};

这样 __list_iterator  __list_const_iterator 就可以合并为一个类

再修改一些 list ,模板参数不同,调用逻辑也不同

        typedef __list_iterator<T, T&> iterator;

		typedef __list_iterator<T, const T&> const_iterator;

		//typedef __list_const_iterator<T> const_iterator; //注释掉

例:

       如果你认为到这里就结束了,那么不好意思,还差一点。因为观察库里 list 的实现,我们会发现迭代器的模板参数是三个。

附上stl部分底层源码:

template<class T, class Ref, class Ptr>
struct __list_iterator {
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  typedef __list_iterator<T, Ref, Ptr>           self;

  typedef bidirectional_iterator_tag iterator_category;
  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __list_node<T>* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  link_type node;

  __list_iterator(link_type x) : node(x) {}
  __list_iterator() {}
  __list_iterator(const iterator& x) : node(x.node) {}

  bool operator==(const self& x) const { return node == x.node; }
  bool operator!=(const self& x) const { return node != x.node; }
  reference operator*() const { return (*node).data; }

#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  self& operator++() { 
    node = (link_type)((*node).next);
    return *this;
  }
  self operator++(int) { 
    self tmp = *this;
    ++*this;
    return tmp;
  }
  self& operator--() { 
    node = (link_type)((*node).prev);
    return *this;
  }
  self operator--(int) { 
    self tmp = *this;
    --*this;
    return tmp;
  }
};

源码的  Ptr 是什么?因为这里不仅重载了*,还重载了 ->,那么什么时候要去调用->呢?

1.迭代器要么就是原生指针
⒉.迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为

解释这个原因的话,先看一个测试用例:

这时候我们会发现,之所以会报错是因为AA这个类没有自己实现一个流插入

所以要是想让代码跑起来,有很多的解决办法

方法一便是根据AA这个类型重载一个流插入

所以回过头来也能发现,c++新增运算符重载,而不是继续使用printf函数,是因为printf函数有局限性,printf只能打印内置类型,%d,%lf等等。

但是打印也不是没有其他办法,比如说上面这种,但是看到似乎是有点怪,解引用之后去访问成员
所以这样为了看起来更顺畅一些,我们需要去实现  ->

这里返回值是T*,但是如果是const迭代器呢?

所以这里就不能使用T*,而是需要新增加函数模板参数 Ptr

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator<T, Ref, Ptr> self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

		Ref operator*()
		{
			return _node->_data;
		}

		self& operator++()
		{
			_node =  _node->_next;
			return *this;
		}

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

		self& operator++(int) 
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

		self& operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

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

	};


以上就是list底层源码的实现逻辑,补充一点:

这里看着是有些怪异的,因为

it-> _a1
it-> ->_a1

本来这里应该是两个 ->

一个是运算符重载的调用

一个是有了结构体的指针再使用 -> 去访问

这里为了增加代码的可读性,省略了一个-> ,可以理解为是一个特殊处理

看似是:

cout << it->_a1 << ":" << it->_a2 << endl;

实际上:

cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

另外需要说明的点是:

迭代器用原生指针只是一个偶然,用类去封装才是一个常态

但是底层的本质都可以认为是指针,只说是嵌入了一个自定义类型去封装指针,在编译器看来是自定义类型而不是指针

并且自定义类型使用运算符只能去重载运算符,至于重载运算符函数的行为,完全是由我们自己来控制的

包括vector的迭代器,  在g++版本下(linux系统)是原生指针,但是vs下也不是原生指针,因为vs需要重载运算符函数,比如 * 用来判断迭代器是否失效。

要注意,不同编译器底层实现不同。以上就是 stl 的精华部分,关于迭代器。
       


swap

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

迭代器区间构造

        template<class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();

			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

拷贝构造

        //现代写法
		list(const list<T>& lt)
		{
			empty_init();

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

		//传统写法
		list(const list<T>& lt)
		{
			empty_init();

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

赋值

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

insert

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

			node* new_node = new node(x);

			prev->_next = new_node;
			new_node->_next = cur;
			new_node->_prev = prev;
			cur->_prev = new_node;
		}

erase

		void erase(iterator pos)
		{
			assert(pos != end());
			node* next = pos._node->_next;
			node* prev = pos._node->_prev;

			next->_prev = prev;
			prev->_next = next;
			delete pos._node;
		}

        //看需求
        iterator erase(iterator pos)
		{
			assert(pos != end());
			node* next = pos._node->_next;
			node* prev = pos._node->_prev;

			next->_prev = prev;
			prev->_next = next;
			delete pos._node;

			return iterator(next);
		}

push_front

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

pop_back

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

pop_front

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

clear

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);
				
				//it = erase(it);  //两种方法都可以,这中 erase 需要有返回值
			}
		}

析构函数

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;

		}

最后附上全部代码以及测试用例:

                                                                                 list.h


namespace dw
{
	template<class T>
	struct list_node //链表节点
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;

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



	//迭代器实现

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator<T, Ref, Ptr> self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

		Ref operator*()
		{
			return _node->_data;
		}

		self& operator++()
		{
			_node =  _node->_next;
			return *this;
		}

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

		self& operator++(int) //编译器会默认传一个整型,进行占位,更前置进行区分
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

		self& operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

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

	};
	

	/*
	template<class T>
	struct __list_const_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_const_iterator<T> self;

		node* _node;

		__list_const_iterator(node* x)
			:_node(x)
		{}

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

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

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

		self& operator++(int) 
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

		self& operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		bool operator==(const self& s)
		{
			return _node == s._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;

		//typedef __list_const_iterator<T> const_iterator;

		void empty_init() //初始化头节点
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}

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

		//现代写法
		list(const list<T>& lt)
		{
			empty_init();

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

		//传统写法
		//list(const list<T>& lt)
		//{
		//	empty_init();

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

		template<class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();

			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

		//lt2 = lt1
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

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

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

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

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

		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;

		}

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

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

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

		iterator erase(iterator pos)
		{
			assert(pos != end());
			node* next = pos._node->_next;
			node* prev = pos._node->_prev;

			next->_prev = prev;
			prev->_next = next;
			delete pos._node;

			return iterator(next);
		}

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

			node* new_node = new node(x);

			prev->_next = new_node;
			new_node->_next = cur;
			new_node->_prev = prev;
			cur->_prev = new_node;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);
				
				//it = erase(it);  //两种方法都可以,这中 erase 需要有返回值
			}
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;

		}


	private:
		node* _head;
	};




	//------------------------------------------------

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


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

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

		print_list(lt);

		list<int> lt2(lt);

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


		list<int> lt3 = lt2;

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

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

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

		lt.push_back(1000); //测试尾插
		lt.push_front(100); // 测试头插

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

		lt.pop_back(); // 测试尾删
		lt.pop_front(); //测试头删

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

		auto pos = lt.begin();
		++pos;

		lt.insert(pos, 9); //测试任意位置插入
		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		pos = lt.begin();
		++pos;

		lt.erase(pos); // 测试任意位置删除
		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

	}

}

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

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

相关文章

为什么要使用国际语音群呼系统?

1.降本增效 通过批量导入客户的电话号码&#xff0c;由系统自动完成批量呼叫&#xff0c;企业可以节省人工拨号的费用&#xff0c;高效助力企业业务增长&#xff1b; 2.降低流失 通过批量群呼&#xff0c;企业可以724小时高并发无故障运行&#xff0c;智能锁定意向客户&…

java代码编写twitter授权登录

在上一篇内容已经介绍了怎么申请twitter开放的API接口。 下面介绍怎么通过twitter提供的API&#xff0c;进行授权登录功能。 开发者页面设置 首先在开发者页面开启“用户认证设置”&#xff0c;点击edit进行信息编辑。 我的授权登录是个网页&#xff0c;并且只需要进行简单的…

UML概扩知识点

UML是一个重要的知识点&#xff0c;考察的频度也很高。我们需要了解的是UML的一系列的图&#xff0c;红框里的是最核心的。 其次是对各种关系有了解&#xff08;红框里的&#xff1a; 依赖关系&#xff0c;关联关系&#xff0c;泛化关系&#xff0c;实现关系&#xff09; UM…

01.前言

前言 1.什么是前端开发 前端开发是创建 Web 页面或 app 等前端界面呈现给用户的过程核心技术&#xff1a;HTML&#xff0c;CSS&#xff0c;JavaScript 以及衍生出的各种技术&#xff0c;框架等 2.前端开发应用场景 3.前端职业路线 4.什么是CS架构与BS架构 介绍 应用软件&a…

项目总体测试计划书-word原件

编写此测试方案的目的在于明确测试内容、测试环境、测试人员、测试工作进度计划等&#xff0c;以保证测试工作能够在有序的计划安排进行。

【如何理解select、poll、epoll?】

如何理解select、poll、epoll&#xff1f; select、poll、epollselectpollepoll 知识扩展三者之间的主要区别是什么&#xff1f;epoll的两种模式是什么&#xff1f; select、poll、epoll select、poll、epoll都是Linux中常见的I/O多路复用技术&#xff0c;他们可以用于同时监听…

使用ffmpeg命令进行视频格式转换

1 ffmpeg介绍 FFmpeg 是一个非常强大和灵活的开源工具集&#xff0c;用于处理音频和视频文件。它提供了一系列的工具和库&#xff0c;可以用于录制、转换、流式传输和播放音频和视频。 FFmpeg 主要特点如下&#xff1a; 格式支持广泛&#xff1a;FFmpeg 支持几乎所有的音频和视…

【lesson14】MySQL表的基本查询(1)

文章目录 表的基本操作介绍retrieveselect列建表基本测试 where子句建表基本测试 表的基本操作介绍 CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; retrieve select列 建表 基本测试 插入数据 全列查询 …

iptables与ipvs的异同

iptables与ipvs的异同 Kubernetes 1.29 新版将抛弃 iptables那么我们就来聊一下iptables与ipvs的异同 iptables和ipvs都是Linux系统中用于网络流量控制和管理的工具&#xff0c;但它们在实现方式、功能和性能上有所不同。本文将对iptables和ipvs进行比较&#xff0c;以帮助读…

JS基础之作用域链

JS基础之作用域链 作用域链作用域链函数创建函数激活总结 作用域链 当JavaScript代码执行一段可执行的代码&#xff08;execution code&#xff09;时&#xff0c;会创建对应的执行上下文&#xff08;execution context&#xff09;。 对于每个执行上下文&#xff0c;都有三个重…

beanshell、jcef

BeanShell BeanShell是一个小型嵌入式Java源代码解释器&#xff0c;具有对象脚本语言特性&#xff0c;能够动态地执行标准JAVA语法。 BeanShell不仅仅可以通过运行其内部的脚本来处理Java应用程序&#xff0c;还可以在运行过程中动态执行你java应用程序执行java代码。因为Bea…

计算机网络传输层(期末、考研)

计算机网络总复习链接&#x1f517; 目录 传输层的功能端口UDP协议UDP数据报UDP的首部格式UDP校验 TCP协议&#xff08;必考&#xff09;TCP报文段TCP连接的建立TCP连接的释放TCP的可靠传输TCP的流量控制零窗口探测报文段 TCP的拥塞控制慢开始和拥塞控制快重传和快恢复 TCP和U…

【网络编程之初出茅庐】

前言&#xff1a;本章主要先讲解一些基本的网络知识&#xff0c;先把基本的知识用起来&#xff0c;后续会更深入的讲解底层原理。 网络编程的概念 网络编程&#xff0c;指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff08;或称为网络数…

SAP 标准GUI 中增加按钮时报错:EC181

今天在打一个note的时候&#xff0c;需要做一些手动的调整&#xff0c;其中的步骤就需要我去在标准的GUI STATUS 增加按钮 我在进入编辑模式的时候&#xff0c;直接去插入的时候&#xff0c;始终报错如下&#xff1a; Function code xxxx has not been assigned to a functio…

数据结构与算法:插入排序

原理 保证区间内排好顺序&#xff0c;逐渐将区间外数据插入到该区间中。 从局部扩散到整体。 第一次&#xff1a;保证0-1范围内有序 arr[0]和arr[1]对比&#xff0c;若arr[0] 大于 arr[1] &#xff0c;交换两个值&#xff0c; 0-1范围内有序。 第二次&#xff1a;保证 0-2 …

蓝牙物联网全屋智能系统解决方案

#蓝牙物联网# 蓝牙物联网全屋智能系统解决方案是一种通过低功耗蓝牙技术将家中的各种设备连接到一起&#xff0c;实现家居物联智能操控的方案。 全屋智能系统解决方案是一种将智能家居设备、传感器、照明、安防等系统集成在一起&#xff0c;实现全屋智能化控制的方案。 蓝牙物…

如何用python编写抢票软件,python爬虫小程序抢购

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python小程序抢购脚本怎么写&#xff0c;如何用python编写抢票软件&#xff0c;现在让我们一起来看看吧&#xff01; 大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python小程序抢购脚本怎么写&#xff0c;如…

【剑指offer|图解|二分查找】点名 + 统计目标成绩的出现次数

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、剑指offer每日一练 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️点名1.1 题目1.2 示例1.3 限制1.4 解题思路一c代码 1.5 解题思路二c代码 二. ⛳️统…

ARM I2C通信

1.概念 I2C总线是PHLIPS公司在八十年代初推出的一种串行的半双工同步总线&#xff0c;主要用于连接整体电路2.IIC总线硬件连接 1.IIC总线支持多主机多从机&#xff0c;但是在实际开发过程中&#xff0c;大多数采用单主机多从机模式 2.挂接到IIC总线上&#xff0c;每个从机设备都…

leetcode--1004 最大连续1的个数 III[滑动窗口c++]

原题链接&#xff1a; 3. 无重复字符的最长子串 - 力扣&#xff08;LeetCode&#xff09; 题目解析&#xff1a; 题目的翻转0&#xff0c;意思就是把0变成1&#xff1b; 将题的 最多可翻转k个0 操作看成 限定范围内最多可有k个0&#xff08;等价转换&#xff09; 因为实…