[C++] vector入门迭代器失效问题详解

Kevin的技术博客.png

文章目录

  • vector介绍
    • **vector iterator 的使用**
  • `vector`迭代器失效问题
    • 由扩容或改变数据引起的迭代器失效
      • `reserve`的实现(野指针)
      • `insert`实现(迭代器位置意义改变)
        • `insert`修改后失效的迭代器
      • `it`迭代器失效
    • `erase`后的问题
    • 总结:`std::vector` 中的迭代器失效和避免方法
      • **插入操作**
        • **解决方法**
      • **删除操作及解决方法**
    • 一定要注意迭代器的更新!!!
  • 其他问题
    • 依赖名称
      • 模板与依赖名称
      • typename关键字
      • 具体示例分析
    • 类外定义成员函数
    • 类内定义函数模板
      • 函数模板的应用
      • 使用的注意事项
  • **使用memcpy拷贝问题**
    • 问题引出
    • 调试分析
    • 解决措施
  • 理解使用 `vector` 构造动态二维数组
    • 什么是二维数组?
    • 使用 `std::vector` 构造动态二维数组
      • 构造方法
      • 解析
        • 定义二维数组
        • 初始化二维数组
        • 打印二维数组
      • 动态调整大小

vector介绍

使用模版指针作为迭代器的方式使用vector

typedef T* iterator;
typedef const T* const_iterator;

成员变量:

iterator _start = nullptr; // 容器的头
iterator _finish = nullptr; // 容器内最后一个数据
iterator _end_of_storage = nullptr; // 容器的最大容量处
  • _start:通常表示容器的开始位置,即指向容器中第一个元素的指针或迭代器。在某些实现中,这可能不是实际存储数据的地址,而是一个指向存储开始的指针。
  • _finish:通常表示容器中最后一个有效元素的下一个位置。这意味着_finish指向的位置是容器中最后一个元素之后的位置,但它本身并不指向一个有效的元素。在C++的std::vector中,finish可能用来表示容器的结束,但实际使用时应该使用end()成员函数(end()_finish指向相同)。
  • _end_of_storage:表示容器分配的内存的末尾。这通常比_finish要远,因为它包括了容器当前使用的所有元素以及可能预留的额外空间,以便于将来的元素扩展,而不需要重新分配内存。

image.png

iterator begin()
{return _start;
}iterator end()
{return _finish;
}const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}

vector iterator 的使用

iterator的使用接口说明
begin + end (重点)获取第一个数据位置的iterator/const_iterator,获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

image.png
image.png

vector迭代器失效问题

迭代器失效主要是由于 vector 在执行某些操作时会重新分配内存或改变数据的位置,导致原有的迭代器指向的内存地址不再有效。以下是一些常见的会导致迭代器失效的操作:

由扩容或改变数据引起的迭代器失效

reserve的实现(野指针)

例如在模拟实现vector中的reserve时:

void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];memcpy(tmp, _start, size() * sizeof(T));delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}
}

可能出现迭代器失效具体代码为:

_start = tmp;
_finish = tmp + old_size;
_end_of_storage = tmp + n;

内部扩容的时候直接申请一块新的tmp空间,此时如果改为以下:

_start = tmp;
_finish = _start + size();
_end_of_storage = _start + n;

由于size()接口实现如下:

size_t size()
{return _finish - _start;
}

当调用sizeof()接口时,此时里面的_finish还是曾经未使用memcpy(tmp, _start, size() * sizeof(T));时原来的_finish指向的位置,所以此时使用_finish = _start + size();来计算_finish时就会出现迭代器失效的问题。

insert实现(迭代器位置意义改变)

模拟实现insert()时,pos会出现失效问题:

由于数据挪动,已经不是指向2,所以insert以后我们认为迭代器失效,不要访问

