当前位置: 首页 > news >正文

List的模拟实现(2)

前言

上一节我们讲解了list的基本功能,那么本节我们就结合底层代码来分析list是怎么实现的,那么废话不多说,我们正式进入今天的学习:)

List的底层结构

我们先来看一下list的底层基本结构:

这里比较奇怪的一点是底层选择了void*来表示链表的指针,其实可以不用这么做

接下来是迭代器部分:

模拟实现链表难就难在实现迭代器,因为链表的物理空间不是连续的,所以不能简单的通过typedef一个指针变量来达到实现迭代器的目的

链表为空的初始化:

链表为空时不是简单的把所有的指针都赋空指针,而是创建一个节点,让这个节点的next和prev指针都指向自己。这就是创造了一个头节点,这里涉及到数据结构阶段双向带头链表的基本知识

get_node是申请节点,调用了内存池,由于我们还没有学习内存池,所以可以简单地将这里理解为malloc

下面是源代码中的头插尾插接口:

通过这些,我们可以知道,单纯实现链表的功能是没有什么难度的,和我们之前实现string和vector差不多,难就难在理解迭代器的实现

既然底层结构已经看的差不多了,那我们现在就来实现一下list吧

List的模拟实现

节点的基本结构

首先要实现链表的基本结构,链表的基本结构是节点

	template<class T>struct list_node{list_node(const T& data = T()):_data(data),_next(nullptr),_prev(nullptr){}T _data;list_node<T>* _next;list_node<T>* _prev;};

链表的借本结构

构造函数 

先来实现一下构造函数,根据底层是双向带头循环链表,写出代码如下:

	template<class T>class list{public:typedef list_node<T> Node;list(){_head = new Node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}private:Node* _head;size_t _size;};

 size和empty函数

实现size和empty函数,由于代码很简单,就不做讲解了:

		size_t size(){return _size;}bool empty(){return _size == 0;}

迭代器

因为不是每一个STL的容器都是存储在连续的物理空间之中,所以并不是每一个容器都支持下标+[]访问,但是所有的容器都有迭代器,都支持范围for,因此实现迭代器是很重要的一个步骤,我们来逐步分析迭代器的实现:

我们知道,对于一个节点而言,*解引用以后找到的不是节点的值,而是节点的地址;因为存储空间不连续,不能通过++来找到下一个节点,所以就不能仅通过typedef来实现迭代器,此时我们就应该考虑封装+运算符重载来实现迭代器,主要内容就是重载 *  ++    != 等,让其满足与其他迭代器相同的作用

	template<class T>struct list_iterator{Node* _node;};
迭代器的构造函数

		list_iterator(Node* node):_node(node){}
重载*
		T& operator*(){return _node->_data;}
重载前置++、--

由于++以后返回的数据类型依旧是迭代器,为了书写简便一点,调用typedef:

		Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}
重载后置++、-- 

因为后置++和--是将当前的值使用以后再执行++或者--,所以我们需要把原本的值拷贝一份,用于完成返回操作:

		Self& operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}
重载!=、==
		bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
重载-> 

先来分析一下为什么需要重载->这个符号:

接下来先构造一个使用->的情景:

假设有一个这样的结构体:

	struct AA{int _aa1 = 1;int _aa2 = 2;};

此时我们要在链表中存入这个结构体并且打印:

		list<AA> ltaa;ltaa.push_back(AA());ltaa.push_back(AA());ltaa.push_back(AA());ltaa.push_back(AA());list<AA>::iterator itaa = ltaa.begin();while (itaa != ltaa.end()){cout << *itaa << " ";++itaa;}cout << endl;

此时运行以后发现编译不通过

这里面的itaa通过解引用会得到节点中的data,data的数据类型是T,也就是自定义类型AA。因为没有重载流插入,所以这里的自定义类型无法打印,要想解决这个问题有两种方法:

第一种方法:给AA重载流插入

第二种方法:逐一访问结构体中的元素

		list<AA>::iterator itaa = ltaa.begin();while (itaa != ltaa.end()){cout << (*itaa)._aa1 << " and " << (*itaa)._aa2 << endl;++itaa;}cout << endl;

此时代码就成功运行了

但是这样写就很麻烦,此时就可以重载->这个运算符,这里先把代码写出来再对其进行分析:

		T* operator->(){return &_node->_data;}
		list<AA>::iterator itaa = ltaa.begin();while (itaa != ltaa.end()){cout << itaa->_aa1 << " and " << itaa->_aa2 << endl;++itaa;}cout << endl;


疑难解答:为什么operator-> 操作符重载函数返回值 T* 而不是 T&

