list【2】模拟实现(含迭代器实现超详解哦)

模拟实现list

  • 引言(实现概述)
  • list迭代器实现
    • 默认成员函数
    • operator* 与 operator->
    • operator++ 与 operator--
    • operator== 与 operator!=
    • 迭代器实现概览
  • list主要接口实现
    • 默认成员函数
      • 构造函数
      • 析构函数
      • 赋值重载
    • 迭代器
    • 容量
    • 元素访问
    • 数据修改
      • insert
      • erase
      • push_back 与 push_front
      • pop_back 与 pop_front
      • clear
      • swap
  • 源码概览
  • 总结

引言(实现概述)

在前面,我们介绍了list的使用:
戳我看list的介绍与使用详解哦

在本篇文章中将重点介绍list的接口实现,通过模拟实现可以更深入的理解与使用list
在这里插入图片描述
我们模拟实现的 list 底层是一个带头双向循环链表

在实现list时,我们首先需要一个结构体以表示链表中结点的结构list_node,大致包括数据与指向前后结点的指针

template<class T>
struct list_node  //结点类
{list_node<T>* _prev;list_node<T>* _next;T _date;list_node(const T& date = T()) //匿名对象: _prev(nullptr), _next(nullptr), _date(date){}
};

在有了结点之后,还需要一个 list类,包括双向链表的头结点指针_pHead,链表中的元素个数_size

template<class T>
class list  //带头双向循环链表
{typedef list_node<T> Node;
private:Node* _pHead;size_t _size;
};

大致结构如下:
在这里插入图片描述
关于带头双向循环链表的实现可以参考之前的C语言版本实现,在本篇文章中结构将不是最重点的内容:戳我看C语言实现带头双向循环链表详解哦

与vector相同,list是一个类模板,其声明与定义不能分离。我们将模拟实现的list放在我们创建的命名空间内,以防止与库发生命名冲突。
在list的模拟实现中,我们只实现一些主要的接口,包括默认成员函数、迭代器、容量、元素访问与数据修改

list迭代器实现

与vector不同,list的迭代器是指针的封装,通过运算符重载来实现原生指针的功能
我们可以通过封装结点的指针,即list_node<T>*,来实现迭代器:

默认成员函数

对于默认成员函数,其实我们只需要实现构造函数即可,这个__list_iterator类的属性只有一个结构体指针,并不存在类中申请的资源,所以编译器生成的析构函数与赋值运算符重载就够用了,不需要再实现了。

  1. 默认构造
    对于默认构造函数,我们可以使用缺省参数,即参数类型为结构体指针,缺省值为nullptr
    直接在初始化列表中用参数初始化类属性即可:
	__list_iterator(Node* pNode = nullptr):_pNode(pNode){}
  1. 拷贝构造
    对于拷贝构造,参数必须是类类型的引用,可以加上const修饰(我们可以在类中将类类型__list_iterator<T, Ref, Ptr>重命名为self,以方便书写)
    在函数中直接赋值即可:
	__list_iterator(const self& it){_pNode = it._pNode;}

operator* 与 operator->

迭代器使用时应该是与指针基本类似的,所以也需要重载*->

  1. 重载 *
    指针当然可以进行解引用的操作,指向容器中元素的指针。对这个指针进行解引用操作结果就应该是该指针指向的元素。对于list的迭代器而言,解引用该迭代器的结果就应该是结点中的_data元素的引用,类型为&T
    (我们可以为模板参数加上一个参数,即Ref,它表示T&
	Ref operator*(){return _pNode->_date;}
  1. 重载->
    T是自定义类型时,其指针还可以通过->直接访问到T类型对象_data中的元素,起指针作用的迭代器自然也需要实现这个功能
    但是对于这个运算符重载而言,它并不知道要返回T类型对象中的什么元素,所以这个operator->函数可以直接返回该迭代器对应的结点中的_data元素的指针,然后在调用的时候再通过->来访问其中元素:it->->a;(通过迭代器it访问T类型对象中的a元素)。
    但是,这样的形式访问又与指针的用法不一致,所以这里有一个特殊的规定,即规定需要使用it->a;的方式通过迭代器访问T类型对象中的元素,以获得与原生指针相同的使用方式
    (我们可以为模板参数加上一个参数,即Ptr,它表示T*
	Ptr operator->(){return &(_pNode->_date);}

operator++ 与 operator–

  1. 重载 ++
    ++操作即实现迭代器的指向向后移动一个元素,list的迭代器底层是一个结构体指针,所以只需要将当前_pNode->_next 赋值给_pNode即可
    ++分为前置++与后置++,在区别这两个的实现时,前面类和对象时已经详细介绍过了,即给后置++增加一个参数int,但是不传参,只用于区分前置与后置。
    另外后置++需要创建临时对象,在*this++后必须要返回临时对象而非引用:
	self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}
  1. 重载 --
    重载--与前面的++类似,即_pNode->_prev 赋值给_pNode即可
	self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}

