C++ list链表的使用和简单模拟实现

目录

前言

1. list的简介

2.list讲解和模拟实现

2.1 默认构造函数和push_back函数

2.2 迭代器实现

2.2.1 非const正向迭代器

2.2.2 const正向迭代器

2.2.3 反向迭代器

2.3 插入删除函数

2.3.1 insert和erase

2.3.2 push_back pop_back push_front pop_front

2.4 构造函数,赋值拷贝函数,析构函数

总结


前言

这篇文章讲述常用容器list的使用和一些重要部分的简单模拟实现,仅仅只是了解一些实现方法。内容丰富,干货多多。


1. list的简介

  1. list是序列容器,允许在序列内的任何位置进行常量时间的插入和删除操作,以及两个方向的迭代。
  2. 列表容器被实现为双链表;双链表中每个元素存储在互不相关的几点钟,在节点中通过指针指向其前一个元素和后一个元素
  3. 它们与forward_list非常相似:主要区别在于forward_list对象是单链表,因此它们只能向前迭代,让其更简单高效。
  4. 与其他基本标准序列容器(array、vector和deque)相比,列表在容器内的任何位置插入、提取和移动元素方面通常表现更好。
  5. 与其他序列容器相比,列表和forward_lists的主要缺点是它们无法通过位置直接访问元素;例如,要访问列表中的第八个元素,必须从已知位置(如开始或结束)迭代到该位置,这需要在两者之间的距离上花费线性时间。它们还消耗一些额外的内存来保存与每个元素相关联的链接信息(对于包含小元素的大型列表来说,这可能是一个重要因素)。

  •  下面是展示list类的内部成员变量结构,并且之后要简单模拟实现,所以加上一个命名空间,防止与库中list起冲突。
  • list即是双向链表结构,我们使用模版定义一个ListNode类,内部成员变量有两个指针,指向前一个元素和后一个元素,还有存储T类型的元素。在list类中,我们可以使用typedef简化LIstNode<T>成Node。Node类型的指针变量_head,代表第一个哨兵位结点,不存放元素。
  • 其中ListNode使用struct而不用class,因为struct默认权限是公有的,且ListNode类里面的所有内容list都会使用到,所以直接用struct定义这个类。
namespace User
{template<class T>struct ListNode{ListNode*<T> _next;ListNode*<T> _prev;T _data;//...};template<class T>class list{typedef ListNode<T> Node; //...private:Node* _head;};    
}

2.list讲解和模拟实现

2.1 默认构造函数和push_back函数

默认构造函数构造一个只含有哨兵位结点的链表,并且哨兵位结点的的两个指针指向自身。我们暂时使用库里的list,先创建一个int类型的list容器,调用的是默认构造函数,之后尾插几个整数。遍历整个链表一般使用迭代器,还可以使用范围for。因为物理结构是不连续的,不支持使用下标方括号遍历链表。

list();//默认构造函数void test_list1()
{std::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int>::iterator it1 = lt1.begin();while (it1 != lt1.end()){cout << *it1 << " ";++it1;}cout << endl;for (const auto& e : lt1){cout << e << " ";}cout << endl;
}

运行结果如下:

