C++参悟:内存管理-unique_ptr
内存管理-unique_ptr
- 一、概述
- 二、成员函数
- 1. 构造、析构函数函数
- 1. 构造函数
- 2. 析构函数
- 3. 赋值号
- 2. 修改器
- 1. release()
- 2. reset()
- 3. swap()
- 3. 观察器
- 1. get()
- 2. get_deleter
- 3. bool 运算
一、概述
std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。相当于一个对象的指针只会被一堆 unique_ptr 中某一个被占用,而且这个对象的指针不能被其他unique_ptr 占用。这个同时还有作用域的限制。
在下列的两个情况下会用调用 unique_ptr 关联的 Deleter 删除器释放对象:
- 销毁了用于管理的 unique_ptr 对象
- 通过 operator= 或 reset() 赋值另一指针给管理的 unique_ptr 对象
通过调用 get_deleter()(ptr) ,用潜在为用户提供的删除器释放对象。默认删除器用 delete 运算符,它销毁定义的unique_ptr 对象并是否这个指针对象的分配内存。
这个的类的定义如下,可以看到,参数有两个,一个是待分配的类型,一个是删除器
template< class T,class Deleter = std::default_delete<T>
> class unique_ptr; template < class T,class Deleter
> class unique_ptr<T[], Deleter>;
unique_ptr 亦可以不占有对象,该情况下称它为空 (empty)。
std::unique_ptr 有两个版本:
-
管理单个对象(例如以 new 分配)
-
管理动态分配的对象数组(例如以 new[] 分配)
注解:
只有非 const 的 unique_ptr 能转移被管理对象的所有权给另一 unique_ptr 。若对象的生存期为 const std::unique_ptr 所管理,则它被限定在创建指针的作用域中。
std::unique_ptr 常用于管理对象的生存期,包含:
- 通过正常退出和经由异常退出两者上的受保证删除,提供异常安全,给处理拥有动态生存期的对象的类和函数
- 传递独占的拥有动态生存期的对象的所有权到函数
- 从函数获得独占的拥有动态生存期对象的所有权
- 作为具移动容器的元素类型,例如保有指向动态分配对象的指针的 std::vector (例如,若想要多态行为)
std::unique_ptr 可为不完整类型 T 构造,例如用于改善用作 pImpl 手法中柄的用途。若使用默认删除器,则 T 必须在代码中调用删除器点处完整,这发生于析构函数、移动赋值运算符和 std::unique_ptr 的 reset 成员函数中。(相反地, std::shared_ptr 不能从指向不完整类型的裸指针构造,但可于 T 不完整处销毁)。注意若 T 是类模板特化,则以 unique_ptr 为运算数的使用,如 !p ,因 ADL 而要求 T 的形参完整。
若 T 是某基类 B 的派生类,则 std::unique_ptr 可隐式转换为 std::unique_ptr。产生的 std::unique_ptr 的默认删除器将使用 B 的 operator delete ,这导致未定义行为,除非 B 的析构函数为虚。注意 std::shared_ptr 表现有别: std::shared_ptr 将使用类型 T 的 operator delete ,而且即使 B 的析构函数非虚,也会正确删除被占有对象。
不同于 std::shared_ptr , std::unique_ptr 可通过任何满足可空指针 (NullablePointer) 的定制柄类型管理对象。例如,这允许管理位于共享内存,但提供定义 typedef boost::offset_ptr pointer; 或其他缀饰指针的 Deleter 的对象。
例子:
#include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
#include <functional>struct B {virtual void bar() { std::cout << "B::bar\n"; }virtual ~B() = default;
};
struct D : B
{D() { std::cout << "D::D\n"; }~D() { std::cout << "D::~D\n"; }void bar() override { std::cout << "D::bar\n"; }
};// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{p->bar();return p;
}void close_file(std::FILE* fp) { std::fclose(fp); }int main()
{std::cout << "unique ownership semantics demo\n";{auto p = std::make_unique<D>(); // p 是占有 D 的 unique_ptrauto q = pass_through(std::move(p)); assert(!p); // 现在 p 不占有任何内容并保有空指针q->bar(); // 而 q 占有 D 对象} // ~D 调用于此std::cout << "Runtime polymorphism demo\n";{std::unique_ptr<B> p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr// 作为指向基类的指针p->bar(); // 虚派发std::vector<std::unique_ptr<B>> v; // unique_ptr 能存储于容器v.push_back(std::make_unique<D>());v.push_back(std::move(p));v.emplace_back(new D);for(auto& p: v) p->bar(); // 虚派发} // ~D called 3 timesstd::cout << "Custom deleter demo\n";std::ofstream("demo.txt") << 'x'; // 准备要读的文件{std::unique_ptr<std::FILE, void (*)(std::FILE*) > fp(std::fopen("demo.txt", "r"),close_file);if(fp) // fopen 可以打开失败;该情况下 fp 保有空指针std::cout << (char)std::fgetc(fp.get()) << '\n';} // fclose() 调用于此,但仅若 FILE* 不是空指针// (即 fopen 成功)std::cout << "Custom lambda-expression deleter demo\n";{std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr){std::cout << "destroying from a custom deleter...\n";delete ptr;}); // p 占有 Dp->bar();} // 调用上述 lambda 并销毁 Dstd::cout << "Array form of unique_ptr demo\n";{std::unique_ptr<D[]> p{new D[3]};} // 调用 ~D 3 次
}
//输出:unique ownership semantics demo
D::D
D::bar
D::bar
D::~D
Runtime polymorphism demo
D::D
D::bar
D::D
D::D
D::bar
D::bar
D::bar
D::~D
D::~D
D::~D
Custom deleter demo
x
Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D
Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D
二、成员函数
1. 构造、析构函数函数
1. 构造函数
#include <iostream>
#include <memory>struct Foo { // 要管理的对象Foo() { std::cout << "Foo ctor\n"; }Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }Foo(Foo&&) { std::cout << "Foo move ctor\n"; }~Foo() { std::cout << "~Foo dtor\n"; }
};struct D { // 删除器D() {};D(const D&) { std::cout << "D copy ctor\n"; }D(D&) { std::cout << "D non-const copy ctor\n";}D(D&&) { std::cout << "D move ctor \n"; }void operator()(Foo* p) const {std::cout << "D is deleting a Foo\n";delete p;};
};int main()
{std::cout << "Example constructor(1)...\n";std::unique_ptr<Foo> up1; // up1 为空std::unique_ptr<Foo> up1b(nullptr); // up1b 为空std::cout << "Example constructor(2)...\n";{std::unique_ptr<Foo> up2(new Foo); // up2 现在占有 Foo} // Foo 被删除std::cout << "Example constructor(3)...\n";D d;{ // 删除器类型不是引用std::unique_ptr<Foo, D> up3(new Foo, d); // 复制删除器}{ // 删除器类型是引用 std::unique_ptr<Foo, D&> up3b(new Foo, d); // up3b 保有到 d 的引用}std::cout << "Example constructor(4)...\n";{ // 删除器不是引用std::unique_ptr<Foo, D> up4(new Foo, D()); // 移动删除器}std::cout << "Example constructor(5)...\n";{std::unique_ptr<Foo> up5a(new Foo);std::unique_ptr<Foo> up5b(std::move(up5a)); // 所有权转移}std::cout << "Example constructor(6)...\n";{std::unique_ptr<Foo, D> up6a(new Foo, d); // 复制 Dstd::unique_ptr<Foo, D> up6b(std::move(up6a)); // 移动 Dstd::unique_ptr<Foo, D&> up6c(new Foo, d); // D 是引用std::unique_ptr<Foo, D> up6d(std::move(up6c)); // 复制 D}#if (__cplusplus < 201703L)std::cout << "Example constructor(7)...\n";{std::auto_ptr<Foo> up7a(new Foo);std::unique_ptr<Foo> up7b(std::move(up7a)); // 所有权转移}
#endifstd::cout << "Example array constructor...\n";{std::unique_ptr<Foo[]> up(new Foo[3]);} // 删除三个 Foo 对象
}
// 输出:Example constructor(1)...
Example constructor(2)...
Foo ctor
~Foo dtor
Example constructor(3)...
Foo ctor
D copy ctor
D is deleting a Foo
~Foo dtor
Foo ctor
D is deleting a Foo
~Foo dtor
Example constructor(4)...
Foo ctor
D move ctor
D is deleting a Foo
~Foo dtor
Example constructor(5)...
Foo ctor
~Foo dtor
Example constructor(6)...
Foo ctor
D copy ctor
D move ctor
Foo ctor
D non-const copy ctor
D is deleting a Foo
~Foo dtor
D is deleting a Foo
~Foo dtor
Example constructor(7)...
Foo ctor
~Foo dtor
Example array constructor...
Foo ctor
Foo ctor
Foo ctor
~Foo dtor
~Foo dtor
~Foo dtor
2. 析构函数
// 下列程序演示定制删除器的用法。#include <iostream>
#include <memory>int main ()
{auto deleter = [](int* ptr){std::cout << "[deleter called]\n";delete ptr;};std::unique_ptr<int,decltype(deleter)> uniq(new int, deleter);std::cout << (uniq ? "not empty\n" : "empty\n");uniq.reset();std::cout << (uniq ? "not empty\n" : "empty\n");
}
//输出:not empty
[deleter called]
empty
3. 赋值号
作为只能被移动的类型,unique_ptr 的赋值运算符只接受右值实参(例如 std::make_unique 的结果或已被 std::move 的 unique_ptr 变量)。
#include <iostream>
#include <memory>struct Foo
{int id;Foo(int id) : id(id) { std::cout << "Foo " << id << '\n'; }~Foo() { std::cout << "~Foo " << id << '\n'; }
};int main()
{std::unique_ptr<Foo> p1(std::make_unique<Foo>(1));{std::cout << "创建新的 Foo...\n";std::unique_ptr<Foo> p2(std::make_unique<Foo>(2));// p1 = p2; // 错误!不能复制 unique_ptrp1 = std::move(p2);std::cout << "准备离开内层块...\n";// 即使 p2 将要离开作用域,但是 Foo 示例会继续生存}std::cout << "准备离开程序...\n";
}
//输出:Foo 1
创建新的 Foo...
Foo 2
~Foo 1
准备离开内层块...
准备离开程序...
~Foo 2
2. 修改器
1. release()
若存在,则释放被管理对象的所有权。调用这个函数后再去 get() 就返回 nullptr 。这个本身也返回的是 nullptr值。
调用方负责删除该对象。
指向被管理对象的指针,或若无被管理对象则为 nullptr ,即调用前 get() 会返回的值。
#include <cassert>
#include <iostream>
#include <memory>struct Foo
{Foo() { std::cout << "Foo\n"; }~Foo() { std::cout << "~Foo\n"; }
};// Foo资源的所有权在调用该函数时被转移
void legacy_api(Foo* owning_foo)
{std::cout << __func__ << '\n';// 没有人能理解或再敢碰的遗留代码// ...delete owning_foo;
}int main()
{std::unique_ptr<Foo> managed_foo(new Foo);legacy_api(managed_foo.release());assert(managed_foo == nullptr);
}
//输出:Foo
legacy_api
~Foo
2. reset()
定义:void reset( pointer ptr = pointer() ) noexcept;
为在提供新删除器时替换被管理对象,可用移动赋值运算符。
不进行自 reset 测试,即 ptr 是否指向已为 *this 管理的对象,除非作为编译器扩展,或调试断言提供。注意如 p.reset(p.release()) 的代码不涉及自重置,只有类似 p.reset(p.get()) 的代码会。
示例
#include <iostream>
#include <memory>struct Foo {Foo() { std::cout << "Foo...\n"; }~Foo() { std::cout << "~Foo...\n"; }
};struct D {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 占有 Foo 指针(删除器 D )std::cout << "Replace owned Foo with a new Foo...\n";up.reset(new Foo()); // 调用旧者的删除器std::cout << "Release and delete the owned Foo...\n";up.reset(nullptr);
}
//输出:Creating new Foo...
Foo...
Replace owned Foo with a new Foo...
Foo...
Calling delete for Foo object...
~Foo...
Release and delete the owned Foo...
Calling delete for Foo object...
~Foo...
3. swap()
void swap(unique_ptr& other) noexcept;
交换 *this 和另一 unique_ptr 对象 other 的被管理对象和关联的删除器。
#include <iostream>
#include <memory>struct Foo {Foo(int _val) : val(_val) { std::cout << "Foo...\n"; }~Foo() { std::cout << "~Foo...\n"; }int val;
};int main()
{std::unique_ptr<Foo> up1(new Foo(1));std::unique_ptr<Foo> up2(new Foo(2));up1.swap(up2);std::cout << "up1->val:" << up1->val << std::endl;std::cout << "up2->val:" << up2->val << std::endl;
}
//输出:Foo...
Foo...
up1->val:2
up2->val:1
~Foo...
~Foo...
3. 观察器
1. get()
返回指向被管理对象的指针,如果无被管理对象,则为 nullptr 。
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <utility>class Res {std::string s;public:Res(std::string arg) : s{ std::move(arg) } {std::cout << "Res::Res(" << std::quoted(s) << ");\n";}~Res() {std::cout << "Res::~Res();\n";}private:friend std::ostream& operator<< (std::ostream& os, Res const& r) {return os << "Res { s = " << std::quoted(r.s) << "; }";}
};int main()
{std::unique_ptr<Res> up(new Res{"Hello, world!"});Res *res = up.get();std::cout << *res << '\n';
}
//输出:Res::Res("Hello, world!");
Res { s = "Hello, world!"; }
Res::~Res();
2. get_deleter
返回会用于析构被管理对象的删除器对象。
#include <iostream>
#include <memory>struct Foo
{Foo() { std::cout << "Foo...\n"; }~Foo() { std::cout << "~Foo...\n"; }
};struct D
{void bar() { std::cout << "Call deleter D::bar()...\n"; }void operator()(Foo* p) const{std::cout << "Call delete for Foo object...\n";delete p;}
};int main()
{std::unique_ptr<Foo, D> up(new Foo(), D());D& del = up.get_deleter();del.bar();
}
//输出:Foo...
Call deleter D::bar()...
Call delete for Foo object...
~Foo...
3. bool 运算
explicit operator bool() const noexcept;
检查 *this 是否占有对象,即是否有 get() != nullptr。若 *this 占有对象则为 true ,否则为 false 。
#include <iostream>
#include <memory>int main()
{std::unique_ptr<int> ptr(new int(42));if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n';ptr.reset();if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\n';
}
//输出:before reset, ptr is: 42
相关文章:
C++参悟:内存管理-unique_ptr
内存管理-unique_ptr 一、概述二、成员函数1. 构造、析构函数函数1. 构造函数2. 析构函数3. 赋值号 2. 修改器1. release()2. reset()3. swap() 3. 观察器1. get()2. get_deleter3. bool 运算 一、概述 std::unique_ptr 是通过指针占有并管理另一对象&a…...