iterator insert(iterator pos, const T& x)
{// 扩容if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}

在扩容部分,通过reserve后,此时的pos指向依然是未交换空间前空间中指向的位置。所以在以上代码中使用size_t len = pos - _start来保存交换空间前pos位置距离_start的距离len,在交换后再通过pos = _start + len;将失效的迭代器重新指向正确。

**推荐:每次使用完进行更新(用返回值接受) | ****insert**使用会返回插入后新的数据的位置

图示:
交换前
image.png

交换后
image.png

insert修改后失效的迭代器
int main() {std::vector<int> v = {1, 2, 3, 4, 5};int x;std::cin >> x;auto p = std::find(v.begin(), v.end(), x);if (p != v.end()) {// insert以后p就是失效,不要直接访问,要访问就要更新这个失效的迭代器的值//v.insert(p, 40);//(*p) *= 10;// 插入新元素并更新迭代器p = v.insert(p, 40);// 修改插入位置之后的元素(*(p + 1)) *= 10;}print_vector(v);return 0;
}

v.insert(p, 40);后,p指向的依旧是原来空间的p,所以最好使用p = v.insert(p, 40);,在每一次使用可能修改或者转移新空间的成员函数时都对迭代器进行更新,这样就会避免了迭代器的失效。

it迭代器失效

有以下程序:

vector<int> v{1,2,3,4,5,6}; // 
auto it = v.begin(); v.assign(100, 8); // 改变容器内容,如果内容数量大于原本数量,会扩容,交换空间,迭代器失效while(it != v.end())
{cout<< *it << " " ;++it;
}
cout<<endl;// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
// v.resize(100, 8);
// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
// v.reserve(100);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
// v.insert(v.begin(), 0);
// v.push_back(8);
// 给vector重新赋值,可能会引起底层容量改变
// v.assign(100, 8)

出错原因:

  • 以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。
  • **解决方式:**在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可

erase后的问题

void erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()){*(it - 1) = *it;++it;}--_finish;
}

例如删除pos位置的数据:
image.png
当执行代码逻辑删除,pos后的所有元素向前覆盖,删除后的pos指向依然是之前的位置,只是后面的数据覆盖在了之前pos上数据的位置上:
image.png

注意:

正是因为删除后的pos位置指向的是覆盖后的数据,所以在使用erase的时候需要注意注意迭代问题,也就是说在erase过后注意当前pos指向的位置再决定是否迭代pos。如果直接迭代可能造成数据检查的遗失。

示例:

// 删除所有的偶数
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){it = v.erase(it);}++it;}

上示代码就是滥用迭代器造成迭代器失效的例子,在每一次使用erase后都会进行迭代,如此就会将覆盖在pos位置上的未迭代的数据给跳过,导致了数据的遍历遗失,迭代器失效。

// 删除所有的偶数
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){it = v.erase(it);}else{++it;}
}

通过以上修改即可解决问题。

总结:std::vector 中的迭代器失效和避免方法

插入操作

  • 当向std::vector中插入元素时,如果插入操作导致重新分配内存(即容量不够,需要扩展),所有的迭代器都会失效。
  • 如果插入操作没有导致重新分配内存,则插入点之后的所有迭代器都会失效
解决方法

在插入元素后,更新所有受影响的迭代器

std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin() + 2; // it 指向 vec[2]
vec.insert(it, 10); // 插入后 it 失效,需要重新获取 it
it = vec.begin() + 2; // 更新 it

删除操作及解决方法

当从std::vector中删除元素时,被删除元素之后的所有迭代器都会失效。

std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin() + 2; // it 指向 vec[2]
vec.erase(it); // 删除后 it 失效,需要重新获取 it
it = vec.begin() + 2; // 更新 it

在插入元素后,更新所有受影响的迭代器。

一定要注意迭代器的更新!!!

其他问题

依赖名称

模板与依赖名称

在类模板中,某些名称的解析依赖于模板参数。例如,在vector<T>中,T是一个模板参数,而vector<T>::const_iterator则是依赖于T的名称。这种名称被称为“依赖名称”。

typename关键字