operator== 与 operator!=

对于==!= 重载,即判断两个迭代器对象的属性是否相等即可

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

迭代器实现概览

	template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _pNode;__list_iterator(Node* pNode = nullptr):_pNode(pNode){}__list_iterator(const self& it){_pNode = it._pNode;}Ref operator*(){return _pNode->_date;}Ptr operator->(){return &(_pNode->_date);}self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}bool operator==(const self& it){return _pNode == it._pNode;}bool operator!=(const self& it){return _pNode != it._pNode;}};

list主要接口实现

在实现list之前,我们可以对一些较为麻烦的类型进行重命名以方便书写:

typedef list_node<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

默认成员函数

构造函数

实现构造函数时,我们需要实现无参构造、n个指定元素构造、迭代器区间构造以及拷贝构造

无参构造
由于我们模拟实现的list底层为带头双向循环链表,所以在无参构造时虽然没有在list中放入元素,但是还使需要先放入一个空结点作为头节点
创建头节点的操作在任何构造函数中都要先进行,所以我们将其封装为一个init_empty_list函数。这个初始化空list的函数需要new一个结点,然后使节点中的_prev_next都指向它自身,最后将_size赋值为0:

	void init_empty_list(){			_pHead = new Node();_pHead->_prev = _pHead;_pHead->_next = _pHead;_size = 0;}list(){init_empty_list();}

n个指定元素构造:
使用n个指定元素构造有两个参数,第一个就是int,第二个是const T&,缺省值为T()
函数中,首先调用init_empty_list构建一个头结点;
再循环,使用push_bake尾插n个指定元素value即可(push_back后面会实现):

	list(int n, const T& value = T()){init_empty_list();while (n--){push_back(value);}}

迭代器区间构造
是用迭代器区间构造函数是一个函数模板,可以使用任何容器的迭代器区间来构造list
函数中,首先调用init_empty_list构建一个头结点;
然后再循环,使用push_bake尾插first迭代器的解引用出的元素,当firstlast相等时出循环:

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

拷贝构造:
拷贝构造函数的参数是一个const list<T>&
实现时,首先调用init_empty_list构建一个头结点;
然后范围for,使用push_bake将l中的元素逐一尾插到list中:

	list(const list<T>& l){init_empty_list();for (auto el : l){push_back(el);}}

析构函数

析构函数需要实现释放list中的资源。
首先复用clear,清空list中的元素。clear中会实现释放结点中的资源,后面部分会实现;
delete _pHead;,释放头节点中的资源,它会调用结点结构体的析构函数

	~list(){clear();delete _pHead;}

赋值重载

对于赋值运算符重载,我们直接使用新写法,即先使用参数l创建一个临时对象;
然后使用swap将临时对象与*this交换(后面会实现swap函数);
最后返回*this即可,创建的临时对象就会在函数栈帧销毁时自动释放

	list<T>& operator=(const list<T>& l)  //list& operator=(const list l) 对于赋值重载,这样也可{list<T> temp(l);swap(temp);return *this;}

迭代器

在前面已经实现了list的迭代器,它是结点指针的封装

这里暂时只实现beginend,关于反向迭代器的实现在后面会详细介绍。
begin返回首元素的地址,即头结点的下一个结点的地址
end返回尾元素下一个位置的地址,即头节点的地址,他们分别重载有const版本:

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

容量