【征稿已开启】第五大数据、人工智能与软件工程国际研讨会(ICBASE 2024)
第五大数据、人工智能与软件工程国际研讨会(ICBASE 2024) 2024 5th International Conference on Big Data & Artificial Intelligence & Software Engineering 2024年09月20-22日 | 中国温州 第五届大数据、人工智能与软件工程国际研讨会&…...

Vue3父子组件传参
一,父子组件传参: 应用场景:父子组件传参 Vue3碎片:defineEmits,defineProps,ref,reactive,onMounted 1.父组件传子组件 a.父组件传参子组件 import { ref} from vue import OnChi…...
SpringBoot整理-微服务
Spring Boot 在构建微服务架构的应用中发挥着关键作用。微服务是一种将大型复杂应用拆分为更小、更容易管理和维护的服务的架构风格。每个服务通常围绕特定的业务功能构建,并且可以独立部署、扩展和更新。Spring Boot 提供了一系列特性和工具,使得创建和维护这些独立服务变得…...

服务器和CDN推荐
简介 陆云Roovps是一家成立于2021年的主机服务商,主要业务是销售美国服务器、香港服务器及国外湖北十堰高防服务器,还有相关CDN产品。( 地址:roovps) 一、相关产品...

c#读取csv文件中的某一列的数据
chat8 (chat779.com) 上面试GPT-3.5,很好的浏览网站,输入问题,可得到答案。 问题1:c#如何在csv中读取某一列数据 解答方案:在 C#中,你可以使用File.ReadAllLines来读取CSV中的所有行,然后逐行解析每一行…...

