C++11中智能指针的使用(shared_ptr、unique_ptr、weak_ptr)

C++11中智能指针的使用(shared_ptr、unique_ptr、weak_ptr)

一、shared_ptr原理

shared_ptr 是另一种智能指针,用于实现多个 shared_ptr 实例共享同一个对象的所有权。它通过内部的控制块(通常是一个包含计数器和指向对象的指针的结构)来管理对象的生命周期。每当一个新的 shared_ptr 被创建并指向对象时,控制块中的计数器就会递增;每当一个 shared_ptr 被销毁或重置时,计数器就会递减。当计数器减至零时,对象被删除。

1.std::shared_ptr的原理

std::shared_ptr 的核心原理是引用计数。它通过一个**控制块(Control Block)**来管理对象的生命周期。控制块记录了以下信息:

1.1 引用计数(Strong Count)

表示当前有多少个 std::shared_ptr 正在管理同一个对象。

当一个 std::shared_ptr 被复制时,引用计数加1。

当一个 std::shared_ptr 被销毁时,引用计数减1。

当引用计数变为0时,对象会被自动释放。

1.2 弱引用计数(Weak Count)

用于支持 std::weak_ptr,表示有多少个 std::weak_ptr 正在观察这个对象。

当弱引用计数变为0时,控制块本身也会被销毁。

1.3 删除器(Deleter)

一个函数对象,用于在对象被销毁时执行清理操作(如释放内存)。

1.4 分配器(Allocator)

用于分配和释放内存。

1.1 控制块的结构

控制块通常是一个独立的结构体,与 std::shared_ptrstd::weak_ptr 共享。它的结构大致如下:

struct ControlBlock {int* ptr;          // 指向被管理的对象unsigned* strong;  // 引用计数(Strong Count)unsigned* weak;    // 弱引用计数(Weak Count)Deleter deleter;   // 删除器
};
1.2 生命周期管理
  • 当创建一个新的 std::shared_ptr 时,控制块会被初始化,引用计数设置为1。

  • 当一个 std::shared_ptr 被复制时,引用计数加1。

  • 当一个 std::shared_ptr 被销毁时,引用计数减1。如果引用计数变为0,控制块会调用删除器来释放对象。

  • 当最后一个 std::weak_ptr 被销毁时,控制块本身也会被销毁。

1.3 引用计数的线程安全性

std::shared_ptr 的引用计数操作是线程安全的。它使用原子操作来保证在多线程环境下引用计数的正确性。

2、std::shared_ptr的用法

2.1 创建 std::shared_ptr
(1)使用 std::make_shared

推荐使用 std::make_shared 来创建 std::shared_ptr,因为它更高效且安全。

#include <iostream>
#include<memory>
using namespace std;int main()
{// 创建一个shared_ptrshared_ptr<int> ptr = make_shared<int>(20);cout << "Value = "<< *ptr << endl;
}

std::make_shared 会同时分配对象和控制块的内存,减少了内存分配的次数。

(2)直接构造

也可以通过构造函数直接创建,但需要小心避免悬挂指针。

 int* ptr2 = new int(10);shared_ptr<int> ptr3(ptr2);
(3)从其他智能指针转换

std::shared_ptr 可以从 std::unique_ptr 或其他 std::shared_ptr 转换而来。

std::shared_ptr<int> ptr3 = std::make_shared<int>(42);
std::shared_ptr<int> ptr4 = ptr3; // 复制构造,引用计数增加
2.2 引用计数机制

std::shared_ptr 的核心是引用计数。当一个 std::shared_ptr 被复制时,引用计数会增加;当一个 std::shared_ptr 被销毁时,引用计数会减少。当引用计数为零时,它所管理的对象会被自动释放。