在 list_iterator 这个迭代器类模板中,operator-> 操作符重载函数设计为返回 T* 而不是 T&,这与 operator-> 操作符的特殊语义和 C++ 语言的规定有关:

在 C++ 中,operator-> 操作符有特殊的处理规则。当我们使用 -> 操作符时,编译器会尝试不断应用 -> 操作,直到得到一个非指针类型的对象

简单地说就是:当你使用迭代器 it 调用 it->member 时,编译器会先调用 it.operator->(),如果 operator->() 返回的是一个指针,那么就直接访问该指针指向对象的成员;如果返回的不是指针,编译器会再次对返回值应用 ->

我们可以分两点来说明返回 T* 的合理性:

1. 符合使用习惯

迭代器的 operator-> 操作符通常用于模拟指针的行为,返回 T* 可以让迭代器像指针一样使用

例如,对于一个存储自定义类型 MyClass 的链表,使用迭代器访问 MyClass 的成员时,我们希望能够像使用指针一样直接通过 -> 操作符访问成员,如下所示:

namespace xiaobai
{template<class T>struct list_node{list_node(const T& data = T()): _data(data), _next(nullptr), _prev(nullptr) {}T _data;list_node<T>* _next;list_node<T>* _prev;};template<class T>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T> Self;list_iterator(Node* node) : _node(node){}T& operator*() {return _node->_data;}Self& operator++() {_node = _node->_next;return *this;}bool operator!=(const Self& s) {return _node != s._node;}T* operator->() {return &_node->_data;}Node* _node;};
}int main() 
{xiaobai::list_node<MyClass> node(MyClass(10));xiaobai::list_iterator<MyClass> it(&node);// 使用迭代器的 -> 操作符访问成员std::cout << it->value << std::endl;return 0;
}

在这个例子中,it->value 能够正常工作,因为 operator->() 返回的是 MyClass* 类型的指针,编译器可以直接通过该指针访问 value 成员

2. 避免额外的复杂度

如果 operator->() 返回 T&,编译器会再次对返回的引用应用 -> 操作,这会导致代码逻辑变得复杂,而且可能不符合用户的预期

例如:若T是一个自定义类型,由于T&不是指针类型,编译器会尝试调用 T 类型的 operator->() 函数,这可能会引发编译错误或意外的行为

所以T* operator->() 是为了让迭代器能够像指针一样使用,符合用户的使用习惯,并且遵循了 C++ 中 operator-> 操作符的特殊语义。而 T& operator->() 会破坏这种语义,导致代码逻辑复杂且不符合预期,因此通常不这样设计


这段代码初步看起来会很奇怪,它这里实际应该是两个箭头,但是为了可读性省略了一个箭头

(具体细节请浏览上面的疑难解答) 

cout << itaa.operator->()->_aa1 << " and " << itaa.operator->()->_aa2 << endl;

第一个箭头是运算符重载,返回的是AA*类型的变量

第二个箭头是原生指针,通过这个箭头就可以访问到list中的数据了

 迭代器代码初步集合

到这里我们就完成了迭代器:

	template<class T>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T> Self;list_iterator(Node* node):_node(node){}T& operator*(){return _node->_data;}Self& operator++(){_node = _node->_next;return *this;}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;}bool operator==(const Self& s){return _node == s._node;}T* operator->(){return &_node->_data;}Node* _node;};

此时再去链表的类中typedef迭代器,简化书写:

	template<class T>class list{public:typedef list_node<T> Node;typedef list_iterator<T> iterator;//.......private:Node* _head;size_t _size;};

重载打印容器以及对迭代器的修缮

实现了迭代器,就满足了范围for遍历,此时就可以写一个访问容器的接口,因为所有容器都具有迭代器,所以这个接口也可以访问其他STL容器

	template<class Container>void print_container(const Container& con){for (auto e : con){cout << e << " ";}cout << endl;}

现在测试一下这个打印:

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

此时运行代码却发现编译报错了,为什么函数外面使用范围for可以打印,但是print_container函数里面使用范围for却打印不了,这是为什么呢?

因为函数里面的参数是const参数,函数外面是普通的参数,我们还没有实现const迭代器,所以这里就会报错

再来分析一下const迭代器的特征:先思考一下const迭代器为什么是const_iterator而不是普通的iterator加上前置的const修饰?

要想想清楚这个问题,我们首先需要明白const迭代器是自身不能修改还是指向的内容不能修改。这里我们结合指针不同位置的const意义来理解:

T* const ptr;
const T* ptr;

第一种:指针本身不允许改变

第二种:指针指向的内容不允许改变

很明显的,const迭代器是想完成第二种功能,如果是const iterator的话,const的作用是确保iterator本身不被修改迭代器本身不能修改的话就无法实现遍历。const迭代器的产生是为了在保证遍历的前提之下保证迭代器指向的内容也不被修改

那么该怎么修改代码来完成这一目的呢?

我们在实现迭代器这一个类时,对数据的修改接口是operator*以及operator->,对于const迭代器而言,返回类型就应该加上const:

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

所以这里就需要拷贝之前的普通迭代器代码,再在*和->的重载中加上const完成const迭代器

	template<class T>struct list_const_iterator{typedef list_node<T> Node;typedef list_iterator<T> Self;list_iterator(Node* node):_node(node){}const T& operator*(){return _node->_data;}Self& operator++(){_node = _node->_next;return *this;}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;}bool operator==(const Self& s){return _node == s._node;}const T* operator->(){return &_node->_data;}Node* _node;};