在模板中,编译器在解析依赖名称时可能会产生歧义,特别是在编译器不知道某个依赖名称是类型还是变量的情况下。例如,在vector<T>::const_iterator这个名称中,如果T是一个模板参数,编译器需要知道const_iterator是一个类型而不是一个静态成员变量。

为了解决这种歧义,C++引入了**typename**关键字,用来显式地告诉编译器某个依赖名称是一个类型。

具体示例分析

假设我们有一个模板类,它使用了std::vector。在这个类中,我们需要声明一个const_iterator类型的变量:

template <typename T>
class MyClass {
public:void myFunction() {std::vector<T> v;typename std::vector<T>::const_iterator it = v.begin(); // 使用typename关键字// ... 其他代码 ...}
};

在上面的代码中,如果我们没有使用typename关键字:

std::vector<T>::const_iterator it = v.begin(); // 消除编译器的歧义

编译器会报错,因为在模板的上下文中,编译器无法确定std::vector<T>::const_iterator是一个类型还是一个静态成员变量。为了消除这种歧义,我们需要在类型前面加上typename关键字:

typename std::vector<T>::const_iterator it = v.begin();

这样,编译器就能够正确地解析const_iterator为一个类型。

类外定义成员函数

长的成员函数可以在类外定义,需要重新声明模板参数。
image.png

类内定义函数模板

在C++中,类模板允许我们定义一个通用的类,而这个类可以操作任意类型的数据。此外,类模板的成员函数也可以是模板函数。这使得我们可以编写更加灵活和通用的代码。

// 类模板的成员函数,还可以继续是函数模版
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

函数模板的应用

很多时候会使用一种容器来初始化另一种容器,以此来弥补该种容器在性能上的问题,例如,将list数据用来初始化vector

//template <typename Container> void print_container(const Container& container) 
//是一个函数模板,用于打印任何容器的内容。std::list<int> myList = {10, 20, 30, 40, 50};
print_container(myList);  // 输出:10 20 30 40 50// 使用 std::list 的迭代器范围初始化 MyVector
MyVector<int> myVector(myList.begin(), myList.end());
myVector.print();  // 输出:10 20 30 40 50// 创建一个 std::vector 并初始化
std::vector<int> myVec = {1, 2, 3, 4, 5};
print_container(myVec);  // 输出:1 2 3 4 5// 使用 std::vector 的迭代器范围初始化 MyVector
MyVector<int> anotherVector(myVec.begin(), myVec.end());
anotherVector.print();  // 输出:1 2 3 4 5

使用的注意事项

注意调用的优先级匹配机制:

// 类模板的成员函数,还可以继续是函数模版
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}vector(size_t n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}
vector<int> v2(v1.begin(), v1.begin() + 3);
vector<int> v3(lt.begin(), lt.end());
vector<string> v4(10, "1111111");
vector<int> v5(10);
vector<int> v6(10, 1);
vector<int> v7(10, 1);

当使用以上函数模板来构造对象的时候,当遇到vector<int> v6(10, 1);vector<int> v7(10, 1);这种构造时编译器会对进入的模板函数产生异常,会优先进入vector(InputIterator first, InputIterator last),当解引用int类型的时候程序就会异常。

所以在写函数模板的是需要注意注意构造时的匹配机制,应该写的更准确一些,这样才能避免被不属于该类型构造的构造函数模板调用:

vector(int n, const T& val = T())
{reserve(n);for (int i = 0; i < n; i++){push_back(val);}
}

当有一个更明确的构造函数的时候,当编译vector<int> v6(10, 1);的时候就会进入该函数模板实例化的函数进行构造,正常运行。

使用memcpy拷贝问题

问题引出

以下是push_backresereve的逻辑代码:

void push_back(const T& x)
{// 扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];memcpy(tmp, _start, old_size * sizeof(T));delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}
}

执行以下测试代码:

void test_vector()
{vector<string> v;v.push_back("11111111111111111111");v.push_back("11111111111111111111");v.push_back("11111111111111111111");v.push_back("11111111111111111111");print_container(v);v.push_back("11111111111111111111");print_container(v);
}

程序崩溃:
image.png

调试分析

前四个stringpush_back正常执行,当调试到第五个string时:

  • 此时tmp空间已经申请成功

image.png

  • 当执行完delete后,发生异常

image.png
image.png

_start被delete释放空间后,监视到tmp空间也被释放,由此可得,_start与tmp可能指向同一块空间

image.png
image.png
有原始视图_Ptr地址观察可得,在memcpy时,执行的是浅拷贝,会直接令tmp指向_start的那块空间,所以才会导致执行delete[],调用析构函数,将vector中存放的string数据全部析构,程序崩溃,_start指向的空间被销毁,tmp也就没有数据了。

解决措施

该问题由memcpy的浅拷贝引出,所以需要手动进行深拷贝来解决空间释放问题:

void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];// 避免memcpy的浅拷贝问题for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}
}

image.png

当使用深拷贝进行拷贝数据后,就不会出问题了

注意:在涉及空间扩容时用深拷贝进行,避免空间的重复指向。(深拷贝的数据类型都不行:vector<string>,vector<vector<string>>…)

理解使用 vector 构造动态二维数组

什么是二维数组?

一个二维数组可以被看作是一个数组的数组。例如,一个 3x3 的二维数组可以表示为:

1 2 3
4 5 6
7 8 9

使用 std::vector 构造动态二维数组

std::vector 是C++标准模板库(STL)中的一个动态数组类模板。与静态数组不同,std::vector 可以在运行时动态调整其大小。我们可以使用 std::vector 来构造一个动态的二维数组。

构造方法

#include <iostream>
#include <vector>int main() {int m = 3; // 行数int n = 4; // 列数// 创建一个 m 行 n 列的二维数组std::vector<std::vector<int>> matrix(m, std::vector<int>(n));// 初始化数组int value = 1;for (int i = 0; i < m; ++i) {for (int j = 0; j < n; ++j) {matrix[i][j] = value++;}}// 打印数组for (const auto& row : matrix) {for (int elem : row) {std::cout << elem << " ";}std::cout << std::endl;}return 0;
}

解析

定义二维数组
std::vector<std::vector<int>> matrix(m, std::vector<int>(n));
  • std::vector<int>(n) 创建了一个包含 nint 元素的向量。
  • std::vector<std::vector<int>> matrix(m, ...) 创建了一个包含 m 个向量的向量,即一个 m x n 的二维数组。
初始化二维数组
int value = 1;
for (int i = 0; i < m; ++i) {for (int j = 0; j < n; ++j) {matrix[i][j] = value++;}
}

使用双重循环遍历二维数组,并将每个元素初始化为一个递增的值。

打印二维数组
for (const auto& row : matrix) {for (int elem : row) {std::cout << elem << " ";}std::cout << std::endl;
}

使用范围 for 循环遍历并打印二维数组的内容。

动态调整大小

使用 std::vector 构造的二维数组可以在运行时动态调整大小。我们可以使用 resize 方法调整二维数组的行和列。例如,增加行和列:

// 增加行
matrix.resize(new_m);// 增加列
for (auto& row : matrix) {row.resize(new_n);
}

范围forrow就是一维数组,然后通过改变一维数组中每一个对应的二维空间的大小来改变列的大小。

使用 std::vector 构造动态二维数组为我们提供了极大的灵活性。与静态数组不同,std::vector 可以在运行时动态调整大小,使其更适合处理动态数据集。


image.png

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

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

相关文章

MyBatis-Plus的基本使用(一)

目录 前言 特性 MyBatis-Plus入门案例 常用注解 小结 前言 这篇文章主要来学习MyBatis-Plus这个非常强大的框架. 在学习MyBatis-Plus之前,需要有MyBatis的学习基础.因为MyBatis -Plus 是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#x…