  • 实现一个list的默认构造函数,使用new动态开辟一个Node类型元素的内存空间,传入一个T类型的值调用LIstNode类的构造函数。下面写的是T()表示如果是自定义类型,就调用该自定义类型的构造函数,如果是内置类型,会初始化一个该类型的值,整型一般初始化为0。
  • 既然list构造函数中调用了LIstNode类的构造函数,LIstNode的构造函数也需要实现。构造函数的参数是const修饰的T类型引用的data变量,顺便给上一个缺省值T(),跟上面的用法相同。
  • push_back函数先动态开辟一个新结点,传入x初始化。然后再改变结点指向的位置即可。
template<class T>
struct ListNode 
{ListNode*<T> _next;ListNode*<T> _prev;T _data;ListNode(const T& data = T()):_next(nullptr),_prev(nullptr),_data(data){}    
};template<class T>
class list
{typedef ListNode<T> Node; 
public:list(){_head = new Node(T());_head->_next = _head;_head->_prev = _head;}void push_back(const T& x){Node* newnode = new Node(x);Node* tail = _head->_prev;// head tail newnodetail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}private:Node* _head;
};

2.2 迭代器实现

2.2.1 非const正向迭代器

list因本身物理空间不连续,需要通过结点存储前后元素地址,进行访问。那么list的迭代器需要封装成一个类,重载前置++,后置--,!=,==,*等符号。

template<class T>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T> Self;Node* _node;//...
};
  • typedef可以帮我们简化类名称,方便之后使用,重命名ListNode<T>类为Node,还有将ListIterator<T>类重命名为Self。

    ListIterator(Node* node):_node(node){}
  • 构造函数实现一个有参的构造函数,使用初始化列表初始化_node。

template<class T>
class list
{typedef ListNode<T> Node;public:typedef ListIterator<T> iterator;iterator begin(){//iterator it(_head->_next);//return it//下面是隐士类型转换调用return iterator(_head->_next);}iterator end(){return iterator(_head);}};
  • 在list中提供begin和end函数,对迭代器进行初始化,或者遍历时做判断。
  • begin函数中,可以先创建一个iterator对象,再返回该对象。也可以直接使用匿名对象,内部传结点参数,进行隐式类型转换。
  • end函数提供最后一个元素的下一个位置,所以用哨兵位结点进行构造。

	// ++it 前置Self& operator++(){_node = _node->_next;return *this;}// it++ 后置Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}// --it 前置Self& operator--(){_node = _node->_prev;return *this;}
  • 前置++,表示访问后一个元素,让_node赋值为_next指针即可。返回类型使用Self的引用,不用再次拷贝,更高效,记得要返回*this。
  • 后置++,返回当前的位置,但是迭代器自身指向下一个元素。创建一个Self类型的tmp变量对*this进行拷贝构造,然后自身赋值为下一个结点指针,返回tmp。
  • 前置--,表示访问前一个元素。做法跟前置++类似,让_node赋值为它的_prev指针即可。也是使用Self引用类型返回。

    T& operator*(){return _node->_data;}
  • *符号,表示要解引用到Node类型存储的元素。返回类型使用T类型的引用,直接返回data的别名,减少拷贝。

	bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}
  • !=和==可直接返回两个迭代器结点地址比较结果。

  • ->符号比较特别,表示当前结点元素数据的地址,可以像结构体->一样使用。
	T* operator->(){return &_node->_data;}

虽然operator->看起来比较奇特,但是也有它的应用场景。

  • 看下面的代码,我们定义一个Pos,Pos是表示坐标位置的类。我们创建一个数据类型为Pos的list容器,尾插三个Pos类对象数据。
  • 使用迭代遍历。如果在while循环使用*it,进行打印,程序会报错,这是因为Pos类没有匹配的cout流插入函数,在全局作用域下也没有重载cout函数。
  • 所以可以用(*it)._row这样的方式打印两个整型坐标,*符号返回的是结点的数据别名,在这里是Pos类变量的别名,我们可以用.符号进行打印,但是看起来太别扭了。
  • 重载的->就派上用场了,我们使用->就像结构体指针访问内部对象。不过正常来说应该是两个->符号,代码中有展开示例。为了可读性,只用写一个->符号就可以使用。
struct Pos
{int _row;int _col;Pos(int row = 0, int col = 0):_row(row), _col(col){}
};void test_list2()
{list<Pos> lt1;lt1.push_back(Pos(100, 100));lt1.push_back(Pos(200, 100));lt1.push_back(Pos(300, 100));//临时对象可以调用非静态成员函数,但是不能引用定义list<Pos>::iterator it = lt1.begin();while (it != lt1.end()){//cout<< *it <<endl;//error,这样写无法运行成功。//可以这样写但是太别扭了//cout << (*it)._row << ":" << (*it)._col << " ";//为了可读性,省略了一个->cout << it->_row << ":" << it->_col << " " << endl;//cout << it->->_row << ":" << it->->_col << " "; 展开就成了下面的样子cout << it.operator->()->_row << ":" << it.operator->()->_col << " "<< endl;++it;}cout << endl;
}

运行结果如下:

2.2.2 const正向迭代器

