【C++初阶】模拟实现list

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、简单剖析list源码
  • 二、准备工作
  • 三、模拟实现list常见操作
      • 3.1 默认构造函数
      • 3.2 push_back - 尾插
      • 3.3 迭代器(重点)
      • 3.4 const的迭代器(重点)
      • 3.5 insert - 插入
      • 3.6 erase - 删除
      • 3.7 头插 - push_front
      • 3.8 尾删 - pop_back
      • 3.9 头删 - pop_front
      • 3.10 个数 - size
      • 3.11 析构
      • 3.12 清空 - clear
      • 3.13 拷贝构造
      • 3.14 交换
      • 3.15 赋值运算符重载
  • 四、源码

一、简单剖析list源码

在模拟vector容量讲过,要想快速了解STL源码,首先要看成员变量

在这里插入图片描述

node从名字上猜测是一个节点,其类型是list_node。然后我发现list_node也是重命名出来的:

在这里插入图片描述

__list_node<T>又是什么东西呢?如下所示:

在这里插入图片描述

显然这是一个双向链表,并且__list_node是用来定义结点的

在这里插入图片描述

接下来就应该分析构造函数

在这里插入图片描述

get_node从名字上是得到结点,那么应该是开辟空间的。我们可以简单看看:

在这里插入图片描述

空间配置器讲起来有点麻烦,直接使用newdelete也是够用的

然后nodenextprev都指向自己。因此list的底层是一个带头(哨兵位)双向循环链表,因此list的成员变量应该是哨兵位结点。

大致结构我们已经知道了,不妨再来看看插入操作:

在这里插入图片描述

这和以往学习过的双向循环链表很相似,无非就是创造新的结点,然后再把它们链接起来。

大致内容已经了解了,直接开始实现吧~

二、准备工作

为了方便管理代码,分两个文件来写:

  • Test.cpp - 测试代码逻辑
  • list.h - 模拟实现list
    在这里插入图片描述

三、模拟实现list常见操作

3.1 默认构造函数

namespace wj
{template<class T>struct list_node // 定义结点{list_node<T>* _next; list_node<T>* _prev;T _val;};template<class T>class list{public:list(){// 为哨兵位头结点开空间_head = new list_node<T>;// 自己指向自己_head->_prev = _head;_head->_next = _head;}private:list_node<T> _head; // 哨兵位(不存储有效数据)};
}

定义结点的成员变量最好是公有的,方便类外可以随时访问。注意:此处的struct可不是C语言的结构体,在C++中已经升级成了类,并且默认成员都是公有的。当然使用class也是没问题的,只是要加上public

以上代码还能简化,我们知道类模板和普通类是不同的,普通类的类名即是类型,而类模板的类名是类名<T>。而有许多人会很容易忘记加上<T>,因此我们可以对list_node<T>进行重命名typedef

namespace wj
{template<class T>struct list_node // 定义结点{list_node<T>* _next; list_node<T>* _prev;T _val;};template<class T>class list{typedef list_node<T> Node;public:list(){// 为哨兵位头结点开空间_head = new Node;// 自己指向自己_head->_prev = _head;_head->_next = _head;}private:list_node<T> _head; // 哨兵位(不存储有效数据)};
}
  • 为了防止与库的list冲突,要重新写一个命名空间域wj
  • typedef在类中是有讲究的。如果typedef放在public段中,则可以在类外部使用;而如果放在private段中,则只能在类内使用。注意:上述代码是只能在类中使用!

3.2 push_back - 尾插