此时在重载一下begin和end函数,给一个const迭代器版本,并且在list类中typedef const迭代器:

(普通迭代器可以调用const迭代器的begin和end,因为权限可以缩小;反之const迭代器无法调用普通迭代器的begin和end,因为权限不能放大

	template<class T>class list{public:typedef list_node<T> Node;typedef list_iterator<T> iterator;typedef list_const_iterator<T> const_iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}//........private:Node* _head;size_t _size;};

**************************************************************************************************************

在 list 类中重载 `begin` 和 `end` 函数(即提供 `const` 和非 `const` 版本)是为了支持对 `const` 对象和非 `const` 对象的迭代操作,下面详细解释原因:

1. 支持非 `const` 对象的迭代

当你有一个非 `const` 的 `list` 对象时,你可能希望能够修改列表中的元素。此时,你需要使用非 `const` 迭代器,因为非 `const` 迭代器允许对其所指向的元素进行修改。非 `const` 版本的 `begin` 和 `end` 函数返回的就是非 `const` 迭代器

2. 支持 `const` 对象的迭代

当你有一个 `const` 的 `list` 对象时,你不应该修改列表中的元素,因为这违反了 `const` 限定的语义。此时,你需要使用 `const` 迭代器,`const` 迭代器只能用于访问元素,而不能修改元素。`const` 版本的 `begin` 和 `end` 函数返回的就是 `const` 迭代器

3. 函数重载的实现

通过函数重载,编译器会根据对象是否为 `const` 来选择合适的 `begin` 和 `end` 函数版本。如果对象是 `const` 的,编译器会调用 `const` 版本的 `begin` 和 `end` 函数,返回 `const` 迭代器;如果对象是非 `const` 的,编译器会调用非 `const` 版本的 `begin` 和 `end` 函数,返回非 `const` 迭代器。

重载 `begin` 和 `end` 函数是为了提供对 `const` 对象和非 `const` 对象的一致迭代接口,同时保证 `const` 对象的元素不被意外修改,遵循了 C++ 的 `const` 正确性原则,使得代码更加安全和灵活。

**************************************************************************************************************

 此时用一个很特别的用例来测试一下代码(这里修改了一下print_container函数):

	template<class Container>void print_container(const Container& con){list<int>::const_iterator it = con.begin();while(it != con.end()){*it += 10;cout << *it << " ";++it;}cout << endl;for (auto e : con){cout << e << " ";}cout << endl;}void test_list01(){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()){cout << *it << " ";++it;}cout << endl;}

可以看到,这里非常奇怪,明明我们故意在打印容器代码里面修改了const迭代器指向的内容,想让它报错,但是代码却成功运行了,这是为什么呢?

这就涉及到我们之前提及的按需实例化,这里的代码存在着编译错误,' *it += 10 ' 中的*it返回的数据类型应该是const T&,对其进行+=10操作本身是应该编译报错的,但是这里非但没有报错反而还运行成功了

之所以产生这样的结果,是因为模板和类模板都会走按需实例化。模板不能被直接调用,模板用于将对应类型的代码实例化出来,实例化出来的代码才能够使用

因为我们在主函数中没有使用print_container函数,编译器在编译的过程中就不会去实例化print_container函数里面的内容,而没有实例化的代码编译器只会对其进行很简单的语法检查,比如:语句结束没加分号,对于细枝末节的操作编译器不会检查。

此时如果使用print_container函数就会报错

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

对迭代器代码的简化

刚才那种实现方式我们的确能成功的完成任务,但是const迭代器和普通迭代器除了operator*和->的代码有区别以外,其他地方的代码一模一样,这么设计的话在代码长度上就会非常的冗余,我们去底层观察一下是怎么实现迭代器的:

可以看到,它没有将迭代器和const迭代器定义为两个类,而是同一个类模板。而且它除了传入了 T ,还传入了 T& 和 T* 这两个参数