不懂快团团大团长对接?凭什么快团团的钱轮到你赚?
对接头部快团团大团长,让快团团大团长帮你卖货 分享几个推品的关键词: 1.推品的内容:产品实拍图核心卖点 不要上来就发笔记,你的产品图和文案还没吸引人,就发笔记没有人看。 可以先发你产品的简短卖点和图片ÿ…...

OpenGL 入门(九)—Material(材质)和 光照贴图
文章目录 材质设置材质光的属性脚本实现 光照贴图漫反射贴图高光反射贴图 材质 材质本质是一个数据集,主要功能就是给渲染器提供数据和光照算法。 如果我们想要在OpenGL中模拟多种类型的物体,我们必须针对每种表面定义不同的材质(Material)属性。 我们…...

jmeter-03界面介绍
文章目录 主界面介绍工具栏介绍测试计划介绍线程组介绍线程组——选择测试计划,右键-->添加-->线程-->线程组1.线程数2.准备时长(Ramp-up)3.循环次数4.same user on each iteratio5.调度器 主界面介绍 工具栏介绍 新建测试计划:创建一个空白的测…...
探究 MySQL 中使用 where 1=1 是否存在性能影响
文章目录 前言聊聊 mybatis 中多条件拼接的两种常规写法where 11使用 <where> 标签 性能影响where 11<where> 标签 总结个人简介 前言 最近在项目中使用 mybatis 写 SQL 使用了 where 11 来简化多条件拼接的写法,案例如下,借此聊聊多条件拼…...