在容器部分,由于list并没有容量的概念,所以我们只需要实现sizeempty即可;
在这里插入图片描述
我们在list的属性中,我们设置了_size,在插入元素时_size++,删除元素时_size--,所以这里只需要返回_size的值即可;
_size == 0时,list为空,empty返回true,否者返回false

	size_t size() const{return _size;}bool empty() const{if (_size == 0)return true;elsereturn false;}

元素访问

由于list在任意位置访问元素的成本较高,就没有提供operator[]的接口,所以我们只需要实现frontback即可。分别返回首尾的元素,有普通对象与const对象两个重载版本:
在这里插入图片描述
在实现时,我们可以借助list的属性_pHead,即头结点的指针来访问首尾元素
我们模拟实现list的底层是带头双向循环链表,所以list中的第一个元素就是_pHead->_next指向的结点中的元素;list中的_pHead->_prev指向的结点中的元素

front只需要返回 _pHead->_next->_date 即可;
back返回_pHead->_prev->_date即可,返回值类型为T&,const版本就返回const T&即可:

	T& front(){return _pHead->_next->_date;}const T& front()const{return _pHead->_next->_date;}T& back(){return _pHead->_prev->_date;}const T& back()const{return _pHead->_prev->_date;}

数据修改

在这里插入图片描述

insert

list 的结构使在任意位置插入数据的效率是较高的,只需要创建结点,再链接到pos位置前即可

在实现insert时,首先new一个结点,类型为Node,并用val初始化(这个Node类型是前面重命名后的类型);
这时我们需要记录pos位置的结点指针为curpos位置前面的结点指针为prev,以方便后续链接;
然后将pos结点的前一个结点与新结点链接,即newnodeprev链接;
再将pos结点与新结点链接,即newnodecur链接;

最后,更新_size,并返回新结点的迭代器:

	// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._pNode;Node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}

erase

erase实现时,只需要释放pos位置的结点,并链接剩余的结点即可:

首先assert判断list是否为空;
这时我们需要记录pos位置的结点指针为curpos位置前面的结点指针为prevpos的后一个结点指针为next,以方便后续链接;
然后直接链接pos位置的前一个结点与后一个结点,即链接prevnext即可;

最后,释放cur指向的结点,更新_size,并返回next

	// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){assert(!empty());Node* cur = pos._pNode;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}

push_back 与 push_front

对于头插与尾插的实现,复用insert即可:

push_front,即在首结点的前面一个位置插入一个元素,即begin()迭代器位置插入
push_back,即在尾结点的后一个位置插入一个元素,即end()位置插入

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

pop_back 与 pop_front

对于头删尾删的实现,复用erase即可

pop_front,即删除头节点,即 erase删除begin()位置的结点 即可;
pop_back,删除尾结点,即 erase删除end()前面一个结点即可,但是由于list迭代器不支持-操作,所以这里传参为--end()

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

clear

clear用于清理list中的所有元素,可以直接复用erase来实现清理