如果是普通迭代器,它的中的参数是 T T& T* ,分别传给了参数 T Ref Ptr ,而 Ref 和 Ptr 分别被重命名为 reference 和 pointer ,而 reference 和 pointer 分别又是 operator* 和 operator-> 的返回值

如果是const迭代器,它的中的参数是 T 、const T&、 const T* ,分别传给了参数 T Ref Ptr , Ref 和 Ptr 分别被重命名为 reference 和 pointer ,而 reference 和 pointer 分别又是 operator* 和 operator-> 的返回值

通过这种形式就控制住了operator*和->的返回值

虽然这两种写法在本质上没有区别,只是之前的写法是自己写了两个类,而这种方法是实现了一个类模板并且传了不同的模板参数给编译器,通过编译器来实例化出两个类。通过这样的方法,我们就可以实现精简代码的需求

那么就根据底层实现的原理来完善一下原来的代码吧:

	template<class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//.......Node* _node;};
	template<class T>class list{public:typedef list_node<T> Node;typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;//.......private:Node* _head;size_t _size;};
 迭代器代码的最终整合
	template<class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Self& operator++(){_node = _node->_next;return *this;}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;}bool operator==(const Self& s){return _node == s._node;}Ptr operator->(){return &_node->_data;}Node* _node;};

begin、end函数

begin函数用于返回链表第一个元素

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

也可以用返回匿名对象来简化代码:

			return iterator(_head->_next);

还有一种更加简单的写法:

			return _head->_next;

因为这里返回的是一个节点的指针,节点的指针是可以隐式类型转换成iterator的,单参数的构造函数支持隐式类型的转换


end函数同理:

		iterator end(){return _head->_prev;}

到这里,我们来测试一下:

		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()){cout << *it << " ";++it;}cout << endl;

这里迭代器的设计相当完美,假设链表为空,此时就只有一个哨兵位的头节点。由于begin是_head的next,此时它指向的还是自己。end是_head,也同样指向自己,此时it = begin() == end在条件判断的时候就不会进入循环,造成非法访问

我们在实现迭代器的时候没有写拷贝构造函数,这就意味着指针在与指针的拷贝的时候进行的是浅拷贝,那么浅拷贝会不会出现问题呢?

答案是不会,我们就以上面的测试代码为例,我们给it赋了头节点,此时我们期望的就是it也指向原来的头节点,就是需要浅拷贝,而不是开一个新的链表,指向新的链表中的头节点

这里的迭代器只是一个访问和遍历链表的工具,它不像vector、string等容器一样,它所指向的资源并不是属于它自己的,它指向的资源是属于list的。所以它也不要写析构函数,它不能去释放list的空间


insert函数

insert函数用于在pos位置之前插入数据,要想实现这个功能还是比较简单的,仅需要通过简单的修改指针指向即可:

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

push_back和push_front函数

可以通过复用insert函数完成这两个函数:

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

erase函数

erase函数用于删除pos位置的节点,这个函数的实现也很简单,也是只需要修改指针指向再释放节点即可:

注意erase不能删除哨兵位的头节点,在这里加上assert断言

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

pop_back和pop_front函数

通过复用erase即可实现这两个函数:

		void pop_back(iterator pos){erase(--end());}void pop_front(iterator pos){erase(begin());}

结尾

因为链表很多接口的实现非常简单,这里就没有把所有接口的实现代码一一列举出来了,下一节我们接着分析链表,我们将会分析迭代器失效。那么本节的内容就到此结束了,谢谢您的浏览!!!!!!!!!

相关文章:

List的模拟实现(2)

前言 上一节我们讲解了list的基本功能&#xff0c;那么本节我们就结合底层代码来分析list是怎么实现的&#xff0c;那么废话不多说&#xff0c;我们正式进入今天的学习&#xff1a;&#xff09; List的底层结构 我们先来看一下list的底层基本结构&#xff1a; 这里比较奇怪的…...

【C++设计模式】观察者模式(1/2):从基础到优化实现

1. 引言 在 C 软件与设计系列课程中&#xff0c;观察者模式是一个重要的设计模式。本系列课程旨在深入探讨该模式的实现与优化。在之前的课程里&#xff0c;我们已对观察者模式有了初步认识&#xff0c;本次将在前两次课程的基础上&#xff0c;进一步深入研究&#xff0c;着重…...

可狱可囚的爬虫系列课程 13:Requests使用代理IP

一、什么是代理 IP 代理 IP&#xff08;Proxy IP&#xff09;是一个充当“中间人”的服务器IP地址&#xff0c;用于代替用户设备&#xff08;如电脑、手机等&#xff09;直接与目标网站或服务通信。用户通过代理IP访问互联网时&#xff0c;目标网站看到的是代理服务器的IP地址&…...