【Java Bean 映射器】通过 MapStruct 和 BeanUtils 拷贝对象的区别

目录 &#x1f44b;前言 &#x1f440;一、环境准备 &#x1f331;二、拷贝工具使用 2.1 BeanUtils 使用 2.2 MapStruct 使用 &#x1f49e;️三、对比 &#x1f4eb;四、章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;最近在一些技术文章中看到了开发时经常接触的对…

面向对象·回顾;万类之祖object;抽象类Abstract。

回顾面向对象 类与对象 类--------&#xff08;instance实例化对象&#xff09;-------->对象 类图 调出你public方法–接口 访问控制符 常用private&#xff0c;public。 封装 可见性本类包不同包private✓✕✕不写dafalt(默认)✓✓✕protected✓✓继承✓public✓✓✓…

【计算机网络】RIP路由协议实验

一&#xff1a;实验目的 1&#xff1a;掌握在路由器上配置RIPv2。 二&#xff1a;实验仪器设备及软件 硬件&#xff1a;RCMS交换机、网线、内网网卡接口、Windows 2019操作系统的计算机等。具体为&#xff1a;三层交换机1台、路由器2台。 软件&#xff1a;wireshark软件、记…

01-调试开发k8s

使用 Docker 构建 Kubernete 官方 release 是使用 Docker 容器构建的。要使用 Docker 构建 Kubernetes&#xff0c;请遵循以下说明: Requirements docker Key scripts 以下脚本位于 build/ 目录中。请注意&#xff0c;所有脚本都必须从 Kubernetes 根目录运行 build/run.…

【科研绘图】记录一次论文结果复现

复现原论文中的图片是科研的基本功之一&#xff0c;它不仅验证了研究结果的可靠性&#xff0c;确保了科学工作的准确性和可重复性&#xff0c;还深刻地评估了方法的有效性&#xff0c;体现了对原始研究的尊重和对科学过程的严谨态度。这个过程不仅提高了研究的透明度&#xff0…

记忆注意力用于多模态情感计算!

记忆注意力用于多模态情感计算&#xff01; 目录 情感计算 一、概述 二、研究背景 三、模型结构和代码 六、数据集介绍 七、性能展示 八、复现过程 九、运行过程 模型总结 本文所涉及所有资源均在传知代码平台可获取。 情感计算 近年来&#xff0c;社交媒体的快速扩张推动了用户…

信通院发布!首个大模型混合云标准

近日&#xff0c;中国信通院发布了首个大模型混合云标准&#xff0c;通过定位当前大模型混合云的能力水平&#xff0c;为基于混合云的大模型服务实践提供指引&#xff0c;并明确未来提升方向。同时&#xff0c;中国信通院基于标准展开大模型混合云能力成熟度专项测试&#xff0…

智能家居全在手机端进行控制,未来已来!

未来触手可及&#xff1a;智能家居&#xff0c;手机端的全控时代 艾斯视觉的观点是&#xff1a;在不远的将来&#xff0c;家&#xff0c;这个温馨的港湾&#xff0c;将不再只是我们休憩的场所&#xff0c;而是科技与智慧的结晶。想象一下&#xff0c;只需轻触手机屏幕&#xf…

VMware 上的 Debian Linux 虚拟机无法听到蓝牙耳机的声音解决方案

项目场景&#xff1a; 在Debian上安装QQ音乐&#xff0c;用来摸鱼 问题描述 在安装完QQ音乐后&#xff0c;发现虚拟机无法听到声音&#xff0c;音乐有在正常播放&#xff0c;但是蓝牙耳机没有听到任何声音&#xff1a; 原因分析&#xff1a; 感觉是虚拟机的声卡没有配置&…

WiFi通信——STM32通过ESP8266-01S与阿里云通信

嵌入式设计中常用的无线通信方式主要由蓝牙、WiFi、Zigbee、Lora、NB-IOT等等。这些是最常用的&#xff0c;也是在实际项目开发中根据项目的数据通信特点来选择相应的无线通信方式。本设计主要是讲解WiFi在嵌入式开发中的使用。 1.ESP8266的三种模式 1.1 STA模式(Station) 工…