  • 非const对象的迭代器迭代器指向的元素可以修改,但是有时候容器内部元素加上const修饰,不可以修改。因此,还要提供const类型的迭代器。我们可以使用上面的代码,只要将operator*和operator->函数的返回类型加上const修饰就行。
template<class T>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T> Self;Node* _node;ListIterator(Node* node = Node())//默认构造:_node(node){}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;}const T& operator*(){return _node->_data;}const T* operator->(){return &_node->_data;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}
};template<class T>
class list
{typedef ListNode<T> Node;public:typedef ListIterator<T> iterator;typedef ListIterator<T> const_iterator;const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}//...
};

  • 但是这两类迭代器就只有operator*和operator->函数的返回类型不同而已,返回类型上有区别,其实我们可以借助模版解决。
  • 只需要在定义两个模版类型参数Ref和Ptr,分别表示引用和指针,在list中分别传不同的参数表示iterator和const_iterator。
	template<class T, class Ref, class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* node = Node())//默认构造:_node(node){}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;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};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;const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}//...};

2.2.3 反向迭代器

反向迭代器的模拟实现和正向迭代器类似,需要注意++和--跟正向迭代器的的方向完全反过来,还要注意迭代器begin和end函数给的结点位置。

	template<class T, class Ref, class Ptr>struct Reverse_ListIterator{typedef ListNode<T> Node;typedef Reverse_ListIterator<T, Ref, Ptr> Self;Node* _node;Reverse_ListIterator(Node* node):_node(node){}Self& operator++()//前置++,赋值为前面的结点的指针{_node = _node->_prev;return *this;}Self operator++(int)//后置++{Self tmp(_node);_node = _node->_prev;return tmp;}Self& operator--(){_node = _node->_next;return *this;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};template<class T>
class list
{typedef ListNode<T> Node;public:typedef Reverse_ListIterator<T, T&, T*> reverse_iterator;typedef Reverse_ListIterator<T, const T&, const T*> reverse_const_iterator;reverse_const_iterator rbegin() const{return reverse_const_iterator(_head->_prev);} reverse_const_iterator rend() const{return reverse_const_iterator(_head);}reverse_iterator rbegin(){return reverse_iterator(_head->_prev);}reverse_iterator rend(){return reverse_iterator(_head);}//...
};

2.3 插入删除函数

2.3.1 insert和erase

  • insert函数是在指定结点位置前插入一个新的结点。需要改变各个结点的指向问题,可以创建几个变量表示结点。
  • 因为该函数传入的参数类型是迭代器,所以还要考虑迭代器失效的问题。解决方法是返回指向新结点位置的迭代器类型的变量。下面采用的是匿名对象。
iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);
}

  • erase函数是删除传入的迭代器变量指向的结点。也可以创建几个变量表示结点,改变各个结点的指向问题。
  • 返回类型也是迭代器,返回的是被删除结点的下一个结点迭代器类型变量。
//erase后Pos失效,因为被释放了
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;return iterator(next);
}

2.3.2 push_back pop_back push_front pop_front

完成了对insert和erase函数的实现,头部或者尾部插入和删除的函数都可以复用这两个函数。

  • push_back函数是在尾部插入一个结点,insert是在指向结点前插入新结点,因为end函数是哨兵位结点,在它前面插入新结点就是尾插,所以传end函数作为迭代器即可。
  • pop_back函数是删除最后一个结点,最后一个结点在哨兵位结点之前,即end函数所代表的迭代器,所以需要--。
  • push_front和pop_front同理。