冒险岛079 V8 整合版源码搭建教程+IDEA启动

今天教大家来部署下一款超级怀旧游戏冒险岛&#xff0c;冒险岛源码是开源的&#xff0c;但是开源的代码会有各种&#xff0c;本人进行了加工整合&#xff0c;并且用idea进行了启动测试&#xff0c;经过修改后没有任何问题。 启动截图 后端控制台 前端游戏界面 声明 冒险岛源码…...

Web刷题之PolarDN(中等)

1.到底给不给flag呢 代码审计 一道典型的php变量覆盖漏洞 相关知识 什么是变量覆盖漏洞 自定义的参数值替换原有变量值的情况称为变量覆盖漏洞 经常导致变量覆盖漏洞场景有&#xff1a;$$使用不当&#xff0c;extract()函数使用不当&#xff0c;parse_str()函数使用不当&…...

[250224] Yaak 2.0:Git集成、WebSocket支持、OAuth认证等 | Zstandard v1.5.7 发布

目录 Yaak 2.0 发布&#xff1a;Git 集成、WebSocket 支持、OAuth 认证等众多功能&#xff01;Zstandard v1.5.7 发布&#xff1a;性能提升&#xff0c;稳定性增强 Yaak 2.0 发布&#xff1a;Git 集成、WebSocket 支持、OAuth 认证等众多功能&#xff01; Yaak&#xff0c;一款…...

插入排序:一种简单而直观的排序算法

大家好&#xff01;今天我们来聊聊一个简单却非常经典的排序算法——插入排序&#xff08;Insertion Sort&#xff09;。在所有的排序算法中&#xff0c;插入排序是最直观的一个。 一、插入排序的基本思想 插入排序的核心思想是&#xff1a;将一个待排序的元素&#xff0c;插…...

vue2响应式数据原理

1. 核心原理 Vue 2 的响应式系统基于 Object.defineProperty&#xff0c;通过 依赖收集 和 派发更新 来实现数据的响应式 依赖收集&#xff1a;在读取数据时&#xff0c;记录哪些函数&#xff08;或组件&#xff09;依赖了该数据。派发更新&#xff1a;在修改数据时&#xff…...

对免认证服务提供apikey验证

一些服务不带认证&#xff0c;凡是可以访问到服务端口&#xff0c;都可以正常使用该服务&#xff0c;方便是方便&#xff0c;但是不够安全。 比如ollama默认安装后就是这样。现在据说网上扫一下端口11434&#xff0c;免apikey的ollama服务一大堆。。。 那我们怎样将本机安装的o…...

算法——Trie 树

Trie 树&#xff08;前缀树或字典树&#xff09;是一种高效处理字符串集合的树形数据结构&#xff0c;核心思想是通过共享公共前缀来优化存储和查询。以下是 Trie 树的详细介绍&#xff1a; 1. Trie 树的基本概念 结构特点&#xff1a; 每个节点表示一个字符。从根节点到某一节…...

python中的JSON数据格式

文章目录 什么是json主要功能Python数据和Json数据的相互转化 什么是json JSON是一种轻量级的数据交互格式。可以按照JSON指定的格式去组织和封装数据。JSON本质上是一个带有特定格式的字符串。 主要功能 json就是一种在各个编程语言中流通的数据格式&#xff0c;负责不同编…...

服务器能否拒绝非浏览器发起的HTTP请求?

互联网各领域资料分享专区(不定期更新): Sheet 前言 服务器可以采取多种方法来拒绝非浏览器发起的HTTP请求,但需要明确的是:HTTP协议本身并不限制客户端类型,任何符合协议规范的请求都会被处理。因此,拒绝非浏览器请求需依赖额外策略。 正文 一、基于请求头过滤 1、Us…...

深度学习之图像分类(二)

前言 文章主要是通过实战项目——食品分类来理解分类项目的整体流程。除此之外&#xff0c;还需要对半监督学习&#xff0c;迁移学习&#xff0c;数据增广&#xff0c;Adam和AdamW进行了解。 数据增广 图片增广&#xff08;Image Data Augmentation&#xff09;是深度学习中一种…...

数据库高安全—openGauss安全整体架构安全认证

openGauss作为新一代自治安全数据库&#xff0c;提供了丰富的数据库基础安全能力&#xff0c;并逐步完善各类高阶安全能力。这些安全能力涵盖了访问登录认证、用户权限管理、审计与追溯及数据安全隐私保护等。本章节将围绕openGauss安全机制进行源码解读&#xff0c;以帮助数据…...