void push_back(const T& val)
{//1. 找尾(哨兵位的prev)Node* tail = _head->_prev;// 2. 开辟一个新节点Node* newnode = new Node(val); // 3. 链接 _head tail newnodetail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}

尾插就容易多了,下面有图帮助大家理解:

在这里插入图片描述

注意:new对于自定义类型除了开空间,还会调用构造函数。初始化_val

struct list_node // 结点的定义
{list_node<T>* _next;list_node<T>* _prev;T _val; list_node(const T& val = T()):_next(nullptr), _prev(nullptr), _val(val){}
};

缺省值给T()相信看过模拟实现vector都不陌生。不能直接给0,这样就写死能,只能int类型适用,对于string就不行了。因此可以给个匿名对象,它会调用T类型的默认构造。内置类型也是有默认构造的:

在这里插入图片描述

3.3 迭代器(重点)

能否定义类似像vector的迭代器?如下所示:

typedef Node* iterator;

答案当然不行!list不能像vector一样以原生指针(普通指针)作为迭代器。

vector类似于数组,数据在内存中是连续存储的。对迭代器(指针)++,就可以跳过一个对象的大小,并且解引用也能得到对应的数据;然而,list的节点不能保证一定在内存空间中连续存在,导致++/--不一定能找到下一个节点,并且对其解引用得到的是结点而不是有效数据。

那问题来了,如何定义list的迭代器呢?

我们可以封装一个类,然后用重载运算符去改变指针的行为。为什么可以这样呢?原因是:内置类型的++是行为规定的,但是自定义类型的++是自己说的算。可以联想以往实现的日期类->点击跳转

auto it = l.begin();
while (it != l.end())
{cout << *it << ' ';++it;
}

我们可以对照以上代码一步一步实现迭代器

  • begin() + end()

在这个类中,只需要一个结点类的指针成员变量,用于指向list某一个结点, 在一开始定义迭代器时,需要一个构造函数,用于迭代器的初始化。注意:beginend需要定义在list类中,因为它们本身就是list内置的接口函数

// 封装一个类实现迭代器
template<class T>
struct __list_iterator 
{typedef list_node<T> Node;Node* _node; //指向某个节点的指针// 迭代器的初始化__list_iterator(Node* node) :_node(node){}
};template<class T>
class list
{typedef list_node<T> Node;
public:typedef __list_iterator<T> iterator; iterator begin(){return _head->_next;// return iterator(_head->_next);}iterator end(){return _head;//return iterator(_head);}
private:Node* _head;
};

这里还有一个知识点,beginend返回类型为迭代器,怎么能返回结点的指针呢?— 这是因为单参数的构造函数支持隐式类型转换。

  • !=== *++--

封装一个类,然后用重载运算符去改变指针的行为

// 封装一个类实现迭代器
template<class T>
struct __list_iterator 
{typedef list_node<T> Node;typedef __list_iterator<T> self;Node* _node; //指向某个节点的指针__list_iterator(Node* node) // 迭代器的初始化:_node(node){}
/// 用结点的指针比bool operator!=(const self& it) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}
/T& operator*(){// 出了作用域,结点还在,引用返回return _node->_val;}
/// 迭代器++返回的还是迭代器self& operator++() //前置{_node = _node->_next;return *this;}self& operator--() // 前置{_node = _node->_prev;return *this;}self operator--(int) // 后置{self tmp(*this);_node = _node->_prev;return tmp;}self operator++(int) // 后置{self tmp(*this);_node = _node->_next;return tmp;}
};

前置++后置++会发生一个问题:函数名会相同。因此,C++规定:后置(++/--)重载时多增加一个int类型的参数,但调用函数时该参数不用传递。

3.4 const的迭代器(重点)

现在又有一个问题,const的迭代器也能否像类似于vector一样设计?如下所示:

在这里插入图片描述

答案当然是不可以的!这是因为 const迭代器要求的是迭代器指向的内容不可以被修改,而对一个类加上一个const,这是让这个类对象无法被修改啊。也就是类的成员变量都不可以被修改,这样一来,这个迭代器里面的指针无法移动了。(const的迭代器指针是可以移动的,但是指向的内容不可被修改)

那么const的迭代器该如何设计呢?我们知道,list迭代器输出数据是依靠解引用的,因此可以在返回值加上const

template<class T>
struct __list_iterator
{typedef list_node<T> Node;typedef __list_iterator<T> selfNode* _node; //指向某个节点的指针__list_iterator(Node* node) // 迭代器的初始化:_node(node){}// 用结点的指针比bool operator!=(const self& it) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}T& operator*(){// 出了作用域,结点还在,引用返回return _node->_val;}// 迭代器++返回的还是迭代器self& operator++() //前置{_node = _node->_next;return *this;}self& operator--() // 前置{_node = _node->_prev;return *this;}self operator--(int) // 后置{self tmp(*this);_node = _node->_prev;return tmp;}self operator++(int) // 后置{self tmp(*this);_node = _node->_next;return tmp;}
};template<class T>
struct __list_iterator
{typedef list_node<T> Node;typedef __list_iterator<T> self;Node* _node; //指向某个节点的指针__list_iterator(Node* node) // 迭代器的初始化:_node(node){}// 用结点的指针比bool operator!=(const self& it) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}const T& operator*(){// 出了作用域,结点还在,引用返回return _node->_val;}// 迭代器++返回的还是迭代器self& operator++() //前置{_node = _node->_next;return *this;}self& operator--() // 前置{_node = _node->_prev;return *this;}self operator--(int) // 后置{self tmp(*this);_node = _node->_prev;return tmp;}self operator++(int) // 后置{self tmp(*this);_node = _node->_next;return tmp;}
};

但以上代码显得有点冗余,只有两个函数的返回值不一样,其它都是一样的。那还有什么别的设计方法呢?

注意:上面两个函数只要返回值的类型不一样,因此可以通过一个类型来控制返回值 -> 即增加一个模板参数(库里也是这么实现的~)

// 封装一个类实现迭代器
template<class T, class Ref> // 增加一个模板参数
struct __list_iterator
{typedef list_node<T> Node;typedef __list_iterator<T, Ref> self;Node* _node; //指向某个节点的指针__list_iterator(Node* node) // 迭代器的初始化:_node(node){}Ref operator*(){return _node->_val;}
}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;iterator begin(){return _head->_next;}const_iterator end() const{return _head;}const_iterator begin() const{return _head->_next;}iterator end(){return _head;}
private:list_node<T> _head; // 哨兵位(不存储有效数据)
};

补充:除了重载*运算符,当然也要重载->操作符

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

那什么时候会用到->操作符呢?下面有个例子:

#include <iostream>
#include "list.h"
using namespace std;struct A
{A(int a = 0):_a(a){}int _a;
};
int main()
{wj::list<A> lt;lt.push_back(A(1));lt.push_back(A(2));lt.push_back(A(3));lt.push_back(A(4));lt.push_back(A(5));wj::list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a << " ";it++;}cout << endl;
}

【输出结果】

在这里插入图片描述

有没有发现operator->非常怪,首先我们这个运算符重载返回的是什么呢?是T*,也就是A*,也就是说它还需要一次->才能打印_a。严格来说,it->->_a,才是符合语法的。那么这里为什么还能编译通过呢?因为运算符重载要求可读性,那么编译器特殊处理,省略了一个->

但是以上代码还是不够完善,由于->只针对普通对象,如果是const对象,其返回值应该是const T*,这个问题就和运算符重载*类似了,再增加一个模板参数,因此完整代码如下:

template<class T, class Ref, class ptr>
struct __list_iterator // 迭代器
{typedef list_node<T> Node;typedef __list_iterator<T, Ref, ptr> self;Node* _node; //指向某个节点的指针__list_iterator(Node* node) // 迭代器的初始化:_node(node){}Ref operator*(){return _node->_val;// 出了作用域,结点还在,要加&}ptr operator->() {return &_node->_val;}
}template<class T> // 为list提供
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; iterator begin(){// return iterator(_head->_next);return _head->_next;}iterator end(){// return iterator(_head);return _head;}
private:Node* _head; // 哨兵位(不存储有效数据)
};

3.5 insert - 插入

iterator insert(iterator pos, const T& x)
{// pos 不需要检查  // 假设在node前插入// head newnode node tail// 步骤如下// 1. 开辟新的结点Node* newnode = new Node(x);// 2. 找到要删除的结点nodeNode* cur = pos._node;// 3. 以及node的前一个节点Node* prev = cur->_prev;// 4. 链接prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;return newnode;// 返回新插入元素的位置
}

在这里插入图片描述

3.6 erase - 删除

iterator erase(iterator pos)
{// 检查pos的有效性assert(pos != end());// 1.分别找到pos的前一个节点和后一个节点Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;// 2, 链接prev->_next = next;next->_prev = prev;// 3. 删除delete cur;// 注意:list的erase会有迭代器失效问题// 返回删除元素的下一个位置return next;
}

在这里插入图片描述

3.7 头插 - push_front

复用insert

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

3.8 尾删 - pop_back

复用erase

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

3.9 头删 - pop_front

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

3.10 个数 - size

遍历即可

size_t size()
{size_t count = 0;iterator it = begin();while (it != end()){++count;++it;}return count;
}

或者还可以在成员变量中定义size_t _size,每次插入数据++,以及删除数据--即可

3.11 析构

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

3.12 清空 - clear

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

3.13 拷贝构造

list(const list<T>& it)
{_head = new Node;_head->_prev = _head;_head->_next = _head;for (auto& e : it){push_back(e);}
}

3.14 交换

void swap(list<T> it)
{std::swap(_head, it._head);std::swap(this->size(), it._size());
}

3.15 赋值运算符重载

list<T>& operator=(const list<T> it)
{swap(it);return *this;
}

四、源码

#pragma once
#include <assert.h>namespace wj
{template<class T> struct list_node {list_node<T>* _next;list_node<T>* _prev;T _val; list_node(const T& val = T()):_next(nullptr), _prev(nullptr), _val(val){}};template<class T, class Ref, class ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, ptr> self;Node* _node; __list_iterator(Node* node) :_node(node){}Ref operator*(){return _node->_val;}ptr operator->() {return &_node->_val;}self& operator++(){_node = _node->_next;return *this;}self& operator--(){_node = _node->_prev;return *this;}self& operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const self& it) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}};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; iterator begin(){// return iterator(_head->_next);return _head->_next;}iterator end(){// return iterator(_head);return _head;}const_iterator begin() const{//return _head->_next;return const_iterator(_head->_next);}const_iterator end() const{return _head;//return const_iterator(_head);}list(){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;}list(const list<T>& it){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;for (auto& x : it){push_back(x);}}void push_back(const T& val){Node* tail = _head->_prev;Node* newnode = new Node(val);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;_size++;}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;_size++;return newnode;}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;_size--;return next;}void push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}size_t size(){/*size_t count = 0;iterator it = begin();while (it != end()){++count;++it;}return count;*/return _size;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}void swap(list<T> it){std::swap(_head, it._head);std::swap(_size, it._size);}list<T>& operator=(const list<T> it){swap(it);return *this;}private:Node* _head; size_t _size;};
}