void push_back(const T& x)
{insert(end(), x);
}void pop_back()
{erase(--end());
}void push_front(const T& x)
{insert(begin(), x);
}void pop_front()
{erase(begin());
}

写一个测试函数

void Func(const list<int>& lt)
{list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;
}void test_list3()
{list<int> lt1;//按需实例化lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);lt1.push_back(5);Func(lt1);lt1.push_front(10);lt1.push_front(20);lt1.push_front(30);Func(lt1);lt1.pop_back();lt1.pop_back();lt1.pop_back();Func(lt1);lt1.pop_front();lt1.pop_front();lt1.pop_front();Func(lt1);
}

运行结果如下:

2.4 构造函数,赋值拷贝函数,析构函数

构造函数这块,有四种类型,默认构造,initializer初始化器构造,迭代器区间构造和拷贝构造。

  • 首先,封装一个创建哨兵位头结点的函数,因为后面的构造函数都需要用到。再写一个遍历打印整个链表的函数。
  • 默认构造函数直接调用empty_init函数,初始化一个头结点。
void Printlist(const list<int> lt)
{for (const auto& e: lt1){cout << e << " ";    }cout << endl;
}void empty_init()
{_head = new Node(T());_head->_next = _head;_head->_prev = _head;
}list()
{empty_init();
}

  • initializer_list函数是针对花括号内部有相同类型的构造函数
list(initializer_list<T> il)
{empty_init();for (const auto& e : il){push_back(e);}}void test1()
{list<int> lt1 = { 1,2,3,4,5,6,7,8 };Printlist(lt1);
}

  • 需要注意判断条件只能是!=,不能是<之类的符号。
template <class InputIterator>
list(InputIterator first, InputIterator last)
{empty_init();while (first != last){push_back(*first);++first;}
}void test2()
{list<int> lt1 = { 1,2,3,4,5,6,7,8 };Printlist(lt2);list<int> lt2(lt1.begin(), lt2.end());Printlist(lt2);
}

  • 拷贝构造函数,可以使用范围for尾插元素实现。
//lt1(lt2)
list(const list<T>& lt)
{empty_init();for (const auto& e : lt){push_back(e);}
}void test3()
{list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);Printlist(lt2);list<int> lt2(lt1);Printlist(lt2);
}

  • 赋值拷贝函数可以先删除原链表的结点,然后再一个个尾插结点。
  • 但是更好的方法就是用正常模版类型参数list接受被拷贝对象,此时的lt就是待拷贝对象的一份临时拷贝,然后使用交换函数交换头结点,因为lt是一份临时拷贝,函数结束后会自动调用析构函数销毁原链表的内容。
//lt1 = lt3
list<T>& operator=(list<T> lt)
{std::swap(_head, lt._head);return *this;
}void test3()
{list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);Printlist(lt2);list<int> lt2;lt2 = lt1;Printlist(lt2);
}

  • 析构函数需要先一个个释放结点,再释放头结点。
void clear()
{auto it = begin();while (it != end()){it = erase(it);++it;}
}~list()
{clear();delete _head;_head = nullptr;
}


总结

看完整篇文章,我相信你对list的使用和一些底层实现原理有进一步的了解,你也可以尝试自己手撕一个简单的list容器实现。树欲静而风不止,既然我们无法改变大势,但是我们可以改变自己!

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

ee192b61bd234c87be9d198fb540140e.png

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

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

相关文章

[word] word如何清除超链接 #媒体#笔记#知识分享

word如何清除超链接 办公中&#xff0c;少不了使用word&#xff0c;这个是大家必备的软件&#xff0c;今天给大家分享下word如何清除超链接的操作办法&#xff0c;一起来学习下吧&#xff01; 1、清除所有超链接 按下组合键CtrlshiftF9&#xff0c;就可以将网上复制带有超链…

