C++ - list介绍 和 list的模拟实现

news2024/12/23 0:29:54

list介绍

 list 是一个支持在常数范围内,任意位置进行插入删除的序列式容器,且这个容器可以前后双向迭代。我们可以把 list 理解为 双向循环链表的结构。

于其他结构的容器相比,list在 任意位置进行插入和函数的效率要高很多;而list 的缺点也很明显,它在随机访问容器当中的数据的时候,它只能从已知位置开始线性寻找,这样寻找相比于其他容器来说有时间上的消耗;而且在存储方面,因为是一个结点一个结点分开存储,所以会多开空间来存储各个结点之间的关系,在存储消耗上也更高。

 list 的使用

 构造函数

构造函数( (constructor))接口说明
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list

list 的构造函数和 string ,vector是类似的,具体可以看以下博客:

C++ string类 迭代器 范围for_c++string迭代器_chihiro1122的博客-CSDN博客

 迭代器

 list 的迭代器不再像是 string 和 vector 当中使用 原生指针 来简单实现,而是使用类和对象来进行包装,这样可以让 指针 不能实现list 当中的 ++ 等等操作,用运算符重载函数来实现(具体迭代器的实现请看 list 的模拟实现)。

函数声明接口说明
begin +
end
返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin +
rend
返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的
reverse_iterator,即begin位置

 注意

  • 因为 list 的存储结构和 string 之类的 存储结构不一样,string 存储结构是 一块连续的区间,所以,对于string类的迭代器就支持  类似   str.begin() + 5 ,这样的操作;但是因为 list 是不连续的空间,对于 "+" 这个操作符的代价就比 string要高,所以在list 的迭代器当中就没有实现 operator+ 这个函数!!
  • 迭代器的使用 ,不能用 " < " 的形式来判断迭代器的区间!!因为 list 的各个结点的存储空间不连续,如果直接用 " < " 来比较,比较的是指针存储的地址大小,这样会出大问题,对于迭代器的使用一般是 这样的 :
list<int> L( 10 , 1 );
list::iterator it = L.begin();

while(it != L.end())
{
    cout << *it << " ";
    ++it;
}
cout << endl;

 其他基本操作

 在STL 当中,这些函数基本使用都差不多,集体可以参照之前介绍 string 和 vector 的博客:

C++ string类 迭代器 范围for_c++string迭代器_chihiro1122的博客-CSDN博客

C++ string类-2_chihiro1122的博客-CSDN博客

C++ - 初识vector类 迭代器失效_chihiro1122的博客-CSDN博客

list capacity

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

list element access

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

 list modifiers

函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

list 当中的迭代器失效

 list 当中的insert()函数就没有迭代器失效了,因为之前在vector 当中出现的 insert()函数迭代器失效,是因为vector 是一段连续的空间,需要扩容操作,而扩容就会导致迭代器失效;但是list 当中的每一次插入数据都是要开辟新空间,并不会影响到list 当中已经存在了的元素。

但是,对于删除的函数来说,比如 erase()函数,还是会存储迭代器失效的问题:

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;

如上述例子,就是外部的迭代器失效问题,当it 指向的空间被删除之后,it指向空间也就被释放,那么在while语句当中的  ++it 这个重载运算符函数就找不到下一个了。

改正;

while (it != l.end())
{
l.erase(it++); // it = l.erase(it);
}

 STL当中的迭代器认识

 我们先来看下面三个函数的不同迭代器类型:

 

 上述有三种迭代器类型:

  •  InputIterator:单向迭代器,只能用 ++ 的操作。
  •  BidirectionalIterator:双向迭代器,可以用 ++ / -- 两个操作。
  • RandomAccessIterator:随机迭代器,可以用      ++ / -- / + / -       四个操作。

 不同的容器类型,对于迭代器的使用就有要求:

 比如,单链表就只能用 单向迭代器,双向和随机都不能用,那么对于库当中的双向和随机迭代器实现的函数,单链表也不能使用。

但是这三个迭代器是向上兼容的,就是说 随机 是 双向的 一种特殊情况,所以,使用随机迭代器的容器就可以使用 双向迭代器的函数;同样,双向 是 单向的一种特殊情况,双向的,可以使用单向的。

 list的模拟实现

 大致框架

#pragma once