测试代码

#include <iostream>
using namespace std;
#include "list.h"int main()
{// 默认构造wj::list<int> ll;// 尾插测试ll.push_back(1);ll.push_back(2);ll.push_back(3);ll.push_back(4);// 迭代器测试wj::list<int>::iterator it = ll.begin();while (it != ll.end()){cout << *it << ' ';it++;}cout << endl;// 范围for(底层迭代器)for (auto& x : ll){cout << x << ' ';}cout << endl;// insert测试// 在3的前面插入30it = ll.begin();for (int i = 0; i < 2; i++){it++;}ll.insert(it, 30);for (auto& x : ll){cout << x << ' ';}cout << endl;//  erase测试it = ll.begin();// 删除30for (int i = 0; i < 2; i++){it++;}ll.erase(it);for (auto x : ll){cout << x << ' ';}cout << endl;// 头插测试// 头插100ll.push_front(100);for (auto x : ll){cout << x << ' ';}cout << endl;// 尾删测试ll.pop_back(); // 100 1 2 3for (auto x : ll){cout << x << ' ';}cout << endl;// 头删测试ll.pop_front(); // 1 2 3for (auto x : ll){cout << x << ' ';}cout << endl;// size测试cout << "个数为:" << ll.size() << endl; // 3// 清空ll.clear();for (auto x : ll){cout << x << ' '; // 无输出}cout << endl;// 拷贝构造ll.push_back(1);ll.push_back(2);ll.push_back(3);ll.push_back(4);ll.push_back(5);wj::list<int> lll(ll);for (auto x : lll){cout << x << ' '; // 1 2 3 4 5}cout << endl;// 赋值运算符重载wj::list<char> a;a.push_back('a');wj::list<char> b;b.push_back('b');b.push_back('b');b.push_back('b');a = b;for (auto x : a){cout << x << ' ';}cout << endl;// 交换wj::list<char> c;a.push_back('c');wj::list<char> d;b.push_back('d');b.push_back('d');b.push_back('d');d.swap(c);for (auto x : c){cout << x << ' ';}cout << endl;for (auto x : d){cout << x << ' ';}cout << endl;return 0;
}

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

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

