【C++】—— list 模拟实现
【C++】—— list 模拟实现
- 1 list 基础结构
- 2 默认构造
- 3 迭代器
- 3.1 整体框架
- 3.2 成员函数
- 3.3 begin() 与 end() 的实现
- 3.4 operator-> 的实现
- 3.5 const 迭代器
- 3.5.1 const 迭代器为什么命名 const_iterator
- 3.5.2 const 迭代器的实现
- 3.5.3 合并两个迭代器
- 4 源码
1 list 基础结构
l i s t list list 的底层就是我们之前学过的双向链表
,它由一个哨兵位头结点 _pHead
和记录链表长度的 _size
组成。
而链表中的每个节点都是由两个自身类型指针
和一个存储数据的变量组成
的。因此,我们不仅要定义链表的类,还要定义节点的类
下面是 l i s t list list 的成员变量和整体框架
namespace my_list
{template<class T>struct ListNode{ListNode* _prev;ListNode* _next;T _val;//默认构造ListNode(const T& val = T()):_prev(nullptr),_next(nullptr),_val(val){}};template<class T>class list{typedef ListNode<T> Node;//给节点重命名//成员函数···private:Node* _pHead;//哨兵位头结点指针size_t _size;}
}
因为我们要频繁访问节点,因此我们直接用 s t r u c t struct struct 定义节点,其成员变量默认全公有
。
2 默认构造
我们先写一个简单的无参默认构造出来。
默认构造可以直接这样写吗?
list():_pHead(nullptr),_size(0)
{}
不可以的,因为双向链表有个哨兵位
。即使链表中没有任何数据,头节点指针也是指向哨兵位,而不是空,所以我们应创建哨兵位,并将其初始化
而哨兵位的前驱指针 _ p r e v prev prev 和后继指针 _ n e x t next next,因为整个链表只有它自己,它的前一个和后一个节点都是自己,因此 _ p r e v prev prev 和 _ n e x t next next 都是指向哨兵位自己
list()
{_pHead = new Node;_pHead->_next = _pHead;_pHead->_prev = _pHead;_size = 0;
}
其是不仅仅是无参的构造,所有的构造函数第一步都是初始化哨兵位
,因此我们不妨单独写一个函数出来
list()
{CreateHead();
}void CreateHead()
{_pHead = new Node;_pHead->_next = _pHead;_pHead->_prev = _pHead;_size = 0;
}
这里有个问题就是CreateHead()
是非 c o n s t const const 成员函数,那定义 c o n s t const const 成员是否还能来调用呢?答案自然是可以的,因为 c o n s t const const 变量在定义的时候是不具有 c o n s t const const 属性的,定义完成之后才有。比如说:
//如果在定义之前就具有const属性,那么n就无法赋值
//const变量只有在定义时可以被赋值
const int n = 10;
const list<int> l1;
3 迭代器
要模拟实现 l i s t list list ,迭代器的实现
是其中的重中之重。
前面我们模拟实现 s t r i n g string string 和 v e c t o r vector vector,他们的迭代器都是原生指针,他们的原生指针完美符合迭代器的所有要求。其本质是因为他们底层物理空间是连续的。
但 l i s t list list 不像 s t r i n g string string 和 v e c t o r vector vector 那样天生丽质,它的底层结构是一个一个节点,并不连续,无法满足迭代器的要求(比如 ++,我们希望的是迭代器跳到下一个节点,如果使用原生指针,因为不是连续的物理空间,当前节点 ++ 大概率是个野指针)。
但没关系, l i s t list list 可以通过封装,通过运算符重载,来满足迭代器的要求。
3.1 整体框架
迭代器其本身就是模拟指针的行为
,既然节点的原生指针无法满足迭代器的要求,我们对节点指针进行封装,通过运算符重载让其满足迭代器的需求
template<class T>
struct ListIterator
{typedef ListIterator<T> Self;//给自身类(迭代器)重命名,短一点方便typedef ListNode<T> Node;//节点重命名//成员变量:节点的指针Node* pNode;//成员函数//··· };
因为待会链表中要大量访问成员变量,我们直接用默认全公有的 s t r u c t struct struct
到现在,我们一共实现了三个类,为什么要实现三个类呢?我们先把每个类的作用过一遍:
class list
:链表这个类是链表的基本结构,指向哨兵位的头结点
,管理这整个链表struct ListNode
:节点这个类是因为链表中每个节点都是自定义类型,每个数据都是存在一个独立的结构体里面struct ListIterator
:迭代器这个类,遍历整个链表本来是用节点的指针,但是节点的指针是不符合我们的预期,我们希望有一个迭代器统一的方式进行遍历,因此我们用一个结构去封装节点的指针,封装以后通过重载运算符使节点的指针能达到迭代器那样的行为
3.2 成员函数
有了迭代器这个类,我们就可以运用运算符重载满足迭代器的行为啦
template<class T>
struct ListIterator
{typedef ListIterator<T> Self;typedef ListNode<T> Node;//成员变量Node* pNode;//解引用T& operator*(){return pNode->_val;}//前置++Self& operator++(){pNode = pNode->_next;return *this;}//后置++Self& operator++(int){Self tmp = *this;pNode = pNode->_next;return tmp;}//前置--Self& operator--(){pNode = pNode->_prev;return *this;}//后置--Self& operator--(int){Self tmp = *this;pNode = pNode->_prev;return tmp;}//不等于bool operator!=(const Self& x){return pNode != x.pNode;}//等于bool operator==(const Self& x){return pNode == x.pNode;}
}
3.3 begin() 与 end() 的实现
迭代器的基本行为实现了,我们也可以在list类
中实现begin()
、end()
等函数了
begin()
函数是返回第一个迭代器,我们要构造第一个位置的迭代器,那怎么构造呢?我们用第一个节点的指针,即_pHead->_next
,就能构造第一个迭代器,但现在ListIterator类
中还缺少一个构造函数:
//默认构造
ListIterator(Node* p = nullptr):pNode(p)
{}//拷贝构造
ListIterator(const ListIterator& x)
{pNode = x.pNode;
}
其实上述拷贝构造可以不用实现,编译器会自己生成一个拷贝构造,完成浅拷贝,指向链表的节点。
这里不要认为有指针指向资源就要自己实现深拷贝,而是看指针所指向的资源是不是属于自己的。像 s t r i n g string string、 v e c t o r vector vector 那些就需要自己实现深拷贝,但迭代器指向的是链表的资源,并不是迭代器自己的,并且迭代器本身的目标就是指向链表的节点,以此来访问遍历链表,因此浅拷贝
即可。
所以赋值重载和析构与不需要写
现在,万事具备只欠东风,我们还要在list类
中将ListIterator类
t y p e d e f typedef typedef 成 i t e r a t o r iterator iterator
template<class T>
class list
{typedef ListNode<T> Node;typedef ListIterator<T> iterator;//重命名为iterator//成员函数···private:Node* _pHead;size_t _size;
}
下面是begin()
的实现
iterator begin()
{iterator it(_pHead->_next);return it;
}
上述是有名对象的写法,我们可以用匿名对象的写法
iterator begin()
{ return iterator(_pHead->_next);
}
甚至我们可以用隐式类型转换
,直接传指针就好啦
迭代器本身就是节点的指针,指针节点指针本身不满足迭代器的那些需求,所以我们才用一个类把他封装一层,用重载运算符使其达到迭代器的要求。
iterator begin()
{return _pHead->_next;
}
end()
是最后一个数据的下一个位置,就是哨兵位的头结点
iterator end()
{return _pHead;
}
3.4 operator-> 的实现
既然迭代器模拟的是指针的行为,那它还要实现 operator->
什么情况下用到 ->
运算符呢?
现在我们定义一个类型 AA,链表中存储的数据是 AA 类型,我们想依次遍历链表,打印每个节点 AA 中的两个成员变量
struct AA
{int _a1 = 1;int _a2 = 2;
}void test1()
{list<AA> lta;lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator it = lta.begin();while (it != lta.end()){cout << (*it)._a1 << " " << (*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;}
}
上述(*it)._a1
与it->_a1
的写法是等价的,但现在还没重载->
运算符
下面是operator->
的实现方式
T* operator->()
{return &(pNode->_val);
}
operator->
的实现方式非常奇怪,返回的是T*
其实这里省略了一个 ->,因为太难看了,为了可读性省略了一个 ->
cout << it->_a1 << " " << it->_a2 << endl;//本质是这样的
cout << it->->_a1 << " " << it->->_a2 << endl;
第一个->是运算符重载出来的,第二个则是普通的->
本质是这样的:
cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << endl;cout << &(pNode->_val)->_a1 << " " << &(pNode->_val)->_a2 << endl;
3.5 const 迭代器
3.5.1 const 迭代器为什么命名 const_iterator
首先问大家一个问题: c o n s t const const 迭代器为什么是const_iterator
,而不是const iterator
呢
c o n s t const const 迭代器是自身不能修改
还是指向的内容不能修改
呢?
就和指针一样,指针的 c o n s t const const 有两个,一个在 * 之前,一个在 * 之后
T* const ptr1//指针本身不能修改
const T* ptr2//指向的内容不能修改
如果是 const iterator
,const 直接修饰一个变量,就是这个变量本身不能修改,即迭代器本身不能修改,而我们 c o n s t const const 迭代器是要指向的内容不能修改,所以const_iterator
更合适
3.5.2 const 迭代器的实现
那我们如何让迭代器指向的内容不能修改呢?
迭代器修改我们指向的内容是怎么修改的?通过operator*
和operator->
,那我们在其返回值上加上 c o n s t const const 就不能修改了
const T& operator*()
{return pNode->_val;
}const T* operator->()
{return &(pNode->_val);
}
那我们就再自己实现一个 c o n s t const const 迭代器的封装吧
template<class T>
struct const_ListIterator
{typedef const_ListIterator<T> Self;typedef ListNode<T> Node;Node* pNode;const_ListIterator(Node* p = nullptr):pNode(p){}const T& operator*(){return pNode->_val;}const T* operator->(){return &(pNode->_val);}Self& operator++(){pNode = pNode->_next;return *this;}Self& operator++(int){Self tmp = *this;pNode = pNode->_next;return tmp;}Self& operator--(){pNode = pNode->_prev;return *this;}Self& operator--(int){Self tmp = *this;pNode = pNode->_prev;return tmp;}bool operator!=(const Self& x){return pNode != x.pNode;}bool operator==(const Self& x){return pNode == x.pNode;}
};
3.5.3 合并两个迭代器
上面我们再重新封装了一个const_iterator
,基本满足了需求,但是代码太冗余了,除了operator*
和operator->
的返回值类型不一样,其他代码全是一样的,有什么办法将他们合二为一呢?
我们来看一下库中是怎么实现的
库中的__list_iterator类
,用了三个模板参数,新增了 Ref
与 Ptr
两个参数。
在 l i s t list list 类中,将__list_iterator<T, T&, T*>
t y p e d e f typedef typedef 成 i t e r a t o r iterator iterator,将__list_iterator<T, const T&, const T*>
t y p e d e f typedef typedef 成 c o n s t const const_ i t e r a t o r iterator iterator。
也就是说 i t e r a t o r iterator iterator 的 R e f Ref Ref 参数即 T&, P t r Ptr Ptr 即参数 T*, c o n s t const const_ i t e r a t o r iterator iterator 的 R e f Ref Ref参数即 c o n s t const const T&, P t r Ptr Ptr 参数即 c o n s t const const T*
以operator*
举例:
T& 传给Ref,Ref 即reference,operator*
的返回值是reference,替换过来operator*
的返回值就是 T&
对 c o n s t const const_ i t e r a t o r iterator iterator
c o n s t const const T& 传给 R e f Ref Ref,替换过来operator*
的返回值就是 c o n s t const const T&
其实,这种写法与上面我们自己实现两个类模板并没有本质区别,他们实例化出来都是两个不同的类。不同的是第一种写法是我们自己写了两个不同的类,而第二种是我们通过控制模板参数让编译器实例化出两个不同的类,把我们干的活交给了编译器
代码如下:
template<class T, class Ref, class Ptr>
struct ListIterator
{typedef ListIterator<T, Ref, Ptr> Self;typedef ListNode<T> Node;Node* pNode;ListIterator(Node* p = nullptr):pNode(p){}Ref operator*(){return pNode->_val;}Ptr operator->(){return &(pNode->_val);}Self& operator++(){pNode = pNode->_next;return *this;}Self& operator++(int){Self tmp = *this;pNode = pNode->_next;return tmp;}Self& operator--(){pNode = pNode->_prev;return *this;}Self& operator--(int){Self tmp = *this;pNode = pNode->_prev;return tmp;}bool operator!=(const Self& x){return pNode != x.pNode;}bool operator==(const Self& x){return pNode == x.pNode;}
};
4 源码
对于 l i s t list list 的其他成员函数,与前面的 s t r i n g string string、 v e c t o r vector vector 实现起来都大同小异,这里就不再赘述了,我们直接看源码
#pragma once
#include<iostream>
#include<assert.h>using namespace std;namespace my_list
{template<class T>struct ListNode{ListNode* _prev;ListNode* _next;T _val;ListNode(const T& val = T()):_prev(nullptr),_next(nullptr),_val(val){}};template<class T, class Ref, class Ptr>struct ListIterator{typedef ListIterator<T, Ref, Ptr> Self;typedef ListNode<T> Node;Node* pNode;ListIterator(Node* p = nullptr):pNode(p){}Ref operator*(){return pNode->_val;}Ptr operator->(){return &(pNode->_val);}Self& operator++(){pNode = pNode->_next;return *this;}Self& operator++(int){Self tmp = *this;pNode = pNode->_next;return tmp;}Self& operator--(){pNode = pNode->_prev;return *this;}Self& operator--(int){Self tmp = *this;pNode = pNode->_prev;return tmp;}bool operator!=(const Self& x){return pNode != x.pNode;}bool operator==(const Self& x){return pNode == x.pNode;}};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;list():_pHead(nullptr), _size(0){CreateHead();}list(int n, const T& value = T()){CreateHead();while (n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){CreateHead();Iterator it = first;while (it != last){push_back(*it);++it;}}list(const list<T>& l){CreateHead();const_iterator it = l.begin();while (it != l.end()){push_back(*it);++it;}}list<T>& operator=(list<T> l){swap(l);return *this;}~list(){clear();delete _pHead;_pHead = nullptr;}iterator begin(){return _pHead->_next;}iterator end(){return _pHead;}const_iterator begin() const{return _pHead->_next;}const_iterator end() const{return _pHead;}size_t size()const{return _size;}bool empty()const{return _size == 0;}T& front() {return _pHead->_next->_val;}T& back(){return _pHead->_prev->_val;}const T& front() const{return _pHead->_next->_val;}const T& back() const{return _pHead->_prev->_val;}void push_back(const T& val){Node* p = new Node(val);p->_next = _pHead;_pHead->_prev->_next = p;p->_prev = _pHead->_prev;_pHead->_prev = p;++_size;}void pop_back(){Node* p = _pHead->_prev;p->_prev->_next = _pHead;_pHead->_prev = p->_prev;delete p;--_size;}void push_front(const T& val){Node* p = new Node(val);p->_next = _pHead->_next;p->_prev = _pHead;_pHead->_next = p;p->_next->_prev = p;++_size;}void pop_front(){Node* p = _pHead->_next;_pHead->_next = p->_next;p->_next->_prev = _pHead;delete p;--_size;}iterator insert(iterator pos, const T& val){Node* p = new Node(val);p->_next = pos.pNode;p->_prev = pos.pNode->_prev;pos.pNode->_prev->_next = p;pos.pNode->_prev = p;++_size;return pos;}iterator erase(iterator pos){assert(pos != end());iterator ret = pos.pNode->_next;pos.pNode->_prev->_next = pos.pNode->_next;pos.pNode->_next->_prev = pos.pNode->_prev;delete pos.pNode;--_size;return ret;}void clear(){iterator it = begin();while (it != end()){iterator cur = it++;delete cur.pNode;}_pHead->_next = _pHead;_pHead->_prev = _pHead;_size = 0;}void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}private:Node* _pHead;size_t _size;void CreateHead(){_pHead = new Node;_pHead->_next = _pHead;_pHead->_prev = _pHead;_size = 0;}};
}template<class Container>
void print_container(const Container& v)
{auto it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}
好啦,本期关于 l i s t list list 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 C++ 的学习路上一起进步!
相关文章:

【C++】—— list 模拟实现
【C】—— list 模拟实现 1 list 基础结构2 默认构造3 迭代器3.1 整体框架3.2 成员函数3.3 begin() 与 end() 的实现3.4 operator-> 的实现3.5 const 迭代器3.5.1 const 迭代器为什么命名 const_iterator3.5.2 const 迭代器的实现3.5.3 合并两个迭代器 4 源码 1 list 基础结…...
Redis主要问题
Redis redis是单线程,基于内存操作,所以执行很快。,与网络延迟有关。 还是买早餐的例子,从早餐店买一个包子,首先早餐店得还有包子没有卖完,然后卖出,包子数量-1,重新设定早餐店包…...

vue3 ref的用法及click事件的说明
1、ref可以定义一个简单的属性,也可以是一个复杂的列表、数组等等。 2、为什么要使用 ref?简单的let个变量不行吗?const个变量不行吗? 其实这个跟vue的响应式的系统有关,官方的说明如下: 3、为 ref() 标注…...

通信工程学习:什么是HFC混合光纤同轴电缆
HFC:混合光纤同轴电缆 HFC(Hybrid Fiber Coaxial)混合光纤同轴电缆是一种结合了光纤与同轴电缆的宽带接入网技术。以下是对HFC混合光纤同轴电缆的详细解释: 一、HFC混合光纤同轴电缆的定义与概述 定义:HFC是一种结合光…...

怎么浏览URL的PDF文件呢
最近发现PDF文件网页端打开就是丑,不知道怎么办 1. 看着实在不舒服,用chorm的插件 然后原本本地用的也是2345pdf阅读器 2. 之后也下载了adobe pdf的桌面阅读器 2345打开是这个样子 这个是现在啦 如果要一些安装包什么的,评论见~ 最…...

【2025届华为秋招机考三道编程题之一】华为校招留学生软件开发工程师-真题机考笔试/(200分)- 跳格子3(Java JS Python C)
华为校招机考的题型: 编程:软件测试工程师,算法,OD岗,三道编程题不限语言【C,Python,Java】 校招:600分 120分钟,100/200/300 社招:400分 150分钟…...
高性能缓存利器:Caffeine 在 Spring Boot 中的应用
在现代应用程序中,缓存是提高数据检索速度、减少对数据库或其他数据源访问次数的重要手段。Spring Cache 提供了多种缓存实现方式,而在我们的 Spring Boot 项目中,我们选择了 Caffeine 作为默认的缓存库。 Caffeine 简介 Caffeine 是一个基…...
pWnOS的第二种全新解法(ssh私钥破解、webmin漏洞提权)
端口 端口扫描内容请看:vulnhub(8):pWnOS(还没信息收集就已经成功打点)-CSDN博客 打点 ssh登录公钥收集 ./2017.pl 192.168.234.116 10000 /home/vmware/.ssh/authorized_keys 0 ./2017.pl 192.168.234.11…...

Maven入门学习笔记
一、maven介绍 Maven是一款自动化构建工具,专注服务于JAVA平台的项目构建和依赖管理。在javaEE开发的历史上构建工具的发展也经历了一系列的演化和变迁。 管理jar包 当我们使用SSM之后我们就需要使用非常多的jar包 没有maven找jar包非常的麻烦。 使用maven下载…...

linux驱动开发-arm汇编基础
目录 写在前面 1、Cortex-A7 处理器有 9 种处理模式 2、Cortex-A 寄存器组 通用寄存器 1、汇编语法 2、Cortex-A7 常用汇编指令 2.1 处理器内部数据传输指令 2.1.1 传输数据操作类型 1、MOV指令 2、MRS指令 3、MSR指令 2.2、存储器访问指令 2.2.1 LDR指令 2.2.2 …...

【HarmonyOS】鸿蒙头像上传-(编辑个人信息页- 头像上传)+实时数据更新
#效果图 #思路 ##步骤: ###一、利用picker api选择1张图片 实例化选择器参数(使用new PhotoSelectOptions())实例化图片选择器 (使用newPhotoViewPicker() )调用图片选择器的select方法传入选择器参数完成图片选取获得结果 利用picker api选择1张图片 async sele…...

[数据集][目标检测]无人机识别检测数据集VOC+YOLO格式6986张1类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):6986 标注数量(xml文件个数):6986 标注数量(txt文件个数):6986 标注…...

基于SSM的二手交易管理系统的设计与实现 (含源码+sql+视频导入教程+文档)
👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的二手交易管理系统1拥有两种角色 管理员:商品管理、订单管理、充值管理、用户管理等用户:发布商品、查看闲置、充值账户、查看所有订单、发布求购信息、修…...
linux-centos 设置系统时间
CentOS 系统提供了多种方式来设置和管理时间,包括手动设置时间和使用网络时间协议 (NTP) 自动同步时间。以下是几种常见的方法: 手动设置时间 使用date命令临时设置时间: 如果你只需要临时设置时间,可以使用 date 命令࿱…...

【Linux基础】冯诺依曼体系结构操作系统的理解
目录 前言一,冯诺依曼体系1. 为什么有内存结构?2. 对硬件中数据流动的再理解 二,操作系统(Operator System)1. 概念2. 操作系统结构的层状划分3. 操作系统对硬件管理的理解4. 用户与操作系统的关系的理解5. 系统调用和库函数的关系6. 为什么要有操作系统…...
算法题解:斐波那契数列(C语言)
斐波那契数列 斐波那契数列是一个经典的数学序列,其中每一项的值是前两项的和。数列的前两项通常定义为0和1,即: F(0) 0 F(1) 1 F(n) F(n-1) F(n-2) (n ≥ 2)输入一个正整数n,求斐波那契数列的第n项。 样例 假设输入 n …...
SSM 框架 个人使用习惯 详细
SpringMVC主要是controller、service、dao(mapper)层交互 controller:处理数据请求的接口 service:处理请求的数据 dao(mapper):对数据进行持久化 下面我将对controller和service.impl进行讲…...

[羊城杯 2020]Blackcat1
知识点:数组加密绕过 进入页面熟悉的web三部曲(url地址,web源代码,web目录扫描) url地址没有什么东西去看看源代码. 这有一个mp3文件点一下看看. 在最后面发现了 PHP源码. if(empty($_POST[Black-Cat-Sheriff]) || em…...

腾讯云Ubuntu系统安装宝塔,配置Java环境,运行spring boot项目
致谢 本次学习宝塔部署spring boot项目,参考如下资料 https://www.cnblogs.com/daen/p/15997872.html 系统安装宝塔 直接用的腾讯云云服务器面板上的登录,你可以换成 xshell 进入宝塔官网: https://www.bt.cn/new/download.html 我们采…...

双亲委派机制知识点
类加载器 双亲委派模型 为什么采用双亲委派模型 打破双亲委派机制的场景 Tomcat 打破双亲委派机制:目的是可以加载不同版本的jar包 实现类隔离:在Tomcat中,每个Web应用使用独立的类加载器加载类文件,这样做的好处在于,当在同一T…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...