namespace My_List
{
	template<class T>
	struct List_Node  // 结点指针结构体
	{
·············
	};

    template<class T>
	struct List_iterator // 非const 迭代器
	{
··········
    };

    template<class T>
	class List         // List 类
	{
············
        private:
		Node* _head;

	};
}

 结点的结构体定义 

 在官方的List源代码当中,List容器的结点定义就是定义在 同一命名空间下的 一个结构体当中,因为在C++ 当中结构体已经升级为了类,所以在结构体当中也可以定义构造函数。

所以,因为结点当中有数值域,和指针域,我们就把这个结构体当做是一个构造结点的函数来实现,效果也是一样的,只不过使用的时候,使用new的方式来开空间和定义:

	template<class T>
	struct List_Node
	{
		List_Node<T>* _next;
		List_Node<T>* _prev;
		T _val;

		// List_Node 的构造函数
		List_Node(const T& val = T())
			:_next(nullptr),
			_prev(nullptr),
			_val(val)
		{}
	};

当然,为了数值域的复用性,使用模版来对数值域的类型进行模版化。

 构造函数和析构函数

 无参数的构造函数

		List()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

因为List的底层是 带头循环双向链表,所以没有结点的链接方式如上,只有一个头结点。

 析构函数

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

 这里可以直接复用clear()函数。

增删查改

 push_back():

		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* newNode = new Node(x);

			_head->_prev = newNode;
			tail->_next = newNode;

			newNode->_prev = tail;
			newNode->_next = _head;

            // 实现insert()函数之后
            // insert(end() , x);
		}

直接开空间然后修改链接关系即可。

 insert()函数:

这里的insert()函数的当中传入的 pos 指针不用检查合法性,因为这里是带头结点循环的链表,在头结点的前面和后面删都是可以的。代码:

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

			prev->_next = newNode;
			newNode->_next = cur:

			cur->_prev = newNode;
			newNode->_prev = prev;

			return newNode;
		}

因为pos位置前插入元素之后,pos迭代器向后移动了以为,我们认为这样也属于迭代器失效,所以要返回新插入的元素的位置,防止外部迭代器失效。 

erase()函数

 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; // 这里会有 pos 迭代器失效的问题,所要要返回新的迭代器

			return next;
		}

 push_front():

 直接复用insert()函数

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

 pop_back() 和 pop_front(): 

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

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

 clear( )和 size():

		size_t size()
		{
			return _size;
		}

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

 赋值运算符重载函数和swap()函数

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

		List<T>& operator(List<T> L)
		{
			swap(L);
			return *this;
		}

 上述原理参考文章:(1条消息) C++-string类的模拟实现_chihiro1122的博客-CSDN博客当中赋值操作符重载函数(比较大小标题下)的介绍。

其实上述的 赋值重载运算符函数当中的模板类的类型名可以不用 List<T>,我们知道List<T>是类型名,List是类名,对于模版类,类型名不是 List,而是List<T>,但是如果是在类模板当中写,可以写类名也可以写类型名(下述写法也可以):

List& operator=(List L)

迭代器(重点)

 非const迭代器

 在上述介绍List迭代器的时候也介绍了,List当中的迭代器不是原生指针,而是自定义类型,所以在定义的时候有些难度,但是这样的好处是和 普通的原生指针迭代器一样,可以直接 ++ 后移,*解引用来访问元素,等等,因为自定义类型当中有运算符重载函数,这样就可以实现。

 其实List的迭代器本质上还是一个指针,只不过这个指针现在指向的是一个结点空间,其中不仅仅有数据域,还有指针域,那么直接解引用是不能访问到数据域的,这时候就要重载 “ * ”(解引用)运算符,实现也很简单,直接返回结点的数据域即可:

		T& operator* ()
		{
			return _Node->_val;
		}

 我们再思考,我们在使用迭代器的时候会使用哪一些运算符,如下所示:
 

	while (it != L.end())
	{
		cout << *it << " ";
		++it; // 因为只重载了 前置的++
	}
	cout << endl;

 我们要对上述用到的运算都要进行重载,那么这个迭代器才能正常使用:

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

		bool operator!= (const List_iterator<T>& L)
		{
			return _Node != L._Node;
		}

		bool operator== (const List_iterator<T>& L)
		{
			return _Node == L._Node;
		}

 最后是整个 迭代器结构体的构建,上述也说过,C++把结构体升级为了类,那么就可以使用构造函数来构造这个 迭代器对象。