相关文章

自组织映射

无监督深度学习&#xff1a; 无监督模型使神经网络能够执行聚类、异常检测、特征选择、特征提取、降维和推荐系统等任务。这些神经网络包括 自组织图、玻尔兹曼机、自动编码器。 什么是 SOM&#xff1f; 简而言之&#xff0c;自组织映射是一种基于竞争学习的人工神经网络&am…

<C++> STL_vector

1.vector的介绍 vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它的…

[管理与领导-52]:IT基层管理者 - 8项核心技能 - 7 - 决策

目录 前言&#xff1a; 一、什么是决策 二、为什么需要管理者的决策 三、什么时候需要管理者决策 四、常见的决策误区 4.1 关于决策的误区 4.2 错误的决策行为 五、如何进行有效决策 六、进行决策的常用方法 前言&#xff1a; 管理者存在的价值就是制定目标&#xff0…

docker 学习-- 04 实践搭建 1(宝塔)

docker 学习-- 04 实践 1&#xff08;宝塔&#xff09; docker 学习-- 01 基础知识 docker 学习-- 02 常用命令 docker 学习-- 03 环境安装 docker 学习-- 04 实践 1&#xff08;宝塔&#xff09; 通过上面的学习&#xff0c; 已经可以搭建简单的案例&#xff0c; 接着我会搭…

