【linux学习指南】模拟线程封装与智能指针shared_ptr
文章目录
- 📝线程封装
- 🌉 Thread.hpp
- 🌉 Makefile
- 🌠线程封装第一版
- 🌉 Makefile:
- 🌉Main.cc
- 🌉 Thread.hpp:
- 🌠线程封装第二版
- 🌉 Thread.hpp:
- 🌉 Main.cc
- 🌠单线程创建测试
- 🌉 Thread.hpp
- 🌉 main.cc
- 🌠智能指针std::shared_ptr
- 🚩总结
📝线程封装
🌉 Thread.hpp
// Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
namespace ThreadModule
{// 原⼦计数器,⽅便形成线程名称std::uint32_t cnt = 0;// 线程要执⾏的外部⽅法,我们不考虑传参,后续有std::bind 来进⾏类间耦合using threadfunc_t = std::function<void()>;// 线程状态enum class TSTATUS{THREAD_NEW,THREAD_RUNNING,THREAD_STOP};// 线程class Thread{private:static void *run(void *obj){Thread *self = static_cast<Thread *>(obj);pthread_setname_np(pthread_self(), self->_name.c_str()); // 设置线程名称self->_status = TSTATUS::THREAD_RUNNING;if (!self->_joined){pthread_detach(pthread_self());}self->_func();return nullptr;}void SetName(){// 后期加锁保护_name = "Thread-" + std::to_string(cnt++);}public:Thread(threadfunc_t func) : _status(TSTATUS::THREAD_NEW),_joined(true), _func(func){SetName();}void EnableDetach(){if (_status == TSTATUS::THREAD_NEW)_joined = false;}void EnableJoined(){if (_status == TSTATUS::THREAD_NEW)_joined = true;}bool Start(){if (_status == TSTATUS::THREAD_RUNNING)return true;int n = ::pthread_create(&_id, nullptr, run, this);if (n != 0)return false;return true;}bool Join(){if (_joined){int n = pthread_join(_id, nullptr);if (n != 0)return false;return true;}return false;}~Thread() {}private:std::string _name;pthread_t _id;TSTATUS _status;bool _joined;threadfunc_t _func;};
}
🌉 Makefile
// main.cc
#include <iostream>
#include <unistd.h>
#include "Thread.hpp"
void hello1()
{char buffer[64];pthread_getname_np(pthread_self(), buffer, sizeof(buffer) - 1);while (true){}std::cout << "hello world, " << buffer << std::endl;sleep(1);
}
void hello2()
{char buffer[64];pthread_getname_np(pthread_self(), buffer, sizeof(buffer) - 1);while (true){std::cout << "hello world, " << buffer << std::endl;sleep(1);}
}
int main()
{pthread_setname_np(pthread_self(), "main");ThreadModule::Thread t1(hello1);t1.Start();ThreadModule::Thread t2(std::bind(&hello2));t2.Start();t1.Join();t2.Join();return 0;
}
运⾏结果查询
$ ps -aLPID LWP TTY TIME CMD
195828 195828 pts/1 00:00:00 main
195828 195829 pts/1 00:00:00 Thread-0
195828 195830 pts/1 00:00:00 Thread-1
🌠线程封装第一版
🌉 Makefile:
bin=testThread
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)$(bin):$(obj)$(cc) -o $@ $^ -lpthread
%.o:%.cc$(cc) -c $< -std=c++17.PHONY:test
test:echo $(src)echo $(obj)
🌉Main.cc
#include "Thread.hpp"
#include <unordered_map>
#include <memory>// using thread_ptr_t = std::shared_ptr<ThreadModule::Thread>;#define NUM 10;class threadData
{
public:int max;int start;
};void Count(threadData td)
{for(int i = td.start; i < td.max; i++){std::cout<< "i == " <<i <<std::endl;sleep(1);}
}int main()
{threadData td;td.max = 60;td.start = 50;//使用lamda表达式封装Count成一个不接受参数的可调用对象auto func = [td](){Count(td);};ThreadModule::Thread<threadData> t(func);t.Start();t.Join();return 0;
}
🌉 Thread.hpp:
//V1
namespace ThreadModule
{// template<typename T>using func_t = std::function<void()>;static int number = 1;enum class TSTATUS{NEW,RUNNING,STOP};template<typename T>class Thread{private:static void* Routine(void *args){Thread* t = static_cast<Thread *>(args);t->_status = TSTATUS::RUNNING;t->_func();return nullptr;}void EnableDetach(){_joinable = false;}public:Thread(func_t func): _func(func), _status(TSTATUS::NEW), _joinable(true){_name = "Thread - " + std::to_string(number++);_pid = getpid();}bool Start(){if (_status != TSTATUS::RUNNING){int n = ::pthread_create(&_tid, nullptr, Routine, this); // 这里使用thisif (n != 0)return false;return true;}return true;}bool Stop(){if (_status != TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if (n != 0)return false;_status = TSTATUS::STOP; return true;}return false;}bool Join(){if(_joinable){int n = ::pthread_join(_tid, nullptr);if(n != 0)return false;_status = TSTATUS::STOP; return true; }return false;}void Detach(){EnableDetach();pthread_detach(_tid);}bool IsJoinable(){return _joinable;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;pid_t _pid;bool _joinable; // 是否是分离的, 默认不是func_t _func;TSTATUS _status;};
}#endif
🌠线程封装第二版
🌉 Thread.hpp:
#ifndef THREAD_HPP
#define THREAD_HPP#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>//V2
namespace ThreadModule
{static int number = 1;enum class TSTATUS{NEW,RUNNING,STOP};template<typename T>class Thread{using func_t = std::function<void(T)>;private://成员方法static void *Routine(void* args){Thread<T> *t = static_cast<Thread<T>*>(args);t->_status = TSTATUS::RUNNING;t->_func(t->_data);return nullptr;}void EnableDetach(){_joinable = false;}public:Thread(func_t func, T data):_func(func),_data(data),_status(TSTATUS::NEW),_joinable(true){_name = "Thread -" +std::to_string(number++);_pid = getpid();}bool Start(){if(_status != TSTATUS::RUNNING){int n = pthread_create(&_tid, nullptr, Routine, this);if(n != 0)return false;return true;}return false;}bool Stop(){if(_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if(n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Join(){if(_joinable){int n = ::pthread_join(_tid, nullptr);if(n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool IsJoinale(){return _joinable;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;bool _joinable;//是否是分离的,默认不是func_t _func;pid_t _pid;TSTATUS _status;T _data;};
}#endif
🌉 Main.cc
#include "Thread.hpp"
#include <unordered_map>
#include <memory>using thread_ptr_t = std::shared_ptr<ThreadModule::Thread<int>>;#define NUM 10class threadData
{
public:int max;int start;
};void Count(threadData td)
{for(int i = td.start; i < td.max; i++){std::cout<< "i == " <<i <<std::endl;sleep(1);}
}
int main()
{//先描述再组织std::unordered_map<std::string, thread_ptr_t> threads;//如果我们要创建多线程呢?for(int i = 0; i< NUM ; i++){auto func = [](){while(true){std::cout<< "hello world" << std::endl;sleep(1);}};int threadData = i+1; thread_ptr_t t= std::make_shared<ThreadModule::Thread<int>>([func](int){func();}, threadData);std::cout<< "Create thread with name : "<<t->Name() <<std::endl;threads[t->Name()] = t;}for(auto &thread : threads){thread.second->Start();}for(auto &thread : threads){thread.second->Join();}
🌠单线程创建测试
🌉 Thread.hpp
#ifndef THREAD_HPP
#define THREAD_HPP#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>//V2
namespace ThreadModule
{static int number = 1;enum class TSTATUS{NEW,RUNNING,STOP};template<typename T>class Thread{using func_t = std::function<void(T)>;private://成员方法static void *Routine(void* args){Thread<T> *t = static_cast<Thread<T>*>(args);t->_status = TSTATUS::RUNNING;t->_func(t->_data);return nullptr;}void EnableDetach(){_joinable = false;}public:Thread(func_t func, T data):_func(func),_data(data),_status(TSTATUS::NEW),_joinable(true){_name = "Thread -" +std::to_string(number++);_pid = getpid();}bool Start(){if(_status != TSTATUS::RUNNING){int n = pthread_create(&_tid, nullptr, Routine, this);if(n != 0)return false;return true;}return false;}bool Stop(){if(_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if(n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Join(){if(_joinable){int n = ::pthread_join(_tid, nullptr);if(n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool IsJoinale(){return _joinable;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;bool _joinable;//是否是分离的,默认不是func_t _func;pid_t _pid;TSTATUS _status;T _data;};
}
🌉 main.cc
#include "Thread.hpp"
#include <unordered_map>
#include <memory>using thread_ptr_t = std::shared_ptr<ThreadModule::Thread<int>>;#define NUM 10class threadData
{
public:int max;int start;
};void Count(threadData td)
{for(int i = td.start; i < td.max; i++){std::cout<< "i == " <<i <<std::endl;sleep(1);}
}int main()
{auto func = [](){while(true){std::cout<< "hello world" <<std::endl;sleep(1);}};std::function<void(int)> wrappedFunc2 = [func](int){ func(); };int signalTreadData = 5;ThreadModule::Thread<int> t( wrappedFunc2,signalTreadData);// std::cout<< "Create thread with name : "<<t->Name() <<std::endl;// t.Start();// t.Join();t.Start();std::cout<< t.Name() << " is running" <<std::endl;std::cout<<std::flush;//手动刷新缓冲期sleep(5);if (t.Stop()) {std::cout << "Stop thread : " << t.Name() << std::endl;std::cout << std::flush; // 手动刷新缓冲区} else {std::cout << "Failed to stop thread : " << t.Name() << std::endl;std::cout << std::flush; // 手动刷新缓冲区}sleep(1);if (t.Join()) {std::cout << "Join thread : " << t.Name() << std::endl;std::cout << std::flush; // 手动刷新缓冲区} else {std::cout << "Failed to join thread : " << t.Name() << std::endl;std::cout << std::flush; // 手动刷新缓冲区}return 0;
}
🌠智能指针std::shared_ptr
std::shared_ptr
是 C++ 标准库 <memory>
头文件中提供的一种智能指针,用于管理动态分配的对象,它实现了共享所有权的语义,下面为你详细介绍它的作用、工作原理以及在你给出的代码中的使用场景。
作用
在传统的 C++ 中,使用 new
操作符动态分配内存后,需要手动使用 delete
操作符释放内存,否则会导致内存泄漏。std::shared_ptr
可以自动管理动态分配的对象的生命周期,当没有任何 std::shared_ptr
指向该对象时,它会自动释放对象所占用的内存,从而避免了手动管理内存带来的复杂性和潜在的内存泄漏问题。
工作原理
std::shared_ptr
使用引用计数的机制来管理对象的生命周期。每个 std::shared_ptr
都维护一个引用计数,记录有多少个 std::shared_ptr
共享同一个对象。当一个新的 std::shared_ptr
指向一个对象时,引用计数加 1;当一个 std::shared_ptr
被销毁或者指向其他对象时,引用计数减 1。当引用计数变为 0 时,说明没有任何 std::shared_ptr
再指向该对象,此时 std::shared_ptr
会自动调用对象的析构函数并释放内存。
-
using thread_ptr_t = std::shared_ptr<ThreadModule::Thread<int>>;
这行代码使用using
关键字定义了一个类型别名thread_ptr_t
,它实际上是std::shared_ptr<ThreadModule::Thread<int>>
的别名。这样做的好处是可以简化代码,避免在后续使用时多次书写冗长的类型名。这里的ThreadModule::Thread<int>
是一个模板类的实例化,表示一个线程对象,std::shared_ptr
用于管理这个线程对象的生命周期。 -
std::unordered_map<std::string, thread_ptr_t> threads;
这行代码定义了一个std::unordered_map
,它是一个无序关联容器,用于存储键值对。键的类型是std::string
,值的类型是thread_ptr_t
,也就是std::shared_ptr<ThreadModule::Thread<int>>
。通过这种方式,可以将线程对象与一个字符串键关联起来,方便对线程对象进行管理和查找。 -
thread_ptr_t t = std::make_shared<ThreadModule::Thread<int>>( ... );
这行代码使用std::make_shared
函数创建了一个std::shared_ptr<ThreadModule::Thread<int>>
对象,并将其赋值给t
。std::make_shared
是一个便捷的函数,用于创建std::shared_ptr
对象,它会在一次内存分配中同时分配对象和引用计数所需的内存,比分别使用new
和std::shared_ptr
的构造函数更加高效。括号内的参数是传递给ThreadModule::Thread<int>
构造函数的参数,用于初始化线程对象。
示例代码
#include <iostream>
#include <memory>class MyClass {
public:MyClass() { std::cout << "MyClass constructor" << std::endl; }~MyClass() { std::cout << "MyClass destructor" << std::endl; }void doSomething() { std::cout << "Doing something..." << std::endl; }
};int main() {// 创建一个 std::shared_ptr 对象std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();// 复制一个 std::shared_ptr 对象,引用计数加 1std::shared_ptr<MyClass> ptr2 = ptr1;// 调用对象的成员函数ptr1->doSomething();// 当 ptr1 和 ptr2 离开作用域时,引用计数减 1// 当引用计数变为 0 时,对象会被自动销毁return 0;
}
代码解释
- 在
main
函数中,首先使用std::make_shared
创建了一个std::shared_ptr<MyClass>
对象ptr1
,此时引用计数为 1。 - 然后将
ptr1
赋值给ptr2
,引用计数变为 2。 - 调用
ptr1->doSomething()
来调用对象的成员函数。 - 当
ptr1
和ptr2
离开main
函数的作用域时,它们会被销毁,引用计数减 1。当引用计数变为 0 时,MyClass
对象的析构函数会被自动调用,释放对象所占用的内存。
🚩总结
如果要像C++11那样进⾏可变参数的传递,是可以这样设计的,但是太⿇烦了,真到了哪⼀步,就直接⽤c++11吧,我们的⽬标主要是理解系统概念对象化,此处不做复杂设计,⽽且后续可以使⽤std::bind来进⾏对象间调⽤
相关文章:

【linux学习指南】模拟线程封装与智能指针shared_ptr
文章目录 📝线程封装🌉 Thread.hpp🌉 Makefile 🌠线程封装第一版🌉 Makefile:🌉Main.cc🌉 Thread.hpp: 🌠线程封装第二版🌉 Thread.hpp:🌉 Main.cc …...

10、Python面试题解析:解释reduce函数的工作原理
reduce 是 Python 中的一个高阶函数,位于 functools 模块中。它的作用是将一个可迭代对象(如列表、元组等)中的元素依次通过一个二元函数(即接受两个参数的函数)进行累积计算,最终返回一个单一的结果。 1.…...

【含开题报告+文档+PPT+源码】学术研究合作与科研项目管理应用的J2EE实施
开题报告 本研究构建了一套集注册登录、信息获取与科研项目管理于一体的综合型学术研究合作平台。系统用户通过注册登录后,能够便捷地接收到最新的系统公告和科研动态新闻,并能进一步点击查看详尽的新闻内容。在科研项目管理方面,系统提供强…...

MySQL主从复制过程,延迟高,解决应对策略
MySQL主从复制延迟高是常见的性能问题,通常由主库写入压力大、从库处理能力不足或配置不当导致。以下从原因定位、优化策略和高级解决方案三个维度提供系统性解决方法: 一、快速定位延迟原因 1. 查看主从同步状态 SHOW SLAVE STATUS\G关键字段…...

Deepseek模拟阿里面试——数据库
在模拟阿里面试时,数据库部分需要涵盖广泛的知识点,包括基础概念、事务管理、索引优化、数据库设计、高并发处理、分布式数据库等。以下是对这些问题的详细分析和解答: 事务的ACID特性是什么,如何保证? ACID特性&…...

大数据学习之SparkStreaming、PB级百战出行网约车项目一
一.SparkStreaming 163.SparkStreaming概述 Spark Streaming is an extension of the core Spark API that enables scalable, high-throughput, fault-tolerant stream processing of live data streams. Spark Streaming 是核心 Spark API 的扩展,支持实时数据…...

Java 高频面试闯关秘籍
目录 Java基础篇:涵盖OOP、多线程、集合等基础知识。Java高级篇:深入探讨HashMap、JVM、线程池等高级特性。Java框架篇:介绍Spring、SpringMVC、MyBatis等常用框架。Mysql数据库篇:包含SQL语句、事务、索引等数据库知识。分布式技…...

边缘计算网关驱动智慧煤矿智能升级——实时预警、低延时决策与数字孪生护航矿山安全高效运营
迈向智能化煤矿管理新时代 工业物联网和边缘计算技术的迅猛发展,煤矿安全生产与高效运营正迎来全新变革。传统煤矿监控模式由于现场环境复杂、数据采集和传输延时较高,已难以满足当下高标准的安全管理要求。为此,借助边缘计算网关的实时数据…...

Oracle认证大师(OCM)学习计划书
Oracle认证大师(OCM)学习计划书 一、学习目标 Oracle Certified Master(OCM)是Oracle官方认证体系中的最高级别认证,要求考生具备扎实的数据库管理技能、丰富的实战经验以及解决复杂问题的能力。本计划旨在通过系统化的…...

力扣 单词拆分
动态规划,字符串截取,可重复用,集合类。 题目 单词可以重复使用,一个单词可用多次,应该是比较灵活的组合形式了,可以想到用dp,遍历完单词后的状态的返回值。而这里的wordDict给出的是list&…...

如何在Linux中设置定时任务(cron)
在Linux系统中,定时任务是自动执行任务的一种非常方便的方式,常常用于定期备份数据、更新系统或清理日志文件等操作。cron是Linux下最常用的定时任务管理工具,它允许用户根据设定的时间间隔自动执行脚本和命令。在本文中,我们将详…...

C# ASP.NET核心特性介绍
.NET学习资料 .NET学习资料 .NET学习资料 在当今的软件开发领域中,C# ASP.NET凭借其强大的功能和丰富的特性,成为构建 Web 应用程序的重要技术之一。以下将详细介绍 C# ASP.NET的核心特性。 多语言支持 ASP.NET 支持多种语言进行开发,这使…...

Response 和 Request 介绍
怀旧网个人博客网站地址:怀旧网,博客详情:Response 和 Request 介绍 1、HttpServletResponse 1、简单分类 2、文件下载 通过Response下载文件数据 放一个文件到resources目录 编写下载文件Servlet文件 public class FileDownServlet exten…...

Spring常用注解和组件
引言 了解Spring常用注解的使用方式可以帮助我们更快速理解这个框架和其中的深度 注解 Configuration:表示该类是一个配置类,用于定义 Spring Bean。 EnableAutoConfiguration:启用 Spring Boot 的自动配置功能,让 Spring Boo…...

Spring中都应用了哪些设计模式?
好的!以下是您提到的八种设计模式在 Spring 中的简单示例: 1. 简单工厂模式 简单工厂模式通过传入参数来决定实例化哪个类。Spring 中的 BeanFactory 就是简单工厂模式的应用。 示例代码: // 1. 创建接口和具体实现类 public interface A…...

VSCode的安裝以及使用
c配置: 【MinGw-w64编译器套件】 https://blog.csdn.net/weixin_60915103/article/details/131617196?fromshareblogdetail&sharetypeblogdetail&sharerId131617196&sharereferPC&sharesourcem0_51662391&sharefromfrom_link Python配置: 【簡單ÿ…...

Datawhale 组队学习 Ollama教程 task1
一、Ollama 简介 比喻:Ollama 就像是一个“魔法箱子”,里面装满了各种大型语言模型(LLM)。你不需要懂复杂的魔法咒语(配置),只需要轻轻一按(一条命令),就能让…...

前端技术学习——ES6核心基础
1、ES6与JavaScript之间的关系 ES6是JavaScript的一个版本:JavaScript是基于ECMAScript规范实现的编程语言,而ES6(ECMAScript 2015)是该规范的一个具体版本。 2、ES6的基础功能 (1)let和const let用于声明…...

《DeepSeek技术应用与赋能运营商办公提效案例实操落地课程》
大模型算法实战专家—周红伟老师 法国科学院数据算法博士/曾任阿里巴巴人工智能专家/曾任马上消费企业风控负责人 课程背景 随着大模型技术的迅猛发展,企业面临着提升工作效率、降低运营成本和优化资源配置的巨大压力。DeepSeek做出十三项革命性的大模型技术突破…...

STM32-知识
一、Cortex-M系列双指针 Cortex-M系列的MSP与PSP有一些重要的区别,双指针是为了保证OS的安全性和稳健性。本质上,区别于用户程序使用PSP,操作系统和异常事件单独使用一个MSP指针的目的,是为了保证栈数据不会被用户程序意外访问或…...

线程同步(互斥锁与条件变量)
文章目录 1、为什么要用互斥锁2、互斥锁怎么用3、为什么要用条件变量4、互斥锁和条件变量如何配合使用5、互斥锁和条件变量的常见用法 参考资料:https://blog.csdn.net/m0_53539646/article/details/115509348 1、为什么要用互斥锁 为了使各线程能够有序地访问公共…...

Ubuntu指令学习(个人记录、偶尔更新)
Ubuntu指令学习 0、一点常用指令列表一、Ubuntu下复制与移动,cp/mv二、Ubuntu下echo 与 重定向>,>>三、Ubuntu下chmod,用户权限四、Ubuntu下的tar打包,gzip压缩五、Ubuntu(22.04)下系统语言为中文,切换主目录文件名为英文。六、Ubun…...

Visual Studio 进行单元测试【入门】
摘要:在软件开发中,单元测试是一种重要的实践,通过验证代码的正确性,帮助开发者提高代码质量。本文将介绍如何在VisualStudio中进行单元测试,包括创建测试项目、编写测试代码、运行测试以及查看结果。 1. 什么是单元测…...

【经验分享】Linux 系统安装后内核参数优化
在 Linux 系统安装后,进行内核优化有助于提升系统的性能、稳定性和安全性。以下是一些常见的内核优化操作: 修改/etc/sysctl.conf 文件 执行sysctl -p使配置生效。 kernel.shmmax 135185569792 kernel.shmall 4294967296 fs.aio-max-nr 3145728 fs.fi…...

linux统计文件夹下有多少个.rst文件行数小于4行
您可以使用 find 命令结合 wc 命令来统计文件夹下 .rst 文件行数小于 4 行的文件数量。以下是一个具体的命令示例: find /path/to/directory -name "*.rst" -type f -exec wc -l {} | awk $1 < 4 | wc -l解释: find /path/to/directory …...

使用开源项目xxl-cache构建多级缓存
xxl-cache简介 官网地址:https://www.xuxueli.com/xxl-cache/ 概述 XXL-CACHE 是一个 多级缓存框架,高效组合本地缓存和分布式缓存(RedisCaffeine),支持“多级缓存、一致性保障、TTL、Category隔离、防穿透”等能力;拥有“高性…...

LVDS接口总结--(5)IDELAY3仿真
仿真参考资料如下: https://zhuanlan.zhihu.com/p/386057087 timescale 1 ns/1 ps module tb_idelay3_ctrl();parameter REF_CLK 2.5 ; // 400MHzparameter DIN_CLK 3.3 ; // 300MHzreg ref_clk ;reg …...

Vue3(1)
一.create-vue // new Vue() 创建一个应用实例 > createApp() // createRouter() createStore() // 将创建实例进行了封装,保证每个实例的独立封闭性import { createApp } from vue import App from ./App.vue// mount 设置挂载点 #app (id为app的盒子) createA…...

玩转适配器模式
文章目录 解决方案现实的举例适用场景实现方式适配器模式优缺点优点:缺点:适配器模式可比上一篇的工厂模式好理解多了,工厂模式要具有抽象的思维。这个适配器模式,正如字面意思,就是要去适配某一件物品。 假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格…...

2.11寒假作业
web:[SWPUCTF 2022 新生赛]js_sign 打开环境是这样的,随便输入进行看看 提示错误,看源码其中的js代码 这个代码很容易理解,要让输入的内容等于对应的字符串,显然直接复制粘贴是错的 这串字符看起来像是base64加密&…...