当前位置: 首页 > news >正文

C++ 智能指针的原理:auto_ptr、unique_ptr、shared_ptr、weak_ptr

目录

  • 一、理解智能指针
    • 1.普通指针的使用
  • 二、智能指针
    • 1.auto_ptr
    • 2.unique_ptr
    • 3.shared_ptr
      • (1)了解shared_ptr
      • (2)shared_ptr的缺陷
    • 4.weak_ptr

本文代码在win10的vs2019中通过编译。

一、理解智能指针

1.普通指针的使用

  如果程序需要动态申请内存空间,我们通常使用malloc或者new从堆上申请空间,从堆上申请的空间是需要释放的,否则就会造成内存泄漏。

  不同的申请方式也对应着不同的释放方式,malloc申请的空间通过free释放,new申请的空间通过delete释放。

  正常来讲,如果我们从堆上申请了空间,那么在空间使用完毕后就需要将空间释放,但是如果程序在运行时出错,直接崩溃,那么我们的空间就来不及释放,从而造成资源泄露。

  下面这段代码因为试图对空指针解引用,因此会崩溃,所以ptr指向的空间就没有释放,造成资源泄露:

#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;int main() {	int* ptr = new int;//ptr指针申请了空间int* pn = nullptr;//pn指针指向空cout << *pn;//对空指针解引用,造成崩溃delete ptr;//释放ptr申请的空间(因为程序崩溃,无法运行到这里)
}

  因此,如果要避免这种情况,就需要在所有可能会中途退出的位置之前释放资源,或者是使用异常来解决。但这样的解决办法会让代码变得复杂、庞大。

  因此我们需要一种智能的指针,让指针使用完毕后自动释放申请的空间,也就是智能指针。

  我们可以自己来实现一个简单的智能指针,当然会有很多缺陷,但主要是了解一下思想。想一想类的构造函数和析构函数,生成一个类的对象时,调用构造方法,销毁类的对象时,调用析构方法。因此我们可以在构造函数中传入空间地址,在析构函数中释放资源,这样就可以把一个类的对象当指针一样使用了。

  看看下面这段代码:

#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;template<typename T>
class SmartPtr {
public:T* ptr;//指针
public:SmartPtr(T* _p = nullptr):ptr(_p){}~SmartPtr() {if (ptr != nullptr) {delete ptr;//释放空间}}T& operator*() {return *ptr;}T* operator->() {return ptr;}
};struct Num {int a;int b;
};void Test() {//new会从堆上申请空间并返回空间首地址,将空间首地址作为参数传递SmartPtr<int> s1(new int);*s1 = 10;//通过重载的*运算符修改空间中的值SmartPtr<Num> s2(new Num);s2->a = 11;//通过->运算符直接访问结构体对象的成员变量s2->b = 12;
}int main() {Test();_CrtDumpMemoryLeaks();//检测内存泄漏
}

  但是这个简单版本的智能指针有一个很大的缺点,浅拷贝。编译器生成的默认拷贝构造函数和赋值运算符重载都是浅拷贝,如果通过拷贝构造和赋值运算符重载来给其他对象赋值,当对象生命周期结束调用析构函数的时候,就会因为多次释放同一个资源造成程序崩溃。

  但是不能因为要解决浅拷贝的问腿,就自己定义深拷贝的拷贝构造函数和赋值运算符重载,因为智能指针是把对象当指针一样使用,指针只负责管理资源。如果我们进行深拷贝,那么多个对象就不能使用同一份资源了。

二、智能指针

  为了解决浅拷贝的问腿,来介绍C++中的智能指针,这里介绍四种:auto_ptr(不推荐使用,仅作了解)、unique_ptr(直接禁止拷贝)、shared_ptr、weak_ptr。

1.auto_ptr

  解决浅拷贝的方式:资源转移,如果要用b给a赋值(或者是拷贝构造),就直接把b的资源转移给a,然后让b指向空。(所以b就不能使用这个资源了)

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <memory>
using namespace std;void Test() {auto_ptr<int> ap1(new int);*ap1 = 10;auto_ptr<int> ap2(ap1);//用ap1拷贝构造ap2*ap2 = 12;
}int main() {Test();_CrtDumpMemoryLeaks();//检测内存泄漏
}

  来看看auto_ptr的缺陷:如图所示,用ap1拷贝构造ap2后,ap1的指针就置为空了(因为资源从ap1转移给ap2了,ap1以后就不能使用这个资源了)。因此不推荐使用,不然很可能在不知情的情况下就去对ap1解引用了。

资源转移

2.unique_ptr

  解决浅拷贝的方式:禁止拷贝,只要不能拷贝,就不会有浅拷贝发生。(从根源解决问题)

  如果确定这个资源只会被一个指针管理,就使用unique_ptr。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <memory>
using namespace std;void Test() {unique_ptr<int> up1(new int);*up1 = 10;//unique_ptr<int> up2(up1); 用up1拷贝构造up2会报错//unique_ptr<int> up3 = up1; 用up1给up2赋值会报错
}int main() {Test();_CrtDumpMemoryLeaks();//检测内存泄漏
}

3.shared_ptr

(1)了解shared_ptr

  解决浅拷贝的方式:引用计数。通过计数来判断当前有多少个shared_ptr指向了这个被管理的资源,当计数变成0,说明此时没有指针管理这个资源,因此可以释放这个资源。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <memory>
using namespace std;void Test() {shared_ptr<int> sp1(new int);*sp1 = 10;shared_ptr<int> sp2(sp1);*sp2 = 11;*sp1 = 12;cout << "sp1管理的资源同时被 " << sp1.use_count() << " 个智能指针管理" << endl;
}int main() {Test();_CrtDumpMemoryLeaks();//检测内存泄漏
}

  如图所示:sp1和sp2同时管理资源,资源的地址是一样的,说明两个指针管理的是同一个资源。

证明

  shared_ptr中有两个变量,一个变量(ptr)指向资源的空间首地址,另一个变量(pcount)指向引用计数的空间。引用计数空间的值初始状态是1,每当有一个智能指针管理了这个资源,就会将这个资源对应的引用计数加1,每当有一个智能指针不再管理这个资源,就会把引用计数减1,当引用计数变成0,说明已经没有指针管理这个资源,因此就可以释放这个资源了。

(2)shared_ptr的缺陷

  shared_ptr有一个很大的缺陷:循环引用

  如下代码,定义了链表结点结构体 struct ListNode,该结构体包含了三个成员变量,value用来保存该结点的值,next用来指向下一个链表结点,prev用来指向前一个链表结点。next和prev都是shared_ptr类型的智能指针变量。

  在函数Testshared()中,使用智能指针shared_ptr管理ListNode对象,然后更新它们的指针指向,让两个链表结点连接起来。

  这份代码运行完毕以后会造成资源泄露。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <memory>
using namespace std;struct ListNode {
public:shared_ptr<ListNode> next;//next指向下一个链表结点shared_ptr<ListNode> prev;//prev指向前一个链表结点int value;
public:ListNode(const int data=0) :next(nullptr),prev(nullptr),value(data){}~ListNode() {cout << "析构函数" << endl;}
};void Testshared() {shared_ptr<ListNode> sp1(new ListNode(4));shared_ptr<ListNode> sp2(new ListNode(5));sp1->next = sp2;sp2->prev = sp1;
}int main() {Testshared();_CrtDumpMemoryLeaks();//检测内存泄漏
}

  来看看为什么会造成资源泄露。

  下图是sp1和sp2刚构建好的样子,智能指针内部的ptr指针指向链表结点(资源)的首地址,pcount指针指向存储引用计数的空间。

  刚创建出来sp1和sp2时,由于每个链表结点都只被一个智能指针管理,因此引用计数都是1。

  下图是 sp1->next = sp2; sp2->prev = sp1; 执行后的情况,因为ListNode结构体内部的next和prev是用shared_ptr实现的,因此next和prev也具有指针ptr和引用计数pcount。

  由于ListNode2被ListNode1的next指向,也就代表着多了一个指针管理ListNode2,因此ListNode2的引用计数需要加1。

  ListNode1被ListNode2的prev指向,代表着多了一个指针管理ListNode1,因此ListNode1的引用计数需要加1。

  当Testshared函数执行完毕,作为局部对象的sp1和sp2就需要被销毁,后创建的先销毁,因此首先销毁sp2。

  sp2的ptr与ListNode2断开连接,然后会将引用计数空间的值减1,引用计数变成1,代表还有1个指针在管理ListNode2,因此不能释放ListNode2的资源。

  然后销毁sp1,sp1同样先断开与ListNode1的联系,然后将引用计数空间的值减1,引用计数变成1,代表还有1个指针在管理ListNode1,因此不能释放ListNode1的资源。

  然后现在就尴尬了,如果要释放ListNode1,就要先把ListNode1的引用计数置为0,因此需要让ListNode2的prev不要指向ListNode1,而ListNode2的prev是ListNode2的成员变量,因此就必须要释放ListNode2才能让prev断开与ListNode1的联系。

  但是要释放ListNode2就需要把ListNode2的引用计数置为0 ……

  这样就成套娃了,因此直到最后也没有释放资源,析构函数没有调用,造成了内存泄漏。

  如下图:

4.weak_ptr

  weak_ptr无法单独管理资源。

  weak_ptr就是用来解决shared_ptr的循环引用的,将weak_ptr和shared_ptr搭配使用,就可以解决循环引用的问题。

  在上面介绍shared_ptr的时候,为了方便理解,因此直接说的是引用计数。实际上shared_ptr的引用计数有两个:_Uses 和 _Weaks。(两个值初始值都是1)

  一个资源每当被一个shared_ptr智能指针管理,那么_Uses加1。一个资源每当被weak_ptr智能指针管理,_Weaks加1。

  因此_Uses 和 _Weaks就表示了这个资源被多少个weak_ptr智能指针或者多少个shared_ptr智能指针管理。

  当引用计数空间的 _Uses 变成0,说明被管理的资源可以释放了。当_Uses 和 _Weaks都变成0,说明引用计数空间可以被释放了。

  将前一段代码进行改造,将next和prev改为weak_ptr智能指针,同时要去掉构造函数中的相关内容。因为weak_ptr不能单独管理资源,就连空指针都不允许。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <memory>
using namespace std;struct ListNode {
public:weak_ptr<ListNode> next;//next指向下一个链表结点weak_ptr<ListNode> prev;//prev指向前一个链表结点int value;
public:ListNode(const int data = 0): value(data){}~ListNode() {cout << "析构函数" << endl;}
};void Testshared() {shared_ptr<ListNode> sp1(new ListNode(4));shared_ptr<ListNode> sp2(new ListNode(5));sp1->next = sp2;sp2->prev = sp1;
}int main() {Testshared();_CrtDumpMemoryLeaks();//检测内存泄漏
}

  通过图片来理解改造后的代码,下图是刚创建出来sp1和sp2的情况。sp2和sp1指向的引用计数空间中有两个值:_Uses 和 _Weaks。(初始状态都是1)

初始

  当 sp1->next = sp2; sp2->prev = sp1; 运行后,ListNode1的next指向了ListNode2,ListNode2的prev指向了ListNode1。

  ListNode1的next指向ListNode2,因此next中的_Ptr指向ListNode2的空间首地址,_Rep指向ListNode2的引用计数空间。另一方同理。

  因为next和prev是weak_ptr智能指针,因此两方的引用计数空间的_Weaks都要加1,_Weaks变成2。

  如图:

中间

  当Testshared函数执行完毕,作为局部变量的sp1和sp2就要被销毁,后创建的先销毁,因此先销毁sp2。

  首先断开sp2的_Ptr和ListNode2的联系,因此该链表结点对应的引用计数空间的_Uses要减1,_Uses变成0,说明可以释放ListNode2了,因此需要把ListNode2的prev与ListNode1断开联系,因此ListNode1对应的引用计数空间的_Weaks需要减1,_Weaks变成1。

  把ListNode2的资源处理干净后,就需要把ListNode2对应的引用计数空间的_Weaks减1,然后sp2就可以释放了。ListNode2对应的引用计数空间的_Weaks变成1,说明该链表结点对应的引用计数空间还不能释放。
销毁sp2
  释放了sp2后就需要释放sp1了,首先断开sp1的_Ptr和LsitNode1的联系,这样该链表结点对应的引用计数空间的_Uses就需要减1,_Uses变成0,说明ListNode1的资源可以释放了,这样就需要断开next和其他结点的联系,然后ListNode2对应的引用计数空间的_Weaks就需要减1,_Weaks变成0,说明引用计数空间可以释放了。

  ListNode1的资源处理后,ListNode1对应的引用计数空间中的_Weaks就需要减1,然后_Weaks变成0,说明这个引用计数空间可以被释放了。

  析构函数此时也被正常调用了,shared_ptr的循环引用被解决了。
最后

相关文章:

C++ 智能指针的原理:auto_ptr、unique_ptr、shared_ptr、weak_ptr

目录一、理解智能指针1.普通指针的使用二、智能指针1.auto_ptr2.unique_ptr3.shared_ptr&#xff08;1&#xff09;了解shared_ptr&#xff08;2&#xff09;shared_ptr的缺陷4.weak_ptr本文代码在win10的vs2019中通过编译。 一、理解智能指针 1.普通指针的使用 如果程序需要…...

二叉树前中后层次遍历,递归实现

文章目录前序遍历代码\Python代码\C中序遍历代码\Python代码\C后序遍历代码\Python代码\C层序遍历代码\Python代码\C反向层序遍历代码\Python代码\C总结前序遍历 题目链接   前序遍历意思就是按照“根节点-左子树-右子树”的顺序来遍历二叉树&#xff0c;通过递归方法来实现…...

【RA4M2系列开发板GPIO体验2按键控制LED】

【RA4M2系列开发板GPIO体验2按键控制LED】1. 前言2. 配置工程2.1 新建FSP项目2.2 硬件连接以及FSP配置2.2.1 硬件连接2.2.2 FSP配置3. 软件实现3.1 实现的功能3.2 FreeRTOS使用3.2.1 Stack分配函数3.2.2 LED任务3.2.3 Key任务3.3 程序设计3.3.1 设置输出hex文件3.3.2 编译3.3.3…...

初步介绍CUDA中的统一内存

初步介绍CUDA中的统一内存 更多精彩内容: https://www.nvidia.cn/gtc-global/?ncidref-dev-876561 文章目录初步介绍CUDA中的统一内存为此&#xff0c;我向您介绍了统一内存&#xff0c;它可以非常轻松地分配和访问可由系统中任何处理器、CPU 或 GPU 上运行的代码使用的数据。…...

UVM实战--加法器

前言 这里以UVM实战&#xff08;张强&#xff09;第二章为基础修改原有的DUT&#xff0c;将DUT修改为加法器&#xff0c;从而修改代码以使得更加深入的了解各个组件的类型和使用。 一. 组件的基本框架 和第二章的平台的主要区别点 &#xff08;1&#xff09;有两个transactio…...

Linux系统点亮LED

目录应用层操控硬件的两种方式sysfs 文件系统sysfs 与/sys总结标准接口与非标准接口LED 硬件控制方式编写LED 应用程序在开发板上测试对于一款学习型开发板来说&#xff0c;永远都绕不开LED 这个小小的设备&#xff0c;基本上每块板子都至少会有一颗 LED 小灯&#xff0c;对于我…...

在superset中快速制作报表或仪表盘

在中小型企业&#xff0c;当下需要快速迭代、快速了解运营效果的业务&#xff0c;急需一款开源、好用、能快速迭代生产的报表系统。 老板很关心&#xff0c;BI工程师很关心&#xff0c;同时系统开发人员也同样关心&#xff0c;一个好的技术选型往往能够帮助公司减少很多成本&a…...

【可视化实战】Python 绘制出来的数据大屏真的太惊艳了

今天我们在进行一个Python数据可视化的实战练习&#xff0c;用到的模块叫做Panel&#xff0c;我们通过调用此模块来绘制动态可交互的图表以及数据大屏的制作。 而本地需要用到的数据集&#xff0c;可在kaggle上面获取 https://www.kaggle.com/datasets/rtatman/188-million-us…...

Obsidium一键编码作业,Obsidia惊人属性

Obsidium一键编码作业,Obsidia惊人属性 每个区域都包含几个可定制的功能&#xff0c;允许用户确定如何完全执行应用程序的安全性。Obsidia的功能区允许用户存储任何调整或一键编码作业。 Obsidia惊人属性&#xff1a; 代码虚拟化&#xff1a;代码虚拟化允许您转换程序代码的特定…...

约束优化:约束优化的三种序列无约束优化方法

文章目录约束优化&#xff1a;约束优化的三种序列无约束优化方法外点罚函数法L2-罚函数法&#xff1a;非精确算法对于等式约束对于不等式约束L1-罚函数法&#xff1a;精确算法内点罚函数法&#xff1a;障碍函数法等式约束优化问题的拉格朗日函数法&#xff1a;Uzawas Method fo…...

RocketMQ快速入门:消息发送、延迟消息、消费重试

一起学编程&#xff0c;让生活更随和&#xff01; 如果你觉得是个同道中人&#xff0c;欢迎关注博主gzh&#xff1a;【随和的皮蛋桑】。 专注于Java基础、进阶、面试以及计算机基础知识分享&#x1f433;。偶尔认知思考、日常水文&#x1f40c;。 目录1、RocketMQ消息结构1.1…...

FANUC机器人通过KAREL程序实现与PLC位置坐标通信的具体方法示例

FANUC机器人通过KAREL程序实现与PLC位置坐标通信的具体方法示例 在通信IO点位数量足够的情况下,可以使用机器人的IO点传输位置数据,这里以传输机器人的实时位置为例进行说明。 基本流程如下图所示: 基本步骤可参考如下: 首先确认机器人控制柜已经安装了总线通信软件(例如…...

[蓝桥杯 2015 省 B] 移动距离

蓝桥杯 2015 年省赛 B 组 H 题题目描述X 星球居民小区的楼房全是一样的&#xff0c;并且按矩阵样式排列。其楼房的编号为 1,2,3,⋯ 。当排满一行时&#xff0c;从下一行相邻的楼往反方向排号。比如&#xff1a;当小区排号宽度为 6 时&#xff0c;开始情形如下&#xff1a;我们的…...

Pandas库入门仅需10分钟

数据处理的时候经常性需要整理出表格&#xff0c;在这里介绍pandas常见使用&#xff0c;目录如下&#xff1a; 数据结构导入导出文件对数据进行操作 – 增加数据&#xff08;创建数据&#xff09; – 删除数据 – 改动数据 – 查找数据 – 常用操作&#xff08;转置&#xff0…...

python的socket通信中,如何设置可以让两台主机通过外网访问?

要让两台主机通过外网进行Socket通信&#xff0c;需要在网络设置和代码实现两个方面进行相应的配置。下面是具体的步骤&#xff1a; 确认网络环境&#xff1a;首先要确保两台主机都能够通过外网访问。可以通过ping命令或者telnet命令来测试两台主机之间是否可以互相访问。 确定…...

检测数据的方法(回顾)

检测数据类型的4种方法typeofinstanceofconstructor{}.toString.call() 检测数据类型的4种方法 typeof 定义 用来检测数据类型的运算符 返回一个字符串&#xff0c;表示操作值的数据类型(7种) number&#xff0c;string&#xff0c;boolean&#xff0c;object&#xff0c;u…...

比特数据结构与算法(第三章_上)栈的概念和实现(力扣:20. 有效的括号)

一、栈&#xff08;stack&#xff09;栈的概念&#xff1a;① 栈是一种特殊的线性表&#xff0c;它只允许在固定的一端进行插入和删除元素的操作。② 进行数据插入的删除和操作的一端&#xff0c;称为栈顶 。另一端则称为 栈底 。③ 栈中的元素遵守后进先出的原则&#xff0c;即…...

JVM13 类的生命周期

1. 概述 在 Java 中数据类型分为基本数据类型和引用数据类型。基本数据类型由虚拟机预先定义&#xff0c;引用数据类型则需要进行类的加载。 按照 Java 虚拟机规范&#xff0c;从 class 文件到加载到内存中的类&#xff0c;到类卸载出内存为止&#xff0c;它的整个生命周期包…...

Docker网络模式解析

目录 前言 一、常用基本命令 &#xff08;一&#xff09;查看网络 &#xff08;二&#xff09;创建网络 &#xff08;三&#xff09;查看网络源数据 &#xff08;四&#xff09;删除网络 二、网络模式 &#xff08;一&#xff09;总体介绍 &#xff08;二&#xff09…...

游山城重庆

山城楼梯多&#xff0c;路都是上坡。 为了赶早上8点从成都到重庆的动车&#xff0c;凌晨5点半就爬起床来&#xff0c;由于昨天喝了咖啡&#xff0c;所以我将尽3点才睡觉&#xff0c;这意味着我只睡了2个多小时。起来简单休息之后&#xff0c;和朋友协商好时间就一起出门了。 …...

入门AJAX——XMLHttpRequest(Post)

一、前言 在上篇文章中&#xff0c;我们已经介绍了 HMLHttpRequest 的GET 请求的基本用法&#xff0c;并基于我提供的接口练习了两个简单的例子。如果你还没有看过第一篇文章&#xff0c;强烈建议你在学习完上篇文章后再学习本篇文章&#xff1a; &#x1f517;入门AJAX——XM…...

光伏功率预测 | BiLSTM多变量单步光伏功率预测(Matlab完整源码和数据)

光伏功率预测 | BiLSTM多变量单步光伏功率预测&#xff08;Matlab完整源码和数据&#xff09; 目录 光伏功率预测 | BiLSTM多变量单步光伏功率预测&#xff08;Matlab完整源码和数据&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 光伏功率预测 | BiLSTM多变…...

【PhysUnits】16.1 完善Var 结构体及其运算(variable.rs)

一、源码 这段代码定义了一个泛型结构体 Var&#xff0c;并为它实现了各种数学运算。 /** 变量结构体 Var* 该结构体泛型参数 T 需满足 Numeric 约束*/use core::ops::{Neg, Add, Sub, Mul}; use crate::constant::Integer; /// 定义 Numeric trait&#xff0c;约束 T 必须实…...

中国首套1公里高分辨率大气湿度指数数据集(2003~2020)

时间分辨率&#xff1a;月空间分辨率&#xff1a;100m - 1km共享方式&#xff1a;开放获取数据大小&#xff1a;34.79 GB数据时间范围&#xff1a;2003-01-01 — 2020-12-31元数据更新时间&#xff1a;2023-07-26 数据集摘要 中国首套1公里高分辨率大气湿度指数数据集&#xf…...

Odoo 19 路线图(新功能)

Odoo 19 路线图(新功能) Odoo 19 路线图是Odoo官方针对下一版本的发布计划&#xff0c;将在自动化、合规性、用户体验、碳排放报告及本地化等领域推出超过16项新功能。本路线图详细阐述了Odoo 19如何在过往版本基础上进一步提升&#xff0c;助力企业优化销售、财务、运营及客户…...

【各种主流消息队列(MQ)对比指南】

主流消息队列对比分析 一、核心指标对比 特性/消息队列RabbitMQKafkaRocketMQActiveMQPulsar协议支持AMQP, MQTT, STOMP自定义协议JMS/自定义协议JMS, AMQP, MQTT, STOMPMQTT, AMQP, STOMP单机吞吐量万级百万级十万级万级百万级延迟微秒级&#xff08;低吞吐&#xff09;毫秒…...

高精度滚珠导轨在医疗设备中的多元应用场景

在医疗行业不断追求高效、精准与安全的今天&#xff0c;医疗设备的性能优化至关重要。每一个精密部件都像是设备这个庞大“生命体”中的细胞&#xff0c;共同维持着设备的稳定运行。滚珠导轨&#xff0c;这一看似不起眼却功能强大的传动元件&#xff0c;正悄然在医疗设备领域发…...

动静态库的使用(Linux下)

1.库 通俗来说&#xff0c;库就是现有的&#xff0c;可复用的代码&#xff0c;例如&#xff1a;在C/C语言编译时&#xff0c;就需要依赖相关的C/C标准库。本质上来说库是一种可执行代码的二进制形式&#xff0c;可以被操作系统载入内存执行。通常我们可以在windows下看到一些后…...

DASCTF

[DASCTF X 0psu3十一月挑战赛&#xff5c;越艰巨越狂热]EzPenetration Tip:数据库里的邮箱key已更改为管理员密码&#xff0c;拿到后可直接登录 打开靶机&#xff0c;用Wappalyzer分析网站&#xff0c;可以看到管理系统是Wordpress&#xff0c;因此可以尝试用WPSSCAN扫描公开…...

Claude vs ChatGPT vs Gemini:功能对比、使用体验、适合人群

随着AI应用全面进入生产力场景&#xff0c;市面上的主流AI对话工具也进入“三国杀”时代&#xff1a; Claude&#xff08;Anthropic&#xff09;&#xff1a;新锐崛起&#xff0c;语言逻辑惊艳&#xff0c;Opus 模型被称为 GPT-4 杀手ChatGPT&#xff08;OpenAI&#xff09;&a…...