《软件定义安全》之三:用软件定义的理念做安全

第3章 用软件定义的理念做安全 1.不进则退&#xff0c;传统安全回到“石器时代” 1.1 企业业务和IT基础设施的变化 随着企业办公环境变得便利&#xff0c;以及对降低成本的天然需求&#xff0c;企业始终追求IT集成设施的性价比、灵活性、稳定性和开放性。而云计算、移动办公…

pytorch 加权CE_loss实现(语义分割中的类不平衡使用)

加权CE_loss和BCE_loss稍有不同 1.标签为long类型&#xff0c;BCE标签为float类型 2.当reduction为mean时计算每个像素点的损失的平均&#xff0c;BCE除以像素数得到平均值&#xff0c;CE除以像素对应的权重之和得到平均值。 参数配置torch.nn.CrossEntropyLoss(weightNone,…

解决Windows窗口聚焦问题

情景引入&#xff1a; 在使用副屏显示器写代码&#xff0c;主屏显示器看教程的时候&#xff0c;突然有个知识点卡住了&#xff0c;这个时候你想要按下空格让视频暂停&#xff0c;但是按下后你会发现&#xff1a;视频没有暂停&#xff0c;倒是代码界面多了个空格。。。这就不好玩…

用HTML实现拓扑面,动态4D圆环面,可手动调节,富有创新性的案例。(有源代码)

文章目录 前言一、示例二、目录结构三、index.html&#xff08;主页面&#xff09;四、main.js五、Tour4D.js六、swissgl.js七、dat.gui.min.js八、style.css 前言 如果你觉得对代码进行复制粘贴很麻烦的话&#xff0c;你可以直接将资源下载到本地。无需部署&#xff0c;直接可…

如何对stm32查看IO功能。

有些同学对于别人的开发板的资源&#xff0c;或者IO口&#xff0c;或者串口等资源不知道怎么分配。 方法1、看硬石、野火、正点原子的开发板&#xff0c;看下他们的例子&#xff0c;那个资源用什么。自己多看几个原理图&#xff0c;多看几个视频&#xff0c;做一下笔记。以后依…

【面试干货】MySQL 三种锁的级别(表级锁、行级锁和页面锁)

【面试干货】MySQL 三种锁的级别&#xff08;表级锁、行级锁和页面锁&#xff09; 1、表级锁2、行级锁3、页面锁4、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在 MySQL 数据库中&#xff0c;锁是控制并发访问的重要机制&#xff0…

GQA,MLA之外的另一种KV Cache压缩方式:动态内存压缩(DMC)

0x0. 前言 在openreview上看到最近NV的一个KV Cache压缩工作&#xff1a;https://openreview.net/pdf?idtDRYrAkOB7 &#xff0c;感觉思路还是有一些意思的&#xff0c;所以这里就分享一下。 简单来说就是paper提出通过一种特殊的方式continue train一下原始的大模型&#x…

DS:树与二叉树的相关概念

欢迎来到Harper.Lee的学习世界&#xff01;博主主页传送门&#xff1a;Harper.Lee的博客主页想要一起进步的uu可以来后台找我哦&#xff01; 一、树的概念及其结构 1.1 树的概念亲缘关系 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限节点…

汇编:数组-寻址取数据

比例因子寻址&#xff1a; 比例因子寻址&#xff08;也称为比例缩放索引寻址或基址加变址加比例因子寻址&#xff09;是一种复杂的内存寻址方式&#xff0c;常用于数组和指针操作。它允许通过一个基址寄存器、一个变址寄存器和一个比例因子来计算内存地址。 语法 比例因子寻…

经典文献阅读之--Online Monocular Lane Mapping(使用Catmull-Rom样条曲线完成在线单目车道建图)