2000-2021年地级市产业升级、产业结构高级化面板数据

2000-2021年地级市产业升级、产业结构高级化面板数据 1、时间&#xff1a;2000-2021年 2、范围&#xff1a;地级市 3、指标&#xff1a;年份、地区、行政区划代码、地区、所属省份、地区生产总值、第一产业增加值、第二产业增加值、第三产业增加值、第一产业占GDP比重、第二…

【RuoYi移动端】HBuild工具插件安装和系统配置manifest.json

一、点【工具】-【插件安装】安装如下工具 二、点【manifest.json】

基于Java+SpringBoot+Vue前后端分离公交线路查询系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

最新人工智能源码搭建部署教程/ChatGPT程序源码/AI系统/H5端+微信公众号版本源码

一、AI系统 如何搭建部署人工智能源码、AI创作系统、ChatGPT系统呢&#xff1f;小编这里写一个详细图文教程吧&#xff01; SparkAi使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到AIGC系统&#xff01; 1.1 程序核心功能 程序已支持ChatGPT3.5/GPT-4提问、AI绘画、…

D.OASIS City 和 Warrix 在The Sandbox 庆祝 Rise of the 10th Legend十周年

D.OASIS 首次展示了变革性娱乐 D.OASIS City&#xff0c;正如它与 WARRIX 一起承诺的那样。WARRIX 是获得泰国国家队球衣生产授权的标志性运动服装品牌。 这款激动人心的游戏冒险游戏于今天推出&#xff0c;让用户能够投入 D.OASIS City x WARRIX&#xff1a;Rise of the 10th…

八月更新 | CI 构建计划触发机制升级、制品扫描 SBOM 分析功能上线!