我们可以通过遍历迭代器的方式逐一释放结点:
it的初始值为begin(),循环逐一erase删除,当it等于end()的时候终止循环,就可以实现删除所有结点并保留头节点;
另外,由于erase删除某节点后,会返回删除节点的下一个位置,所以只要把返回值载赋值给it就实现了迭代器的向后移动

	void clear(){iterator it = begin();while (it != end()){it = erase(it);//erase返回删除的结点的下一个位置的迭代器}}

swap

实现list的交换函数时,只需要使用库swap交换list的属性即可,即交换_pHead_size

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

源码概览

(关于反向迭代器的实现在后面会详细介绍,现在可以暂时忽略)

#include<iostream>
#include<cassert>
#include"my_reverse_iterator.h"namespace qqq
{template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _date;list_node(const T& date = T()) //匿名对象: _prev(nullptr), _next(nullptr), _date(date){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _pNode;__list_iterator(Node* pNode = nullptr):_pNode(pNode){}__list_iterator(const self& it){_pNode = it._pNode;}Ref operator*(){return _pNode->_date;}Ptr operator->(){return &(_pNode->_date);}self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}bool operator==(const self& it){return _pNode == it._pNode;}bool operator!=(const self& it){return _pNode != it._pNode;}};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 ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;public:/ constructor and destructor void init_empty_list(){			_pHead = new Node();_pHead->_prev = _pHead;_pHead->_next = _pHead;_size = 0;}list(){init_empty_list();}list(int n, const T& value = T()){init_empty_list();while (n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){init_empty_list();while (first != last){push_back(*first);++first;}}list(const list<T>& l){init_empty_list();for (auto el : l){push_back(el);}}list<T>& operator=(const list<T>& l)  //list& operator=(const list l) 对于赋值重载,这样也可{list<T> temp(l);swap(temp);return *this;}~list(){clear();delete _pHead;}// List Iterator///iterator begin(){return iterator(_pHead->_next);}iterator end(){return iterator(_pHead);}const_iterator begin() const{return const_iterator(_pHead->_next);}const_iterator end() const{return const_iterator(_pHead);}reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}/List Capacity//size_t size() const{return _size;}bool empty() const{if (_size == 0)return true;elsereturn false;}///List Access///T& front(){return _pHead->_next->_date;}const T& front()const{return _pHead->_next->_date;}T& back(){return _pHead->_prev->_date;}const T& back()const{return _pHead->_prev->_date;}List Modify//void push_back(const T& val){insert(end(), val);}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* newnode = new Node(val);Node* cur = pos._pNode;Node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){assert(!empty());Node* cur = pos._pNode;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}void clear(){iterator it = begin();while (it != end()){it = erase(it);//erase返回删除的结点的下一个位置的迭代器}}void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}private:Node* _pHead;size_t _size;};
};

总结

到此,关于list的模拟实现就到此结束了
模拟实现容器并不是为了造一个更好的轮子,而是为了更好的理解与使用容器

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka3.0.0版本——消费者(消费者组详细消费流程图解及消费者重要参数)

目录 一、消费者组详细消费流程图解二、消费者的重要参数 一、消费者组详细消费流程图解 创建一个消费者网络连接客户端&#xff0c;主要用于与kafka集群进行交互&#xff0c;如下图所示&#xff1a; 调用sendFetches发送消费请求&#xff0c;如下图所示&#xff1a; (1)、Fet…

【halcon】halcon字符识别——OCR

前言 OCR&#xff08;Optical Character Recongnition&#xff09;光学字符识别。 halcon 的OCR&#xff0c;提供了几种方式&#xff0c;我们应该如何选择&#xff1f; 自动文本阅读器&#xff08;find_text&#xff09;手动文本阅读器&#xff08;find_text&#xff09;自己…

Android USB电源管理

The USB peripheral detects the lack of 3 consecutive SOF packets as a suspend request from the USB host. 1 驱动shutdown顺序 系统关机或重启的过程中&#xff0c;会调用设备驱动的shutdown函数来完成设备的关闭操作&#xff0c;有需要的设备可以在驱动中定义该函数。其…

Python一行命令搭建HTTP服务器并外网访问 - 内网穿透

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 Python作为热度比较高的编程语言&#xff0c;其语法简单且语句清晰&#xff0c;而且python有…

射频功率放大器的指标有哪些内容

射频功率放大器是射频系统中至关重要的组件&#xff0c;用于放大射频信号的功率。本文将详细介绍射频功率放大器的指标&#xff0c;包括功率增益、带宽、线性度、效率、稳定性等关键指标。 一、功率增益 功率增益是射频功率放大器最基本的指标之一&#xff0c;表示放大器将输入…

taro h5 点击页面任意地方关闭弹窗组件 --- findDOMNode 判断点击节点是否属于某个组件

场景&#xff1a;如图&#xff0c;弹窗在大组件中&#xff0c;点击小组件显示弹窗&#xff0c;要求点击除弹窗外的任何元素都能关闭弹窗并且能执行元素原有的逻辑 方法一 最简单的是弹窗背后有一个覆盖整个页面的透明的cover, 点击直接关闭&#xff0c;但是这样虽然点击页面…

Spring最佳实践: 构建高效可维护的Java应用程序

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【docker】私有仓库搭建

Docker 私有仓库搭建 在 Docker 中&#xff0c;当我们执行 docker pull xxx 的时候 &#xff0c;它实际上是从 registry.hub.docker.com 这个地址去查找&#xff0c;这就是Docker公司为我们提供的公共仓库。在工作中&#xff0c;我们不可能把企业项目push到公有仓库进行管理。…

试图替代 Python 的下一代AI编程语言:Mojo

文章目录 为什么叫 Mojo &#xff1f;Python 家族的一员&#xff0c;MojoPython 的好处&#xff1a;Python 兼容性Python 的问题移动和服务器部署&#xff1a;Python 子集和其他类似 Python 的语言&#xff1a; Mojo 是一种创新的编程语言&#xff0c;结合了 Python 的可用性和…

浅谈redis未授权漏洞

redis未授权漏洞 利用条件 版本比较高的redis需要修改redis的配置文件&#xff0c;将bind前面#注释符去掉&#xff0c;将protected-mode 后面改为no 写入webshell config get dir #查看redis数据库路径 config set dir web路径# #修改靶机Redis数据库路径 config set dbfilen…

基于SSM的人事管理信息系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【笔试强训选择题】Day39.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff…

数据集笔记 geolife (操作篇)

数据集介绍可看&#xff1a;数据集笔记:GeoLife GPS 数据 &#xff08;user guide&#xff09;_UQI-LIUWJ的博客-CSDN博客 1 读取数据 import os os.chdir(D:/Geolife Trajectories 1.3/Geolife Trajectories 1.3/Data/000/Trajectory)import pandas as pd data pd.read_csv(…

基于AHP模型指标权重分析python整理

一 背景介绍 日常会有很多定量分析的场景&#xff0c;然而也会有一些定性分析的场景针对定性分析的场景&#xff0c;预测者只能通过主观判断分析能力来推断事物的性质和发展趋势然而针对个人的直觉和虽然能够有一定的协助判断效果&#xff0c;但是很难量化到指标做后期的复用 …

windows服务器自带IIS搭建网站并发布公网访问【内网穿透】

文章目录 1.前言2.Windows网页设置2.1 Windows IIS功能设置2.2 IIS网页访问测试 3. Cpolar内网穿透3.1 下载安装Cpolar3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5.结语 1.前言 在网上各种教程和介绍中&#xff0c;搭建网页都会借助各种软件的帮助&#xff0c;比如…

自动计算比例 计算属性 computed @input=“rate“

<el-col :span"12"><el-form-item label"当年累计实收租金:" prop"cumulativeRent"><el-inputv-model"createForm.cumulativeRent"input"rate"clearable:disabled"value 2"><template slot…

Gin项目实战

Gin项目实战 Gin博客项目-项目架构Gin博客项目-集成gormGin博客项目-集成Bootstrap创建用户表单Gin 博客项目-实现控制器和路由Gin 博客项目-设计静态页面Gin 博客项目-用户注册Gin 博客项目-用户登录Gin 博客项目-集成markdown编辑器Gin 博客项目-创建博客模型和DAOGin 博客项…

深入解析OLED透明屏的工作原理与优势,智能家居的未来之选

OLED透明屏作为一项突破性的显示技术&#xff0c;不仅具备出色的视觉效果&#xff0c;还带来了全新的功能和应用。 在这篇文章中&#xff0c;尼伽将深入探讨OLED透明屏的功能特点&#xff0c;介绍其在各个领域的广泛应用&#xff0c;并提供实用的案例和数据&#xff0c;希望看…

修复 ChatGPT 发生错误的问题

目录 ChatGPT 发生错误&#xff1f;请参阅如何修复连接错误&#xff01; 修复 ChatGPT 发生错误的问题 基本故障排除技巧 检查 ChatGPT 的服务器状态 检查 API 限制 检查输入格式 清除浏览数据 香港DSE是什么&#xff1f; 台湾指考是什么&#xff1f; 王湘浩 生平 …

upload-labs/Pass-07 未知后缀名解析漏洞复现

upload-labs/Pass-07 漏洞复现 页面&#xff1a; 我们看到有一个图片上传功能。 我们上传一个png文件发现能够成功上传&#xff0c;那其他文件呢&#xff0c;如php文件。 我们看一下是否能上传一个php文件&#xff1a; php文件内容&#xff1a; <?phpeval($_REQUEST[]…