0. 简介 对于单目摄像头完成SLAM建图这类操作&#xff0c;对于自动驾驶行业非常重要&#xff0c;《Online Monocular Lane Mapping Using Catmull-Rom Spline》介绍了一种仅依靠单个摄像头和里程计生成基于样条的在线单目车道建图方法。我们提出的技术将车道关联过程建模为一个…

Java 习题集

&#x1f496; 单选题 &#x1f496; 填空题 &#x1f496; 判断题 &#x1f496; 程序阅读题 1. 读代码写结果 class A {int m 5;void zengA(int x){m m x;}int jianA(int y){return m - y;} }class B extends A {int m 3;int jianA(int z){return super.jianA(z) m;} …

Java Web学习笔记20——Ajax-Axios

Axios&#xff1a; 介绍&#xff1a;Axios对原生的Ajax进行封装&#xff0c;简化书写&#xff0c;快速开发。 官网&#xff1a;https://www.axios-http.cn Axios 入门&#xff1a; {}是Js的对象。 get的请求参数是在URL后面&#xff1f;和相关参数值。 post的请求参数是在请…

Soildworks学习笔记(二)

放样凸台基体&#xff1a; 自动生成连接两个物体两个面的基体&#xff1a; 2.旋转切除&#xff1a; 3.剪切实体&#xff1a; 4.转换实体引用&#xff1a; 将实体的轮廓线转换至当前草图使其成为当前草图的图元,主要用于在同一平面或另一个坐标中制作草图实体或其尺寸的副本。 …

【深度学习】Transformer分类器,CICIDS2017,入侵检测

文章目录 1 前言2 什么是入侵检测系统&#xff1f;3 为什么选择Transformer&#xff1f;4 数据预处理5 模型架构5.1. 输入嵌入层&#xff08;Input Embedding Layer&#xff09;5.2. 位置编码层&#xff08;Positional Encoding Layer&#xff09;5.3. Transformer编码器层&…

MySQL—多表查询—子查询(介绍)

一、引言 上一篇博客学习完联合查询。 这篇开始&#xff0c;就来到多表查询的最后一种形式语法块——子查询。 &#xff08;1&#xff09;概念 SQL 语句中嵌套 SELECT 语句&#xff0c;那么内部的 select 称为嵌套查询&#xff0c;又称子查询。 表现形式 注意&#xff1a; …

复数的概念

1. 虚数单位&#xff1a;i 引入一个新数 ‘i’&#xff0c;i又叫做虚数单位&#xff0c;并规定&#xff1a; 它的平方等于 -1&#xff0c;即 i -1。实数可以与它进行四则运算&#xff0c;并且原有的加&#xff0c;乘运算律依然成立。 2.定义 复数的定义&#xff1a;形如 a…

CTFHUB-SQL注入-字符型注入

目录 查询数据库名 查询数据库中的表名 查询表中数据 总结 此题目和上一题相似&#xff0c;一个是整数型注入&#xff0c;一个是字符型注入。字符型注入就是注入字符串参数&#xff0c;判断回显是否存在注入漏洞。因为上一题使用手工注入查看题目 flag &#xff0c;这里就不…

GIS数据快捷共享发布工具及操作视频

有网友反映还是不会操作GIS数据快捷共享发布工具&#xff08;建立自己的地图网站&#xff09;&#xff0c;要我录个视频。 好&#xff0c;那就录一个: GIS数据快捷共享发布工具及操作视频 虽然默认例子是二维的&#xff0c;但这个服务器可以为二维、三维系统发布时间服务。都是…

NASA数据集——SARAL 近实时增值业务地球物理数据记录海面高度异常

SARAL Near-Real-Time Value-added Operational Geophysical Data Record Sea Surface Height Anomaly SARAL 近实时增值业务地球物理数据记录海面高度异常 简介 2020 年 3 月 18 日至今 ALTIKA_SARAL_L2_OST_XOGDR 这些数据是近实时&#xff08;NRT&#xff09;&#xff…