std::shared_ptr<int> ptr5 = std::make_shared<int>(100);
{std::shared_ptr<int> ptr6 = ptr5; // 引用计数 +1std::cout << "Value: " << *ptr6 << std::endl;std::cout << "ptr5 Use_Count: " << ptr5.use_count() << std::endl;std::cout << "ptr6 Use_Count: " << ptr6.use_count() << std::endl;} // ptr6 超出作用域,引用计数 -1
std::cout << "Value: " << *ptr5 << std::endl; // 仍然可以访问
std::cout << "Use_Count: " << ptr5.use_count() << std::endl;

代码运行结果:
在这里插入图片描述

在C++中通过 std::shared_ptr 的成员函数 use_count() 来获取当前 shared_ptr 的引用计数。这个函数返回一个 std::size_t 类型的值,表示当前有多少个 std::shared_ptr 共享同一个控制块。

2.3 使用自定义删除器

如果需要对对象进行特殊处理(如释放资源或调用特定函数),可以为 std::shared_ptr 提供自定义删除器。

std::shared_ptr<int> ptr7(new int(30), [](int* p) {cout << "Custom Deleter Call" << endl;delete p;});

在这里插入图片描述

程序结束,调用客户自定义的删除器。

2.4 使用 std::weak_ptr 避免循环引用

std::shared_ptr 可能会导致循环引用问题,从而无法正确释放资源。通过 std::weak_ptr 可以解决这个问题。

class B;class A
{
public:shared_ptr<B> m_ptrB;~A() { cout << "A 析构" << endl; };
};class B
{
public:shared_ptr<A> m_ptrA;~B() { cout << "B 析构" << endl; };
};int main()
{shared_ptr<A> ptrA = make_shared<A>();shared_ptr<B> ptrB = make_shared<B>();ptrA->m_ptrB = ptrB;ptrB->m_ptrA = ptrA;
}

上述代码运行时不会调用析构函数。

在这里插入图片描述

解决办法:将B类中的shared_ptr改为weak_ptr后,程序运行结果:

在这里插入图片描述

2.5 其他操作
  • reset():释放当前管理的对象,并可选地绑定到新的对象。

  • use_count():返回当前对象的引用计数。

  • get():返回底层裸指针(不推荐直接使用,仅在必要时)。

std::shared_ptr<int> ptr8 = std::make_shared<int>(200);
std::cout << "Use count: " << ptr8.use_count() << std::endl; // 输出引用计数
ptr8.reset(); // 释放对象

3、注意事项

  1. 循环引用问题

    • 如果两个或多个 std::shared_ptr 相互引用,会导致引用计数永远不会变为0,从而无法释放对象。

    • 使用 std::weak_ptr 可以解决循环引用问题。

  2. 不要直接操作底层指针

    • 尽量避免使用 get() 获取底层指针,因为这可能导致悬挂指针或内存泄漏。

    • 如果需要操作底层指针,建议使用 std::weak_ptr 来确保对象仍然有效。

  3. 性能开销

    • std::shared_ptr 的引用计数操作是线程安全的,但会带来一定的性能开销。

    • 如果不需要线程安全,可以考虑使用 std::unique_ptr

  4. 优先使用 std::make_shared

    std::make_shared 会同时分配对象和控制块的内存,减少了内存分配的次数,性能更好。

4、注意事项

std::shared_ptr 是一种非常强大的智能指针工具,适用于需要共享所有权的场景。它通过引用计数自动管理内存,减少了内存泄漏和悬挂指针的风险。但在使用时需要注意以下几点:

  • 尽量避免循环引用,必要时使用 std::weak_ptr

  • 不要直接操作底层指针,除非绝对必要。

  • 优先使用 std::make_shared 创建 std::shared_ptr,因为它更高效。

二、unique_ptr原理

unique_ptr是独占式的智能指针,每一次只会有一个指针指向其给定的对象。当unique_ptr离开其作用域时,其所指的对象会被自动删除,并且该对象拥有的任何资源都会被释放。

std::unique_ptr 是 C++11 引入的一种智能指针,用于管理动态分配的资源(如通过 new 分配的内存)。它的核心原理是通过独占所有权语义来自动管理资源的生命周期,确保资源在合适的时机被释放,从而避免内存泄漏和野指针问题。

以下是 std::unique_ptr 的工作原理和关键特性:

1. 独占所有权

std::unique_ptr 采用独占所有权机制,即同一时间只能有一个 unique_ptr 指向某个资源。这意味着:

  • 不能复制unique_ptr 不能被复制(即没有拷贝构造函数和拷贝赋值运算符),因为复制会导致多个指针指向同一资源,从而破坏所有权的独占性。

    微信截图_20250315111348

  • 可以移动unique_ptr 支持移动语义(通过移动构造函数和移动赋值运算符),允许将资源的所有权从一个 unique_ptr 转移到另一个 unique_ptr。移动操作会将原指针置为 nullptr,确保资源的唯一所有权。

    微信截图_20250315111447

2. 自动释放资源

std::unique_ptr 在以下情况下会自动释放其管理的资源:

  • 析构函数:当 unique_ptr 被销毁(如超出作用域、对象析构)时,它会调用资源的析构函数(如 delete)来释放资源。

  • 重置:通过调用 reset() 方法,可以手动释放当前资源,并可选择分配新的资源。

    微信截图_20250315111954

3. 定制删除器

std::unique_ptr 允许用户自定义删除器(deleter),这使得它不仅可以管理动态分配的内存,还可以管理其他类型的资源(如文件句柄、网络连接等)。删除器是一个可调用对象,用于定义资源的释放方式。默认情况下,unique_ptr 使用 delete 作为删除器,但用户可以通过模板参数或构造函数传递自定义删除器。

struct FileDeleter
{void operator()(FILE* file){fclose(file);cout << "Custom Deleter called" << endl;}
};unique_ptr<FILE,FileDeleter> file(fopen("1.txt","r"));
file.reset(); // 调用自定义删除器关闭文件

4. 实现原理

std::unique_ptr 的实现基于模板和智能指针的底层机制:

模板参数std::unique_ptr 是一个模板类,通常有两个模板参数:

T:指针指向的类型。

Deleter:删除器类型(默认为 std::default_delete<T>)。

内部存储unique_ptr 内部存储一个裸指针(T*)和一个删除器对象。它通过操作这个裸指针来管理资源,并在需要时调用删除器释放资源。

移动语义unique_ptr 的移动构造函数和移动赋值运算符通过交换内部指针和删除器来实现资源的转移,确保所有权的唯一性。

5. 使用示例

以下是一个简单的 std::unique_ptr 使用示例:

#include <iostream>
#include <memory>struct Foo // object to manage
{Foo() { std::cout << "Foo...\n"; }~Foo() { std::cout << "~Foo...\n"; }
};struct D // deleter
{void operator() (Foo* p){std::cout << "Calling delete for Foo object... \n";delete p;}
};int main()
{std::cout << "Creating new Foo...\n";std::unique_ptr<Foo, D> up(new Foo(), D()); // up owns the Foo pointer (deleter D)std::cout << "Replace owned Foo with a new Foo...\n";up.reset(new Foo());  // calls deleter for the old onestd::cout << "Release and delete the owned Foo...\n";up.reset(nullptr);      
}

代码运行结果:

微信截图_20250315122904

6. 优点

自动管理资源:避免手动调用 delete,减少内存泄漏风险。

独占所有权:确保同一时间只有一个指针管理资源,避免野指针问题。

支持自定义删除器:可以管理除动态内存之外的其他资源。

轻量级std::unique_ptr 通常只有裸指针大小,性能开销极小。

7.缺点

  • 不能复制:由于独占所有权,unique_ptr 不能被复制,这在某些场景下可能需要额外的逻辑来处理。
  • 依赖移动语义:需要 C++11 或更高版本支持,因为其依赖于移动构造函数和移动赋值运算符。

总之,std::unique_ptr 是一种非常强大且轻量级的智能指针,适用于大多数需要管理动态资源的场景。

三、weak_ptr原理

在C++中,weak_ptr是一种智能指针,用于解决shared_ptr的循环引用问题。它不拥有对象的所有权,而是观察shared_ptr管理的对象,避免增加引用计数。以下是weak_ptr的原理和用法详解:

1. weak_ptr的核心原理

不增加引用计数weak_ptr指向由shared_ptr管理的对象,但不会增加其引用计数。当最后一个shared_ptr被销毁时,对象仍会被释放,即使有weak_ptr存在。

观察者模式weak_ptr只是"观察"资源,不控制生命周期。若要访问资源,需临时转换为shared_ptr(通过lock()方法)。

解决循环引用:在相互持有shared_ptr的场景中(如双向链表、父-子对象),使用weak_ptr打破循环,防止内存泄漏.

1.1 std::shared_ptr 的关系
  • std::weak_ptr 是基于 std::shared_ptr 的。它不会增加对象的引用计数,但会与 std::shared_ptr 共享对象的控制块(control block)。控制块中包含了对象的引用计数和弱引用计数。

  • 引用计数(use_count):记录有多少个 std::shared_ptr 指向对象。当引用计数为 0 时,对象会被销毁。

  • 弱引用计数(weak_count):记录有多少个 std::weak_ptr 指向对象。弱引用计数不会影响对象的生命周期,但当对象被销毁时,所有指向该对象的 std::weak_ptr 会失效。

1.2 实现原理
  • 当创建一个 std::weak_ptr 时,它会从一个 std::shared_ptr 或另一个 std::weak_ptr 中获取控制块的指针,并增加弱引用计数。

  • 当使用 std::weak_ptrlock() 方法时,它会检查控制块中的对象是否仍然存在。如果对象存在,它会返回一个指向该对象的 std::shared_ptr,并增加引用计数;如果对象已经被销毁,则返回一个空的 std::shared_ptr

  • 当一个 std::weak_ptr 被销毁时,它会减少弱引用计数。当弱引用计数和引用计数都为 0 时,控制块也会被销毁。

2. 基本用法

2.1** 创建weak_ptr**

必须从shared_ptr或另一个weak_ptr构造.

std::shared_ptr 创建:从 std::shared_ptr 创建:

std::shared_ptr<int> ptr = std::make_shared<int>(10);
std::weak_ptr<int> w_ptr(ptr); // 从shared_ptr中构造

从另一个 std::weak_ptr 创建:

 std::weak_ptr<int> w_ptr2(w_ptr);
2.2** 检查对象是否仍然存在**
  • 使用 expired() 方法检查对象是否已经被销毁:
 if (w_ptr2.expired()){std::cout << "Object has been destroyed." << std::endl; }else{std::cout << "Object still exists." << std::endl;}
2.3获取对象的 std::shared_ptr
  • 使用 lock() 方法获取对象的 std::shared_ptr
 std::shared_ptr<int> shared_from_weak = w_ptr2.lock();if (shared_from_weak) {std::cout << "Accessing object: " << *shared_from_weak << std::endl;}else {std::cout << "Object has been destroyed." << std::endl;}

如果对象仍然存在,lock() 方法会返回一个指向该对象的 std::shared_ptr;如果对象已经被销毁,则返回一个空的 std::shared_ptr

3、std::weak_ptr 的使用场景

3.1解决循环引用问题
  • 在使用 std::shared_ptr 时,可能会出现循环引用的情况,导致对象无法被正确销毁。例如,两个对象互相持有对方的 std::shared_ptr,它们的引用计数永远不会为 0。

  • 通过将其中一个引用改为 std::weak_ptr,可以打破循环引用。例如:

class B;class A {
public:std::shared_ptr<B> b_ptr;
};class B {
public:std::weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b;b->a_ptr = a;return 0;
}

在这个例子中,B 持有 Astd::weak_ptr,避免了循环引用。

3.2观察但不拥有对象

当一个对象需要被多个组件观察,但这些组件不需要拥有对象时,可以使用 std::weak_ptr。例如,一个观察者模式中,观察者可以持有被观察对象的 std::weak_ptr,这样即使观察者仍然存在,也不会阻止被观察对象的销毁。

3.3缓存场景

在某些缓存场景中,可以使用 std::weak_ptr 来存储对对象的弱引用。当对象被销毁时,缓存中的 std::weak_ptr 会自动失效,避免了对已销毁对象的访问。

4、注意事项

  1. 线程安全

    • std::weak_ptr 的操作(如 lock()expired() 等)是线程安全的,因为它们都是通过控制块中的原子操作来实现的。
  2. 生命周期管理

    • 使用 std::weak_ptr 时,需要注意对象的生命周期。虽然 std::weak_ptr 不会阻止对象的销毁,但在访问对象时,必须确保对象仍然存在。
  3. 性能开销

    • std::weak_ptr 的使用会带来一定的性能开销,因为它需要维护弱引用计数。在性能敏感的场景中,需要权衡使用 std::weak_ptr 的利弊。

总之,std::weak_ptr 是一种非常有用的智能指针,它通过与 std::shared_ptr 共享控制块,提供了对对象的弱引用。它可以用于解决循环引用问题、观察对象以及缓存场景等。在使用时,需要注意对象的生命周期和性能开销。

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

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

相关文章

2024年认证杯SPSSPRO杯数学建模B题(第二阶段)神经外科手术的定位与导航全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 B题 神经外科手术的定位与导航 原题再现&#xff1a; 人的大脑结构非常复杂&#xff0c;内部交织密布着神经和血管&#xff0c;所以在大脑内做手术具有非常高的精细和复杂程度。例如神经外科的肿瘤切除手术或血肿清除手术&#xff0c;通常需要…

尝试在软考62天前开始成为软件设计师-信息系统安全

安全属性 保密性:最小授权原则(能干活的最小权限)、防暴露(隐藏)、信息加密、物理保密完整性(防篡改):安全协议、校验码、密码校验、数字签名、公证 可用性:综合保障( IP过滤、业务流控制、路由选择控制、审计跟踪)不可抵赖性:数字签名 对称加密 DES :替换移位 3重DESAESR…

Rocky9.5基于sealos快速部署k8s集群

首先需要下载 Sealos 命令行工具&#xff0c;sealos 是一个简单的 Golang 二进制文件&#xff0c;可以安装在大多数 Linux 操作系统中。 以下是一些基本的安装要求&#xff1a; 每个集群节点应该有不同的主机名。主机名不要带下划线。 所有节点的时间需要同步。 需要在 K8s …

G口服务器和普通服务器之间的区别

今天小编主要来为大家介绍一下G口服务器和普通服务器之间的区别&#xff01; 首先&#xff0c;从硬件配置上看&#xff0c;普通服务器通常都会配备中央处理器、内存和硬盘等基本的硬件配置&#xff0c;能够适用于各种应用程序和服务&#xff1b;G口服务器除了基础的硬件配置还增…

Cursor软件如何刷新机器码流程

一.退出Cursor软件账号 打开Cursor软件&#xff0c;点击设置-->General-->Account-->Log out,现将Cursor软件上登录的账户退出。 二.将Cursor官网上登录的Cursor账户也清空掉 点击头像--> ACCOUNT SETTINGS -->Account-->Advanced-->Delete Account-->…

类与对象(中)(详解)

【本节目标】 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数 5. 赋值运算符重载 6. const成员函数 7. 取地址及const取地址操作符重载 1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&…

开发语言漫谈-groovy

groovy是一门脚本语言&#xff0c;在前期的脚本语言中简单介绍了下。现在再深入介绍下&#xff0c;因为它是本平台上选用的脚本语言。所谓脚本语言就是不用编译&#xff0c;直接执行。这种特色非常适合做嵌入编程&#xff0c;即编即用。我们知道平台后台的业务开发语言是Java&a…

React+Ant Design的Layout布局实现暗黑模式切换

目录 效果预览完整代码我遇到的BUG问题代码BUG1&#xff1a;暗黑模式下内容区不变成深色BUG2&#xff1a;光亮模式下的左右区域是深色 补充知识ConfigProvider是什么&#xff1f;Ant Design中的theme如何使用&#xff1f;theme 配置的常见字段主题算法通过 useToken 获取主题 效…

TCP 三次握手与四次挥手过程

TCP 作为一种面向连接的、可靠的传输层协议&#xff0c;其连接管理机制对于保障数据的可靠传输至关重要。 三次握手&#xff08;建立连接&#xff09; 三次握手是 TCP 建立连接时所采用的机制&#xff0c;其目的在于确保客户端和服务器双方都具备发送和接收数据的能力&#x…

【线程安全的单例模式和STL是否是线程安全/智能指针是否是线程安全】

文章目录 一、单例模式的特点二、饿汉模式实现单例三、懒汉模式实现单例四、STL线程安全吗&#xff1f;五、智能指针线程安全吗&#xff1f; 一、单例模式的特点 一个类&#xff0c;只应该实例化了一个对象&#xff0c;就是单例。 二、饿汉模式实现单例 举个饿汉模式的例子&…

力扣DAY24 | 热100 | 回文链表

前言 简单 √ 是反转链表的衍生题&#xff0c;很快写完了。不过没考虑到恢复链表结构的问题。 题目 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输…

【GL010】C++

1.C中的const关键字有哪些用法&#xff1f; 1.修饰变量&#xff1a;表示变量的值不可修改。 const int a 10; 2.修饰指针&#xff1a; const int* p&#xff1a; // 指针指向的内容不可修改。 int* const p&#xff1a; // 指针本身不可修改。 const int* const…

金融行业 UE/UI 设计:解锁高效体验,重塑行业界面

在数字化浪潮中&#xff0c;金融行业的竞争日益激烈&#xff0c;用户体验&#xff08;UE&#xff09;和用户界面&#xff08;UI&#xff09;设计成为企业脱颖而出的关键。兰亭妙微凭借丰富的经验和创新的方法&#xff0c;为金融行业打造了一套行之有效的 UE/UI 解决方案&#x…

C语言字符函数,字符串函数以及内存函数

那么博主写这一片博客的目的就是为下一篇c的string类做铺垫&#xff0c;那么下面就请期待博主的下一篇文章吧。 目录 1.字符函数 2.字符串函数&#xff08;均在string.h头文件中&#xff09; strlen的使用和模拟实现 strcpy 的使用和模拟实现 strcat 的使用和模拟实现 s…

_DISPATCHER_HEADER结构中的WaitListHead和_KWAIT_BLOCK的关系

第一部分&#xff1a; // // Wait block // // begin_ntddk begin_wdm begin_nthal begin_ntifs begin_ntosp typedef struct _KWAIT_BLOCK { LIST_ENTRY WaitListEntry; struct _KTHREAD *RESTRICTED_POINTER Thread; PVOID Object; struct _KWAIT_BLOCK *R…

flutter 自定义控件RenderObjectWidget使用

CustomWidget的自定义组件的注释还是比较清晰的 参考文档Flutter实战 import package:flutter/cupertino.dart; import package:flutter/gestures.dart; import package:flutter/material.dart; /* * 如果组件不会包含子组件&#xff0c;则我们可以直接继承自 LeafRenderObject…

机器视觉场景应用中,有没有超景深的工业镜头

在机器视觉领域,确实存在具有超景深特性的工业镜头,这类镜头通过特殊的光学设计或技术手段,能够显著扩大清晰成像的纵向范围,从而满足复杂检测场景中对多平面物体清晰成像的需求。以下是相关技术要点及典型镜头类型: 1. 远心镜头 远心镜头是超景深镜头的典型代表,其特点包…

【Linux】同步原理剖析及模拟BlockQueue生产消费模型

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

光流 | 基于KLT算法的人脸检测与跟踪原理及公式,算法改进,matlab代码

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 人脸检测与跟踪 一、KLT算法原理与分析1. 核心思想2. 数学模型二、人脸…

<数据集>轨道异物识别数据集<目标检测>

数据集下载链接&#xff1a;https://download.csdn.net/download/qq_53332949/90527370 数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1659张 标注数量(xml文件个数)&#xff1a;1659 标注数量(txt文件个数)&#xff1a;1659 标注类别数&#xff1a;6 标注类别…