利用开源小智AI制作桌宠机器狗

本文主要介绍如何利用开源小智AI制作桌宠机器狗 1 源码下载 首先下载小智源码,下载地址, 下载源码后,使用vsCode打开,需要在vscode上安装esp-idf,安装方式请自己解决 2 源码修改 2.1添加机器狗控制代码 在目录main/iot/things下添加dog.cc文件,内容如下; #include…...

uniapp在app下使用mqtt协议!!!支持vue3

什么&#xff1f;打包空白&#xff1f;分享一下我的解决方法&#xff01; 第一步 找大师算过了&#xff0c;装4.1版本运气好&#xff01; 所以根目录执行命令… npm install mqtt4.1.0第二步 自己封装一个mqtt文件方便后期开坛做法&#xff01; // utils/mqtt.js import mqt…...

Orange 开源项目 - 集成阿里云大模型

1 阿里云的大模型服务平台百炼 阿里云的大模型服务平台百炼是一站式的大模型开发及应用构建平台。不论是开发者还是业务人员&#xff0c;都能深入参与大模型应用的设计和构建。您可以通过简单的界面操作&#xff0c;在5分钟内开发出一款大模型应用&#xff0c;或在几小时内训练…...

DSP芯片C6678的SRIO及其中断跳转的配置

C6678SRIO读写测试门铃中断跳转测试 SRIO简述代码前言SRIO配置原始代码1.使能电源2.初始化SRIO回环修改 3.SRIO测试 Doorbell门铃中断1.初始化中断函数2.中断向量表建立3.中断向量表的链接 本博客基于创龙“678ZH产品线”的SRIO代码&#xff0c;部分参考于网友们的博客&#xf…...

MongoDB#常用脚本