FPGA开发——独立仿真和联合仿真

一、概述 我们在进行FPGA开发的过程之中&#xff0c;大部分情况下都是在进行仿真&#xff0c;从而验证代码实现结果的正确与否&#xff0c;这里我们引入了独立仿真和联合仿真进行一个简单介绍。 联合仿真&#xff1a;一般我们在进行仿真之前需要在相应的软件中建立相应的工程…

昇思25天学习打卡营第01天|昇思MindSpore大模型基础j介绍

昇思MindSpore和华为昇思MindSpore大模型学习打卡系列文章&#xff0c;本文仅供参考~ 文章目录 前言一、昇思MindSpore是什么&#xff1f;二、执行流程三、设计理念四、层次结构五、Huawei昇腾AI全栈 前言 随着计算机大模型的不断发展&#xff0c;Ai这门技术也越来越重要&#…

【LeetCode 随笔】C++入门级,详细解答加注释,持续更新中。。。

文章目录 58.【简单】最后一个单词的长度&#x1f31f; &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f388;欢迎踏入我的博客世界&#xff0c;能与您在此邂逅&#xff0c;真是缘分使然&#xff01;&#x1f60a; &#x1f338;愿您在此停留的每一刻&#xff0c;都…

使用vfbox网关实现modbus opc profinet iec61850等协议间的转换

在当今物联网&#xff08;IoT&#xff09;与工业自动化日益融合的时代背景下&#xff0c;协议转换网关作为连接不同设备与系统之间的桥梁&#xff0c;扮演着至关重要的角色。VFBox协议转换网关&#xff0c;作为这一领域内的佼佼者&#xff0c;以其高效、灵活、可靠的性能&#…

学习周报:文献阅读+HEC RAS案例

目录 摘要 Abstract 文献阅读&#xff1a;通过HEC RAS软件为罗马尼亚布加勒斯特市的Dmbovița河水管理的水力模型 文献摘要 讨论|结论 理论知识 边界条件计算 流量计算方式 曼宁公式 (Mannings Equation) 连续性方程 (Continuity Equation) 能量方程 (Energy Equatio…

API资源对象CRD、认识Operator-理论知识和认识Operator-初次上手(2024-07-17)

一、API资源对象CRD Kubernetes 自定义资源定义&#xff08;Custom Resource Definition&#xff0c;简称 CRD&#xff09;是一种强大的 Kubernetes API 扩展机制&#xff0c;允许你定义和创建自己的资源类型&#xff0c;以满足您的应用程序或基础设施需求。 CRD 的核心思想是…

LeetCode 2766题: 重新放置石块(原创)

【题目描述】 给你一个下标从 0 开始的整数数组 nums &#xff0c;表示一些石块的初始位置。再给你两个长度 相等 下标从 0 开始的整数数组 moveFrom 和 moveTo 。 在 moveFrom.length 次操作内&#xff0c;你可以改变石块的位置。在第 i 次操作中&#xff0c;你将位置在 moveF…

基于Pytorch框架的深度学习densenet121神经网络鸟类行为识别分类系统源码

第一步&#xff1a;准备数据 5种鸟类行为数据&#xff1a;self.class_indict ["bowing_status", "grooming", "headdown", "vigilance_status", "walking"] &#xff0c;总共有23790张图片&#xff0c;每个文件夹单独放一…

从零搭建pytorch模型教程(八)实践部分(二)目标检测数据集格式转换

前言 图像目标检测领域有一个非常著名的数据集叫做COCO&#xff0c;基本上现在在目标检测领域发论文&#xff0c;COCO是不可能绕过的Benchmark。因此许多的开源目标检测算法框架都会支持解析COCO数据集格式。通过将其他数据集格式转换成COCO格式可以无痛的使用这些开源框架来训…