VSCode无法启动:Waiting for server log...
问题基本情况 [13:30:20.720] > code 1.86.0 (commit 05047486b6df5eb8d44b2ecd70ea3bdf775fd937) [13:30:20.724] > Running ssh connection command... /var/fpwork/reiss/vscdata/server/cplane/.vscode-server/code-05047486b6df5eb8d44b2ecd70ea3bdf775fd937 comman…...

VMware虚拟机清理瘦身
用了一段时间VMware虚拟机之后,发现内存越来越小,也没装什么软件。。。 1.查询磁盘空间分布 虚拟机中磁盘空间查询 先看一下哪些地方占用的空间大,进行排查。 2.排查VMware复制文件产生的缓存路径 VMware复制文件有一个特点,以…...

Coil:Android上基于Kotlin协程的超级图片加载库
Coil:Android上基于Kotlin协程的超级图片加载库 1. coil简介 在当今移动应用程序的世界中,图片加载是一个不可或缺的功能。为了让应用程序能够高效地加载和显示图片,开发人员需要依赖于强大的图片加载库。而今天,我将向大家介绍…...
时间序列(Time-Series)MultiWaveletCorrelation.py代码解析
#这两行导入了PyTorch和NumPy库,分别用于深度学习和数值计算 import torch import numpy as np #这两行导入了PyTorch的神经网络模块和函数模块。 import torch.nn as nn import torch.nn.functional as F from torch import Tensor from typing import List, Tuple…...
C++的缺省参数和函数重载
目录 1.缺省参数 1.1缺省参数的概念 1.2缺省参数的分类 1.3缺省参数使用场景 2.函数重载 2.1函数重载的概念 2.2构成函数重载 1.缺省参数 1.1缺省参数的概念 概念:缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没…...
nginx upstream server主动健康检测模块ngx_http_upstream_check_module 使用和源码分析(上)
目录 1. 缘起2. 配置指令2.1 check2.2 check_keepalive_requests2.3 check_http_send2.4 check_http_expect_alive2.5 check_shm_size2.6 check_status3. 加载健康检测模块3.1 模块的编译3.2 模块的配置4. 测试验证5. 思考与问题6. 源码分析1. 缘起 众所周知,nginx原生的upst…...