批量插入数据脚本 const oneDayAgo new Date(Date.now() - 1 * 24 * 60 * 60 * 1000);const documents []; for (let i 1; i < 100; i) {documents.push({id: i, // 递增的 idcreateTime: oneDayAgo, // 1天前的日期data: Sample data ${i} // 其他字段&#xff08;可选…...

MySQL 主从集群同步延迟问题分析与解决方案

MySQL 主从复制&#xff08;Replication&#xff09;是构建高可用架构的核心技术&#xff0c;但在实际应用中&#xff0c;主从同步延迟&#xff08;Replication Lag&#xff09;是常见且棘手的问题。延迟会导致从库数据不一致、读请求返回旧数据&#xff0c;甚至引发业务逻辑错…...

论文笔记(七十二)Reward Centering(五)

Reward Centering&#xff08;五&#xff09; 文章概括摘要附录B 理论细节C 实验细节D 相关方法的联系 文章概括 引用&#xff1a; article{naik2024reward,title{Reward Centering},author{Naik, Abhishek and Wan, Yi and Tomar, Manan and Sutton, Richard S},journal{arX…...

FFmpeg 是什么?为什么?怎么用?

摘要&#xff1a;本文介绍了 FFmpeg&#xff0c;一个功能强大的开源多媒体处理工具&#xff0c;广泛应用于视频和音频文件的处理。FFmpeg 支持多种多媒体格式&#xff0c;能够实现视频编码/解码、格式转换、裁剪、合并、音频提取、流媒体处理等功能。本文详细阐述了 FFmpeg 的主…...

雷池WAF动态防护技术实测

作者&#xff1b; Hacker / 0xh4ck3r 介绍 长亭雷池&#xff08;SafeLine&#xff09;是由北京长亭科技有限公司耗时近10年研发并推出的Web应用防火墙&#xff08;WAF&#xff09;&#xff0c;其核心检测能力由智能语义分析算法驱动。雷池旨在为用户提供高质量的Web攻击防护、…...

BUU40 [CSCCTF 2019 Qual]FlaskLight1【SSTI】

模板&#xff1a; {{.__class__.__base__.__subclasses__()[80].__init__.__globals__[__builtins__].eval("__import__(os).popen(type flag.txt).read()")}} 是个空字符串&#xff0c;.__class__代表这个空字符串的类是什么&#xff08;这里是单引号双引号都行&a…...

【每日八股】Redis篇(二):数据结构

Redis 数据类型&#xff1f; 主要有 STRING、LIST、ZSET、SET 和 HASH。 STRING String 类型底层的数据结构实现主要是 SDS&#xff08;简单动态字符串&#xff09;&#xff0c;其主要应用场景包括&#xff1a; 缓存对象&#xff1a;可以用 STRING 缓存整个对象的 JSON&…...

VScode+stfp插件,实现文件远程同步保存【2025实操有效】

目录 1 痛点2 准备工作3 操作步骤3.1 第一步&#xff0c;下载STFP插件3.2 第二步&#xff0c;修改配置文件3.3 第三步&#xff0c;测试是否成功 4 后记 1 痛点 我一直用vscode远程连接服务器&#xff0c;传代码文件等到服务器上面&#xff0c;突然有一次服务器那边尽心维修&am…...

115 道 MySQL 面试题,从简单到深入!

1. 什么是数据库事务&#xff1f; 数据库事务是一个作为单个逻辑工作单元执行的一系列操作。事务具有ACID属性&#xff0c;即原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&#xff09;和持久性&#xf…...

不同安装路径重复R包清理

df <- as.data.frame(installed.packages()) table(duplicated(df$Package)) ids <- df$Package[duplicated(df$Package)] df2 <- subset(df, df$Package %in% ids)...

Grouped-Query Attention(GQA)详解: Pytorch实现

Grouped-Query Attention&#xff08;GQA&#xff09;详解 Grouped-Query Attention&#xff08;GQA&#xff09; 是 Multi-Query Attention&#xff08;MQA&#xff09; 的改进版&#xff0c;它通过在 多个查询头&#xff08;Query Heads&#xff09;之间共享 Key 和 Value&am…...

选择排序:简单高效的选择

大家好&#xff0c;今天我们来聊聊选择排序&#xff08;Selection Sort&#xff09;算法。这是一个非常简单的排序算法&#xff0c;适合用来学习排序的基本思路和操作。选择排序在许多排序算法中以其直观和易于实现的特点著称&#xff0c;虽然它的效率不如其他高效算法&#xf…...

(教程)PDF 字体技术入门

PDF字体技术 许多人觉得PDF字体令人困惑的主要原因在于PDF文件可以使用多种不同的字体技术。PDF文件规范已经存在16年&#xff0c;在此期间&#xff0c;出现了多种不同的字体技术&#xff08;既有技术方面的原因&#xff0c;也有商业方面的原因&#xff09;。因此&#xff0c;…...

LabVIEW中CFURL.llb 工具库说明

CFURL.llb 是 LabVIEW 2019 安装目录下 C:\Program Files (x86)\National Instruments\LabVIEW 2019\vi.lib\Platform\ 路径下的工具库&#xff0c;主要用于处理 LabVIEW 与 URL 相关的操作&#xff0c;涵盖 URL 解析、HTTP 请求发送、数据传输等功能模块&#xff0c;帮助开发者…...

BGP配置华为——路径优选验证

实验拓扑 实验要求 实现通过修改AS-Path属性来影响路径选择实现通过修改Local_Preference属性来影响路径选择实现通过修改MED属性来影响路径选择实现通过修改preferred-value属性来影响路径选择 实验配置与效果 1.改名与IP配置 2.as300配置OSPF R3已经学到R2和R4的路由 3.…...

Linux8-互斥锁、信号量

一、前情回顾 void perror(const char *s);功能&#xff1a;参数&#xff1a; 二、资源竞争 1.多线程访问临界资源时存在资源竞争&#xff08;存在资源竞争、造成数据错乱&#xff09; 临界资源&#xff1a;多个线程可以同时操作的资源空间&#xff08;全局变量、共享内存&a…...

【Springboot3】Springboot3 搭建RocketMQ 最简单案例

说来也奇怪&#xff0c;RocketMQ 不能很好的兼容Springboot3&#xff0c;刚开始上手Springboot3集成RocketMQ会发现总是不能实例化RocketMQTemplate&#xff0c;老是启动时报错。本项目采用Springboot3&#xff0c;JDK21 &#xff0c;Maven 3.9&#xff0c;提供一个非常简单的示…...

使用docker安装mysql 挂起之后 再次运行无法连接问题

# 首先 vim /usr/lib/sysctl.d/00-system.conf # 在最后面添加 net.ipv4.ip_forward 1 # 然后保存退出&#xff0c;接着重启网络服务 systemctl restart network # 重启以后&#xff0c;输入以下命令&#xff0c;查看IPv4转发状态 sysctl net.ipv4.ip_forward # 显示net.ipv4…...

hot100-二叉树

二叉树 二叉树递归 相当于这个的顺序来回调换 class Solution {private List<Integer> res new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {if(root null)return res;inorderTraversal(root.left);res.add(root.val);inorde…...

从零开始用react + tailwindcs + express + mongodb实现一个聊天程序(二)

1.安装mogondb数据库 参考MongoDB安装配置教程&#xff08;详细版&#xff09;_mongodb安装详细步骤-CSDN博客 安装mondbcompass数据库连接工具 参考https://www.mongodb.com/zh-cn/docs/compass/current/connect/ 2.后端服务 1.创建src文件夹 并在src文件夹下创建 index…...

基于Spring Boot的党员学习交流平台设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

Plantsimulation中机器人怎么通过阻塞角度设置旋转135°

创建一个这样的简单模型。 检查PickAndPlace的角度表。源位于180的角位置&#xff0c;而物料终结位于90的角位置。“返回默认位置”选项未被勾选。源每分钟生成一个零件。启动模拟时&#xff0c;Plant Simulation会选择两个位置之间的最短路径。示例中的机器人无法绕135的角位…...

2025.2.23机器学习笔记:PINN文献阅读

2025.2.23周报 一、文献阅读题目信息摘要Abstract创新点网络架构架构A架构B架构C 实验结论后续展望 一、文献阅读 题目信息 题目&#xff1a; Physics-Informed Neural Networks for Modeling Water Flows in a River Channel期刊&#xff1a; IEEE TRANSACTIONS ON ARTIFICI…...

关于Postman自动获取token

在使用postman测试联调接口时&#xff0c;可能每个接口都需要使用此接口生成的令牌做Authorization的Bearer Token验证&#xff0c;最直接的办法可能会是一步一步的点击&#xff0c;如下图&#xff1a; 在Authorization中去选择Bearer Token&#xff0c;然后将获取到的token粘贴…...

Android KMP初探

Android KMP初探 前言&#xff1a; 最近线上听了Kotlin官网举行的KMP会议&#xff0c;感觉听神奇的&#xff0c;于是就把官方demo下载下来尝试了一下&#xff0c;下载插件和所需要的依赖都用了很久&#xff0c;但是发现里面的代码很少&#xff0c;于是尝试自己手写了一下&…...

ncDLRES:一种基于动态LSTM和ResNet的非编码RNA家族预测新方法

现有的计算方法主要分为两类&#xff1a;第一类是通过学习序列或二级结构的特征来预测ncRNAs家族&#xff0c;另一类是通过同源序列之间的比对来预测ncRNAs家族。在第一类中&#xff0c;一些方法通过学习预测的二级结构特征来预测ncRNAs家族。二级结构预测的不准确性可能会导致…...

前端项目打包过滤指定icon文件

1.需求背景 项目中有部分功能需要vip权限才可以使用&#xff0c;所有部分筛选、按钮 等有vip的icon提示 如下图 此项目衍生出一个特殊版本&#xff0c;此版本无需登录且拥有最高权限&#xff0c;所以产品要求去除项目中的所有vip相关的提示。 2.解决思路 &#xff08;1&am…...

蓝桥杯 Java B 组之最短路径算法(Dijkstra、Floyd-Warshall)

Day 2&#xff1a;最短路径算法&#xff08;Dijkstra、Floyd-Warshall&#xff09; &#x1f4d6; 一、最短路径算法简介 最短路径问题是图论中的经典问题&#xff0c;主要用于求解 单源最短路径 或 多源最短路径。在实际应用中&#xff0c;最短路径广泛应用于 导航系统、网络…...

科普:HTTP端口80和HTTPS端口443

你会发现&#xff0c;有的网址不带端口号&#xff0c;怎么回事&#xff1f; HTTP协议默认端口&#xff1a;HTTP协议的默认端口是80。当用户在浏览器中输入一个没有指定端口的以http://开头的网址时&#xff0c;浏览器会自动使用80端口与服务器建立连接&#xff0c;进行超文本数…...

如何安装vm和centos

安装 VMware Workstation Pro 步骤 1&#xff1a;下载 VMware Workstation Pro 访问 VMware 官方网站&#xff08;Desktop Hypervisor Solutions | VMware &#xff09;&#xff0c;根据你的操作系统选择合适的版本进行下载。 步骤 2&#xff1a;运行安装程序 找到下载的安装…...

鸿蒙-验证码输入框的几种实现方式-上

文章目录 效果图、优缺点多TextInput多 TextCanvas 绘制 多个 TextInput 拼接放置四个输入框焦点移动输入时向后移动输入完成回调删除时向前移动 防止点击总结 最近在做应用鸿蒙化&#xff0c;说白了就是把原来Android、iOS的代码重新用ArkTS写一遍&#xff0c;我负责基础建设和…...

Vi 编辑器基本使用指南

一、Vi 编辑器的启动与退出 启动 Vi 编辑器 在终端中&#xff0c;输入vi加上要编辑的文件名&#xff0c;如vi example.txt&#xff0c;如果example.txt存在&#xff0c;Vi 编辑器会打开该文件&#xff1b;若不存在&#xff0c;则会创建一个新的空文件并打开。如果只输入vi&am…...