首先,这个迭代器的成员其实就一个,就是某一个结点的指针,那么这个结构体的构造函数就只用对这一个对象进行初始化就行了,只需要在构造的时候传入这个结点的指针就可以:
 

		typedef List_Node<T> Node;
		Node* _Node;

		List_iterator(Node* node)
			:_Node(node)
		{}

 那么整个非const的迭代器就实现了,如下所示:

	template<class T>
	struct List_iterator
	{
		typedef List_Node<T> Node;
		Node* _Node;

		List_iterator(Node* node)
			:_Node(node)
		{}

		T& operator* ()
		{
			return _Node->_val;
		}

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

		bool operator!= (const List_iterator<T>& L)
		{
			return _Node != L._Node;
		}

		bool operator== (const List_iterator<T>& L)
		{
			return _Node == L._Node;
		}
	};

 然后,在类当中也需要给出 begin()和end()两个接口,begin()指向头结点 _head 的后一个,end 指向 _head 就行了:

		typedef List_iterator<T> iterator; // 一定要是公有的,不然不能访问
		iterator begin()
		{
			//return _head->_next; // 可以这样写,只有一个参数的构造函数发生 隐式类型的转换
		    // 上下两种都是一样的
			return iterator(_head->_next);
		}

		// 返回值应该是引用,要不然 != 函数会出错 传值返回返回的不是 _head 是 _head 的一个拷贝 
       // 临时对象具有常性  ······· 1
		iterator end()
		{
			return _head; // 同样发生隐式类型的转换
			// 上下两种都可以
			//return iterator(_head);
		}

 需要注意几个问题: 

  •  上述代码注释当中提到的返回值类型应该是引用的问题(1),其实不用这样做,上述的 begin()和end()函数是在 operator!= 函数 和 构造函数当中使用的,所以只需要把 operator!= 函数的参数修改为 const 即可,上述已经做出了更改。
  • My_List::List<int>::iterator it = L.begin();     这里不是赋值,而是拷贝构造,因为L.begin() 是一个已经存在了的对象赋值给另一个对象需要调用拷贝构造函数,但是迭代器没有实现拷贝构造,这里的编译器自己实现的浅拷贝,这里的浅拷贝没有问题,因为我们这里需要的就是浅拷贝。
  • My_List::List<int>::iterator it = L.begin();     这里的两个指针都指向一个对象,那么为什么编译器没有报错呢?这是因为迭代器类没有实现析构函数,编译器就会自己调用默认析构函数去 释放迭代器指针空间,而迭代器指向的结点空间,并不需要迭代器类来进行释放,在List类的析构函数当中进行释放。

const迭代器

 这里的const 迭代器针对的是 const 对象,如果是const 修饰的对象,是不用普通的迭代器的,因为 从 const 对象 const 的迭代器(指针),返回给非 const迭代器构造函数的时候,从事const 变成了 非const,造成了权限的放大。

所以还是需要单独实现const 迭代器,对于const 迭代器和普通的迭代器功能类似,只不过在 operator* 这个函数当中返回的不是 非const 对象,而是 const 对象:

	template<class T>
	struct const_List_iterator
	{
		typedef List_Node<T> Node;
		Node* _Node;

		const_List_iterator(Node* node)
			:_Node(node)
		{}

		const T& operator* ()
		{
			return _Node->_val;
		}

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

		bool operator!= (const const_List_iterator<T>& L)
		{
			return _Node != L._Node;
		}

		bool operator== (const const_List_iterator<T>& L)
		{
			return _Node == L._Node;
		}
	};



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

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

这样做虽然能够实现const迭代器的效果,但是太冗余了,不太好。