第01课:自动驾驶概述
文章目录 1、无人驾驶行业概述什么是无人驾驶智慧出行大趋势无人驾驶能解决什么问题行业趋势无人驾驶的发展历程探索阶段(2004年以前)发展阶段(2004年-2016年)成熟阶段(2016年以后) 2、无人驾驶技术路径无人…...

Docker进阶篇-CIG重量级监控系统
一、简介 通过docker stats命令可以很方便的查看当前宿主机上所有容器的CPU、内存、网络流量等数 据,可以满足一些小型应用。 但是docker stats统计结果只能是当前宿主机的全部容器,数据资料是实时的,没有地方存储、 没有健康指标过线预警…...

鸿蒙踩坑合集
各位网络中的小伙们,关于鸿蒙的踩坑陆陆续续收集中,本文章会持续更新,希望对您有所帮助 1、预览视图无法正常加载 重新编译项目,点击刷新按钮,控制台提示Build task failed. Open the Run window to view details. 解…...

Golang-Map有序输出——使用orderedmap库实现
前言 工作中遇到一个问题:需要导出一个MySQL表格,表格内容由sql查询得来。但现在发现,所导出的表格中,各列的顺序不确定。多次导出, 每一次的序列顺序也是不定的。 因此确定是后端,Map使用相关导致的问题。…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...

elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...