点击链接了解详情 这个八月&#xff0c;腾讯云 CODING DevOps 对持续集成、制品管理、项目协同、平台权限等多个产品模块进行了升级改进&#xff0c;为用户提供更灵活便捷的使用体验。以下是 CODING 新功能速递&#xff0c;快来看看是否有您期待已久的功能特性&#xff1a; 01…

数据结构(Java实现)-二叉树(上)

树型结构 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#xff0c;称为根结点&…

【微服务】04-Polly实现失败重试和限流熔断

文章目录 1. Polly实现失败重试1.1 Polly组件包1.2 Polly的能力1.3 Polly使用步骤1.4 适合失败重试的场景1.5 最佳实践 2.Polly实现熔断限流避免雪崩效应2.1 策略类型2.2 组合策略 1. Polly实现失败重试 1.1 Polly组件包 PollyPolly.Extensions.HttpMicrosoft.Extensions.Htt…

Maven的超级POM

对于我们创建的一个maven工程&#xff0c;即便我们自己的pom.xm文件中没有明确指定一个父工程&#xff08;父POM&#xff09;&#xff0c;其实也默认继承了超级POM&#xff0c;就好比JAVA类继承Object类一样。 maven官网关于超级POM的介绍&#xff1a; https://maven.apache.o…

wireshark进行网络监听

一、实验目的&#xff1a; 1&#xff09;掌握使用CCProxy配置代理服务器&#xff1b; 2&#xff09;掌握使用wireshark抓取数据包&#xff1b; 3&#xff09;能够对数据包进行简单的分析。 二、预备知识&#xff1a; 包括监听模式、代理服务器、中间人攻击等知识点&#xf…

微信开发之一键修改群聊备注的技术实现

修改群备注 修改群名备注后&#xff0c;如看到群备注未更改&#xff0c;是手机缓存问题&#xff0c;可以连续点击进入其他群&#xff0c;在点击进入修改的群&#xff0c;再返回即可看到修改后的群备注名&#xff0c;群名称的备注仅自己可见 请求URL&#xff1a; http://域名…

【无标题】jenkins消息模板(飞书)

这里写目录标题 Jenkins 安装的插件 发送消息到飞书预览 1 &#xff08;单Job&#xff09;预览 2 &#xff08;多Job&#xff0c;概览&#xff09; Jenkins 安装的插件 插件名称作用Rebuilder Rebuilder。 官方地址&#xff1a;https://plugins.jenkins.io/rebuild 安装方式&a…

leetcode1475. 商品折扣后的最终价格 【单调栈】

简单题 第一次错误做法 class Solution { public:vector<int> finalPrices(vector<int>& prices) {int n prices.size();stack<int> st;unordered_map<int, int> mp;int i 0;while(i ! prices.size()) {int t prices[i];if (st.empty() || t …

适应高速率网络设备的-2.5G/5G/10G网络变压器/网络滤波器介绍

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;在高速发展的互联网/物联网时代&#xff0c;为满足高网速的网络数据传输需求&#xff0c;网络设备在制造中也要选用合适的网络变压器/滤波器产品&#xff0c;有哪些可供选择的高速率网络变压器产品也是广大采购人员…

C++初学者学习指南

文章目录 环境职业选择基本技能新特性与学习曲线高阶技能C模版元编程线程池&#xff0c;异步任务 C 相关工具及资源C ToolsC Resources 项目大项目小项目 如何学未来期望 环境 编程工具&#xff1a;VSCode插件&#xff1a; BazelC/CClang-FormatVim 职业选择 AI领域&#xf…

如何将多个网页合并成一个PDF文件?

pdfFactory是一款PDF虚拟打印软件&#xff0c;但与其他虚拟打印机软件不同的是&#xff0c;它使用起来更加简单高效。由于无需Acrobat就能生成Adobe PDF文件&#xff0c;它可以帮助用户在系统没有连接打印机的情况下&#xff0c;将大部分支持打印的文档资料迅速转换成PDF文件&a…