所以这个时候我们有了多个模版参数的使用,如下所示,是 普通迭代器类的模版:
 

	template<class T, class Ref>
	struct List_iterator
	{

······················

此时,在类当中的 typedef 哑鼓这样写:
 

		typedef List_iterator<T , T&> iterator; // 一定要是公有的,不然不能访问
		typedef List_iterator<T , const T&> const_iterator;

这样就可以在同一个类当中区分出 const类和 非 const 类,那么之间对 opeartor* 函数的修改就可以是这样的了;

		Ref operator* ()
		{
			return _Node->_val;
		}

同样,在类当中不同的地方都要进行修改,完整代码如下所示 :

template<class T, class Ref>
	struct List_iterator
	{
		typedef List_Node<T> Node;
		typedef List_iterator<T,Ref> selt;
		Node* _Node;

		List_iterator(Node* node)
			:_Node(node)
		{}

		Ref operator* ()
		{
			return _Node->_val;
		}

		selt& operator++ ()
		{
			_Node = _Node->_next;
			return *this;
		}

		selt operator++ (int)
		{
			_Node = _Node->_next;
			return *this;
		}

		bool operator!= (const selt& L)
		{
			return _Node != L._Node;
		}

		bool operator== (const selt& L)
		{
			return _Node == L._Node;
		}
	};
	template<class T>
	class List
	{
		typedef List_Node<T> Node;

	public:
		typedef List_iterator<T , T&> iterator; // 一定要是公有的,不然不能访问
		typedef List_iterator<T , const T&> const_iterator;

·········································

这样的话,看似是写了一个类,其实是写了两个类,但是代码的大小就节省了,这就是模版带来的好处。

在迭代器当中,还会使用到 " -> " 这个操作符,所以这个操作符也需要重载:

		T* operator-> ()
		{
			return &_Node->_val;
		}

但是,在使用的时候,下面这个场景就有些怪,如下所示:

struct A
	{
		A(int a1 = 0, int a2 = 0)
			:_a1(a1)
			, _a2(a2)
		{}

		int _a1;
		int _a2;
	};

	void test_list2()
	{
		list<A> lt;
		lt.push_back(A(1, 1));
		lt.push_back(A(2, 2));
		lt.push_back(A(3, 3));
		lt.push_back(A(4, 4));

		list<A>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._a1 << " " << (*it)._a2 << endl;
			cout << it->_a1 << " " << it->_a2 << endl;

			++it;
		}
		cout << endl;
	}

 上述的 cout 流当中的 it 用法其实应该这样写 :

it ->-> _a2

上面才是正常写法,但是在 operator-> 函数的使用当中却直接 it -> _a2 这样使用了,这是因为运算符重载要求可读性,编译器在这个地方进行特殊处理,省略了一个 " -> " 。

 而上述的 operator-> 这个函数如果是 const 的迭代器当中是实现的话,返回值应该是 const T*,所以这里,对迭代器类模版的参数再加上一个 ptr 来使用

最终迭代器的代码:

	template<class T, class Ref , class ptr>
	struct List_iterator
	{
		typedef List_Node<T> Node;
		typedef List_iterator<T,Ref, ptr> selt;
		Node* _Node;

		List_iterator(Node* node)
			:_Node(node)
		{}

		Ref operator* ()
		{
			return _Node->_val;
		}

		selt& operator++ ()
		{
			_Node = _Node->_next;
			return *this;
		}

		selt operator++ (int)
		{
			_Node = _Node->_next;
			return *this;
		}

		selt& operator-- ()
		{
			_Node = _Node->_prev;
			return *this;
		}

		selt operator-- (int)
		{
			_Node = _Node->_prev;
			return *this;
		}

		bool operator!= (const selt& L)
		{
			return _Node != L._Node;
		}

		bool operator== (const selt& L)
		{
			return _Node == L._Node;
		}

		ptr operator-> ()
		{
			return &_Node->_val;
		}
	};

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;

·······················································

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

list和vector 的比较

 list 就是链表, vector 是顺序表,两者的结构不同,导致两者的使用场景不同,两者也是典型的连续空间存储和链式空间存储不同特性的表现,下表是对两者进行的简单比较:

vector        list
底 层 结 构动态顺序表,是一段连续空间带头结点的双向循环链表
随 机 访 问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素
效率O(N)
插 入 和 删 除任意位置插入和删除效率低,需要搬移元素,时间复杂
度为O(N),插入时有可能需要增容,增容:开辟新空
间,拷贝元素,释放旧空间,导致效率更低
任意位置插入和删除效率高,不
需要搬移元素,时间复杂度为
O(1)
空 间 利 用 率底层为连续空间,不容易造成内存碎片,空间利用率
高,缓存利用率高
底层节点动态开辟,小节点容易
造成内存碎片,空间利用率低,
缓存利用率低
迭 代 器原生态指针对原生态指针(节点指针)进行封装
迭 代 器 失 效在插入元素时,要给所有的迭代器重新赋值,因为插入
元素有可能会导致重新扩容,致使原来迭代器失效,删
除时,当前迭代器需要重新赋值否则会失效
插入元素不会导致迭代器失效,
删除元素时,只会导致当前迭代
器失效,其他迭代器不受影响
使 用 场 景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随
机访问

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

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

相关文章

JavaScript:Promise 组合器

如果可以实现记得点赞分享&#xff0c;谢谢老铁&#xff5e; Promise在 JavaScript 中并不是一个新概念。它们是表示异步操作的最终完成或失败及其结果值的对象。 Promise 有三种可能的状态&#xff1a;pending – 初始状态&#xff08;仍在等待&#xff09;、已完成– Promis…

[NLP]使用Alpaca-Lora基于llama模型进行微调教程

Stanford Alpaca 是在 LLaMA 整个模型上微调&#xff0c;即对预训练模型中的所有参数都进行微调&#xff08;full fine-tuning&#xff09;。但该方法对于硬件成本要求仍然偏高且训练低效。 [NLP]理解大型语言模型高效微调(PEFT) 因此&#xff0c; Alpaca-Lora 则是利用 Lora…

Qt6 Qt Quick UI原型学习QML第七篇

文章目录 效果演示QML语法 ClickableImageV2.qmlQML语法 EasingCurves.qml时钟小球滚动QML 源码## 时钟小球滚动QML解释 语法解释参考动画片动画元素应用动画可点击图像V2上升的物体第一个对象第二个对象第三个对象缓和曲线分组动画并行动画连续动画嵌套动画 效果演示 QML语法 …

给APK签名—两种方式(flutter android 安装包)

前提&#xff1a;给未签名的apk签名&#xff0c;可以先检查下apk有没有签名 通过命令行查看&#xff1a;打开终端或命令行界面&#xff0c;导入包含APK文件的目录&#xff0c;并执行以下命令&#xff1a; keytool -printcert -jarfile your_app.apk 将 your_app.apk替换为要检查…

MybatisPlus查询条件为空字符串或null问题及解决

参考&#xff1a;https://www.yii666.com/blog/292928.html 解决办法 mybatisplus的条件构造器方法 eq()、like()等这些方法能支持第三个参数 condition condition是一个布尔值&#xff0c;当condition为false 时&#xff0c;当前这个条件方法不会生效&#xff0c;即生成的s…

曲线拟合曲面拟合(MATLAB拟合工具箱)位置前馈量计算(压力闭环控制应用)

利用PLC进行压力闭环控制的项目背景介绍请查看下面文章链接,这里不再赘述。 信捷PLC压力闭环控制应用(C语言完整PD、PID源代码)_RXXW_Dor的博客-CSDN博客闭环控制的系列文章,可以查看PID专栏的的系列文章,链接如下:张力控制之速度闭环(速度前馈量计算)_RXXW_Dor的博客-CSD…

flash attention 2论文学习

flash attention作者Tri Dao发布了flash attention 2&#xff0c;性能为flash attention的2倍。 优化点主要如下&#xff1a; 一、减少 non-matmul FLOPs A00中由于tensor core的存在&#xff0c;使得gpu对于浮点矩阵运算吞吐很高&#xff0c;如FP16/BF16可以达到312 TFLOPs/…

【弹力设计篇】聊聊熔断设计

为什么需要熔断 熔断这个词一听从生活中就是保险丝超过一定的温度后自动断开&#xff0c;以此来保护家用电器&#xff0c;属于电路中自我保护装置。如果没有熔断&#xff0c;那么家用电器一定会损坏的。 进一步再来分析一下&#xff0c;在分布式系统中&#xff0c;各个系统之间…

建立TCP连接的各个系统调用

TCP 连接的过程图 服务器 socket() 函数 socket() 返回的 sockfd 是一个描述符。socket()对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字&#xff0c;而socket()用于创建一个socket描述符&#xff08;socket descriptor&#xff09;&#xff0c;它唯一标识…

PX4仿真jMAVSim没有界面

切换java版本,使用java-8 sudo update-alternatives --config java删除旧文件 rm -rf Tools/jMAVSim/out编辑accessibility.properties 文件&#xff1a; sudo gedit /etc/java-8-openjdk/accessibility.properties注释掉下面这行 #assistive_technologiesorg.GNOME.Acessi…

笔试题:统计字符串中某字符串在其出现的字符个数

笔试题&#xff1a;统计字符串中某一子串的字符个数&#xff1a;例如字符串aabbcd,有aabb:4,ab:2 哈哈&#xff0c;这道题是小编面试音视频龙头企业的笔试题&#xff0c;以下是我写的代码&#xff1a;如果有错误&#xff0c;希望可以指正!!! 解题思路&#xff1a;利用双指针i和…

一刷总结篇

也养成了记录博客的好习惯吧&#xff0c;不过一刷有时也偷懒没跟上&#xff0c;但总体而言是比没刷代码随想录之前的状态要好。还是要记得当前目标是什么&#xff08;深抓主要矛盾&#xff09;。二刷代码随想录时每题要充分思考并且刷之前放过的题&#xff08;如扩展提等&#…

单相导轨电表支持双路双控吗?

单相导轨电表是一种电子式电能表&#xff0c;它采用导轨式安装结构&#xff0c;体积小、安装方便&#xff0c;适用于城市、农村或工厂企业的单相电能计量和集中式安装。单相导轨电表可以支持双路双控&#xff0c;也就是可以同时测量两个电路的电能消耗并进行控制。 双路双控是指…

图形编辑器开发:是否要像 Figma 一样上 wasm

大家好&#xff0c;我是前端西瓜哥。 wasm 拿来做 Web 端的图形编辑器貌似是不错的选择。 因为图形处理会有相当多无法利用到 WebGL GPU 加速的 CPU 密集的计算。比如对一条复杂贝塞尔曲线进行三角化&#xff0c;对多个图形进行复杂图形的布尔运算。 图形编辑器性能天花板 F…

TypeChat,用TypeScript快速接入AI大语言模型

TypeChat是C# 和 TypeScript 之父 Anders Hejlsberg全新的开源项目。使用AI在自然语言和应用程序和API之间建立桥梁&#xff0c;并且使用TypeScript。 现在出现了很多大型语言模型&#xff0c;但是如何将这些模型最好地集成到现有的应用程序中&#xff0c;如何使用人工智能来接…

设计模式||工厂模式(含有代码样例)

什么是工厂模式&#xff1f; 工厂模式&#xff08;Factory Pattern&#xff09;是一种常见的创建型设计模式&#xff0c;它提供了一种封装对象创建过程的方式。工厂模式通过定义一个创建对象的接口&#xff0c;但具体的对象创建在子类中实现&#xff0c;这样可以将对象的实例化…

Docker系列 1 - 镜像和容器

Docker系列 1 - 镜像和容器 1、关于 Docker2、镜像 image3、容器 container 1、关于 Docker docker官网&#xff1a;http://www.docker.com docker中文网站&#xff1a;https://www.docker-cn.com/ Docker Hub 仓库官网: https://hub.docker.com/ Docker 的基本组成&#…

【C++】多态原理剖析,Visual Studio开发人员工具使用查看类结构cl /d1 reportSingleClassLayout

author&#xff1a;&Carlton tag&#xff1a;C topic&#xff1a;【C】多态原理剖析&#xff0c;Visual Studio开发人员工具使用查看类结构cl /d1 reportSingleClassLayout website:黑马程序员C tool&#xff1a;Visual Studio 2019 date&#xff1a;2023年7月24日 目…

电脑记事本在哪里?电脑桌面显示记事本要怎么设置?

绝大多数上班族在使用电脑办公时&#xff0c;都需要随手记录一些琐碎或重要的事情&#xff0c;例如工作注意事项、常用的文案、某项工作的具体要求、多个平台的账号和密码等。于是就有不少小伙伴想要使用电脑记事本软件来记录&#xff0c;那么电脑记事本在哪里呢&#xff1f;想…

VM虚拟机网络配置桥接模式方法步骤

VM虚拟机配置桥接模式&#xff0c;可以让虚拟机和物理主机一样存在于局域网中&#xff0c;可以和主机相通&#xff0c;和互联网相通&#xff0c;和局域网中其它主机相通。 vmware为我们提供了三种网络工作模式&#xff0c;它们分别是&#xff1a;Bridged&#xff08;桥接模式&…