【C++】指针与智慧的邂逅:C++内存管理的诗意

文章目录

  • RAII
  • 智能指针
    • `auto_ptr`
    • `unique_ptr`
  • shared_ptr
    • 模拟实现
    • 定制删除器
    • 循环引用 和 `weak_ptr`

RAII

RAII(Resource Acquisition Is Initialization)是一种广泛应用于 C++ 等编程语言中的编程范式,它的核心思想是:资源的获取和释放与对象的生命周期绑定。在 RAII 中,资源(如内存、文件句柄、网络连接等)的获取通常发生在对象的构造函数中,而资源的释放则发生在对象的析构函数中。

这种设计模式确保了资源在不再需要时自动释放,从而避免了手动管理资源的复杂性和潜在的错误(如内存泄漏和资源泄露)。

核心思想

  • 资源获取: 当一个对象被创建时,它会立即获取某个资源。例如,分配内存、打开文件或创建数据库连接等。
  • 资源释放: 当该对象超出作用域或被销毁时,它的析构函数会自动释放相应的资源。这意味着开发者不需要显式地释放资源,降低了出错的概率。

实现方式

  • 构造函数:在对象创建时,负责分配所需的资源。例如,在构造函数中打开一个文件或分配一块内存。
  • 析构函数:在对象销毁时,负责释放该对象占用的资源。当对象的生命周期结束时,析构函数会自动执行,释放资源。

RAII 的优势

  • 自动资源管理: RAII 自动处理资源的释放,不需要显式调用资源释放代码,减少了出错的可能性(如忘记释放资源)。
  • 异常安全: RAII 能够保证即使程序中发生异常,资源也会被正确释放。例如,在 try 块中的对象被销毁时,析构函数会自动释放资源,从而避免资源泄漏。
  • 简洁性和易维护性: 使用 RAII 模式可以使资源管理代码更加简洁和模块化,减少了繁琐的手动管理。
  • 防止内存泄漏: 通过将资源与对象的生命周期绑定,可以有效防止内存泄漏、悬挂指针等问题。

RAII 的缺点

  • 不能自由控制资源释放的时机: 在 RAII 模式中,资源的释放依赖于对象的生命周期,无法显式控制资源的释放时机。如果需要在对象销毁之前释放资源,RAII 可能不适用。
  • 资源生命周期绑定问题: RAII 通过对象生命周期管理资源,这对于某些类型的资源可能不适用。例如,某些外部资源(如数据库连接)可能需要在特定时刻关闭,而不仅仅是在对象销毁时。

RAII 的应用场景

  • 内存管理:例如,unique_ptrshared_ptr 是 C++ 中的智能指针,它们的实现就是基于 RAII 模式,自动管理内存资源。
  • 文件操作:如上文所示,RAII 可以用于文件的打开和关闭,确保即使发生异常,文件资源也会被自动释放。
  • 数据库连接:RAII 可用于数据库连接的管理,确保连接在对象生命周期结束时被自动关闭。
  • 线程锁管理:通过 RAII 模式,锁的获取和释放可以自动管理,避免忘记释放锁导致死锁。

智能指针

智能指针(Smart Pointer 是现代 C++ 中用于自动管理动态内存的一种工具,它通过封装原始指针,提供对内存资源的自动管理,帮助避免常见的内存管理错误,如内存泄漏和悬挂指针。

智能指针实际上是一个类,它重载了指针操作符(如 *->),使得使用智能指针的代码和普通指针一样简便,但它能自动处理资源的释放。

C++标准库中的智能指针都在 <memory> 这个头文件下,智能指针主要有 auto_ptrunique_ptrshared_ptrweak_ptr 等。

auto_ptr

auto_ptr 是C++98标准中引入的一个智能指针类型,通过自动释放资源来避免内存泄漏和悬挂指针的问题。

1. auto_ptr 的缺陷

auto_ptr 的设计存在巨大缺陷,在涉及资源所有权转移时(拷贝或者赋值时)原auto_ptr 不再拥有资源,资源的所有权转移给 目标auto_ptr ,这导致了 原auto_ptr 变成一个悬挂指针(类似于空指针)。

代码示例:

//模拟一个日期类
struct Date
{int _year;int _month;int _day;Date(int year = 2000, int month = 1, int day = 1):_year(year),_month(month),_day(day){}~Date(){cout << "~Date" << endl;}};int main()
{auto_ptr<Date> ap1(new Date);//拷贝时,Date的管理权限从ap1转移ap2,ap1被置空auto_ptr<Date> ap2(ap1);//ap1相当于空指针了,再去访问会造成程序崩溃//ap1->_day;//赋值也是同样的道理,Date的管理权限从ap2转移ap3,ap2被置空auto_ptr<Date> ap3;ap3 = ap2;//ap2被置空,访问会造成程序崩溃ap2->_day;return 0;
}

auto_ptr 的设计存在缺陷,在在涉及资源所有权转移时,其行为会造成意外的错误,auto_ptr在C++11中被废弃,不推荐使用

2. auto_ptr 的模拟实现
auto_ptr 的模拟实现比较简单,在涉及资源的转移时,将原指针置空即可。

template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr = nullptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){if (_ptr != ap._ptr){if (_ptr)delete _ptr;_ptr = ap._ptr;ap._ptr = nullptr}return *this;}~auto_ptr(){if (_ptr)delete _ptr;_ptr = nullptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

unique_ptr

unique_ptr 是独占式的智能指针,表示指向一个动态分配的对象的唯一所有者。该指针不支持拷贝和赋值,但支持移动构造或者赋值

当一个资源只能有一个拥有者时,使用 unique_ptr 是最合适的选择。

代码示例:

unique_ptr<Date> up1(new Date);
//不支持拷贝或者赋值
//unique_ptr<Date> up2(up1);
//unique_ptr<Date> up3; up3 = up1;//支持移动构造或者赋值,但是ap1置空了,谨慎使用
unique_ptr<Date> up2(move(up1));
unique_ptr<Date> up3;
up3 = move(up2);

1. make_unique
make_unique 是 C++11/14 标准库中引入的一个函数模板,用于创建动态分配的对象并返回一个 unique_ptr,从而安全高效地管理对象的生命周期。

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args);

与直接使用 new 操作符相比的优势

  1. 避免手动调用 newdelete :使用 make_unique 能够简化动态内存分配,避免使用裸指针容易产生的内存泄漏或未定义行为。
  2. 性能优化 :它能够一次性分配对象和控制块所需的内存,减少额外开销。
  3. 强异常安全性:使用 make_unique 时,不会因为对象构造和分配的中间异常而泄漏内存。
//使用 make_unique 创建一个 int 类型的 unique_ptr(推荐)
auto up1 = make_unique<int>(20);//直接使用 unique_ptr(容易出错),如果构造函数抛异常就会出现内存泄漏
unique_ptr<int> up2(new int(10));

2. 定制删除器

unique_ptr 在释放资源时,默认是 delete _ptr ,如果指向的资源是 new type[num] 而来的,默认释放资源的方式就不适合了,需要 delete[] 的方式是释放资源,这时我们需要定制删除器。

new [] 的方式经常使用,库里已经有了特化版本,而对于定制删除器,仿函数、函数指针、lamba表达式都可作为删除器。

不过要注意的是传定制删除器给 unique_ptr ,是传给模板参数,其构造参数也要传。
在这里插入图片描述
代码示例:

//new []特化
unique_ptr<Date[]> up1(new Date[5]);// 仿函数作删除器,将其传到模板参数,仿函数构造的对象可以直接调用,不需要传给构造参数
unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);//函数指针作删除器,既要传模板参数也要传构造参数
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);// lambda表达式作删除器,decltype获取delArr的类型
auto delArr = [](Date* ptr) {delete[] ptr; };
unique_ptr<Date, decltype(delArr)> up4(new Date[5], delArr);

简单说一下定制删除器的底层,将定制删除器的类型传过去利用其类型创建删除器对象并用传给构造参数的具体定制删除器对象来初始化,这样底层就有了外层传进来的定制删除器,然后利用删除器释放资源

3. unique_ptr 的模拟实现

unique_ptr 的模拟实现也比较简单,将其构造函数赋值重载函数 delete 即可。

template<class T>
class unique_ptr
{
public://防止隐式类型转换explicit unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr)delete _ptr;_ptr = nullptr;}unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;unique_ptr(unique_ptr<T>&& up):_ptr(up._ptr){up._ptr = nullptr;}unique_ptr<T>& operator=(unique_ptr<T>&& up){delete _ptr;_ptr = up._ptr;up._ptr = nullptr;}private:T* _ptr;
};

有关 explicit

关键字 explicit 的作用是修饰构造函数,防止隐式类型转换

Date* ptr = new Date;
//hz::unique_ptr<Date> up1 = ptr; 这种写法编译会出错,explicit不允许隐式类型转换,本质是构造+拷贝
unique_ptr<Date> up1(ptr);

使用 explicit 修饰单参数构造函数可以提高代码的可读性,减少维护负担。

shared_ptr

shared_ptr 是 C++11 标准引入的一种智能指针,用于管理动态分配的对象,并允许多个 shared_ptr 实例共享同一对象的所有权。shared_ptr 使用 引用计数 来追踪有多少个 shared_ptr 对象共享资源,并在最后一个 shared_ptr 被销毁时自动释放资源。这种机制确保了内存管理的安全性,避免了内存泄漏,同时允许多个对象共享相同的资源。

shared_ptr 是一种 共享所有权 的智能指针,而非独占所有权(像 unique_ptr )。多个 shared_ptr 对象可以共同管理一个动态分配的对象,而不必担心资源的重复释放或遗漏释放。

代码示例:

//创建一个 shared_ptr 管理 Date 对象
shared_ptr<Date> sp1(new Date);//复制sp1,增加引用计数
shared_ptr<Date> sp2(sp1);
cout << "Reference count: " << sp1.use_count() << endl;//当 sp1 和 sp2 超出作用域时,Date 对象会被自动销毁

代码解析:
1.shared_ptr<Date> sp1(new Date);

  • sp1 是一个 shared_ptr,它管理一个动态分配的 Date 对象。此时引用计数为 1。

2.shared_ptr<Date> sp2(sp1);

  • sp2sp1 的副本,意味着它也指向同一个 Date 对象,引用计数增加到 。

3.sp1.use_count() 返回当前有多少个 shared_ptr 管理相同的对象。此时返回 2。

4.当 sp1sp2 超出作用域时,它们的引用计数都会减少。当引用计数降到 0 时,Date 对象会自动销毁。

有关 make_shared

make_shared 也是一个函数模板,用于创建共享指针,可以接受任何类型的参数,并返回一个指向该类型对象的共享指针。

template <class T, class... Args>shared_ptr<T> make_shared (Args&&... args);
  • make_shared 与直接使用 shared_ptr 的对比
特性make_shared直接用shared_ptr
语法简洁性更简洁需要手动调用 new
内存分配次数1 次2 次(对象和引用计数分别分配)
异常安全性更安全容易出现内存泄漏

模拟实现

对于 shared_ptr 的模拟实现,我们首先要考虑的就是引用计数的设计。

引用计数用静态成员变量是无法实现的

因为静态成员变量是整个类共有的,每当指向一个资源,无论是不同的资源还是相同的资源,静态成员变量都会增加,不能做到对于不同的资源都有独立的一份引用计数

比如 sp1 和 sp2 指向着资源1,引用计数是2,在创建一个 sp3 指向资源2,由于引用计数是静态成员变量,引用计数就变成3了,这显然是错误的,sp3 的引用计数应该是1.

  • 引用计数的设计应该采用动态开辟的方式,做到每一个不同的资源都有一份独立的引用计数。
    在这里插入图片描述
    以下为 shared_ptr 的实现:
template<class T>
class shared_ptr
{
public://explicit防止隐式类型转换explicit shared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}~shared_ptr(){if (--(*_pcount) == 0){delete _ptr;delete _pcount;}}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count() const{return *_pcount;}private:T* _ptr;int* _pcount;
};

shared_ptr 的成员函数的实现都比较简单,但是赋值重载函数有比较多细节要注意:

  1. 赋值操作要保证不是一个指针自己给自己赋值this != &sp 不能完全处理所有情况,因为不同的 shared_ptr 对象的 _ptr 可能是一样的,得用 _ptr != sp._ptr 才可以完全覆盖所有情况。

  2. 被赋值的指针的引用计数要先要减1判断该指针是否是最后一个指向对应资源的指针若是则要释放原来的资源

  3. 进行赋值操作,完成后引用计数要+1,最后返回 *this
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{if (_ptr != sp._ptr){if (--(_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;
}

定制删除器

shared_ptr 也可以传定制删除器,不过相比 unique_ptr 的方式,shared_ptr 传递删除器的方式只需传到构造函数的参数即可

//其构造函数的声明
template <class U, class D> shared_ptr (U* p, D del);
template <class D> shared_ptr (nullptr_t p, D del);

使用示例:

template<class T>
struct DeleteArray
{void operator()(T* ptr){delete[] ptr;}
};template<class T>
void DeleteArrayFunc(T* ptr)
{delete[] ptr;
}//[]特化版本
shared_ptr<int[]> sp1(new int[10]);//仿函数作删除器
shared_ptr<Date> sp2(new Date[10], DeleteArray<Date>());//函数指针作删除器
shared_ptr<Date> sp3(new Date[10], DeleteArrayFunc<Date>);//lambda作删除器
auto  delArr = [](Date* ptr) {delete[] ptr; };
shared_ptr<Date> sp4(new Date[10], delArr);

增加定制删除器的模拟实现:

template<class T>
class shared_ptr
{
public://explicit防止隐式类型转换explicit shared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new int(1)),_del(del){}~shared_ptr(){if (--(*_pcount) == 0){_del(_ptr);delete _pcount;}}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del){++(*_pcount);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){if (--(*_pcount) == 0){_del(_ptr);delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;_del = sp._del;++(*_pcount);}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;//利用function来包装 _del,默认是不带[] 的deletefunction<void(T*)> _del = [](T* ptr) {delete ptr; };
};

代码细节:

  • 写多一个构造函数并套一层模板,当传递删除器的时候,调用此函数。
template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new int(1)),_del(del){}
  • 删除器可以是仿函数、函数指针和 lambda 表达式等,我们是没有办法用具体的某个类型去创建 _del 变量,但是C++11中有一个类模板 function ,它是通用的函数包装器,可以包装仿函数、函数指针和 lambda 表达式,而删除器的函数签名都是 void(T* ptr)(返回类型和参数类型)。我们就可以用 function 来创建 _del 变量,并给上 lambda 缺省值 [](T* ptr) {delete ptr; }
//利用function来包装 _del,默认是不带[] 的deletefunction<void(T*)> _del = [](T* ptr) {delete ptr; };

循环引用 和 weak_ptr

智能指针是用来管理动态分配的内存,以避免内存泄漏的问题。然而,如果使用不当,智能指针也会引入一些新的问题,例如循环引用

循环引用(Cyclic Reference)是指两个或多个对象互相持有对方的引用形成一个环导致它们无法被释放,即使它们已经不再被其他部分使用。

代码示例

class Node {
public://指向下一个节点的智能指针shared_ptr<Node> next;~Node() { cout << "Node destroyed" << endl; }
};int main() {auto node1 = make_shared<Node>();auto node2 = make_shared<Node>();// 相互引用node1->next = node2;node2->next = node1;// 程序结束时,node1和node2不会被释放return 0;
}

在这里插入图片描述

为什么会出现循环引用?

  1. shared_ptr 的原理:

    • shared_ptr 通过引用计数来管理对象的生命周期。
    • 当引用计数变为0时,shared_ptr 会自动释放内存。
  2. 循环引用的本质:

    • 在上述例子中,node1node2 互相持有对方的 shared_ptrnode1 需要 node2的shared_ptr 析构时释放,而 node2的shared_ptrnode2 的成员变量,需要让 node2 释放才会析构,node2 需要 node1的shared_ptr 析构时释放, node1的shared_ptr 需要让 node1 释放才会析构。这样就形成一个环了,两个节点的 shared_ptr 的引用计数始终不为0;
    • 即使它们超出了作用域,也不会被销毁,从而引发内存泄漏
      在这里插入图片描述

如何解决循环引用问题?

可以通过将其中一个 shared_ptr 替换为 weak_ptr来打破循环引用。

class Node {
public:// 用weak_ptr打破循环引用weak_ptr<Node> next;~Node() { cout << "Node destroyed" << endl; }
};int main() {auto node1 = make_shared<Node>();auto node2 = make_shared<Node>();// weak_ptr不会增加引用计数node1->next = node2;node2->next = node1;// 程序结束时,node1和node2会被正确释放return 0;
}

程序结束时,先析构 node2 ,引用计数减到0,释放 node2 ,而不会像循环引用那般由于 node2 和 node1->next 指向同一个对象,析构 node2 时其引用计数从2减到1,导致引用计数永远不为0导致 node2 无法释放。node2 释放后 node1 也能正常释放了

weak_ptr 是一种辅助智能指针,它与 shared_ptr 配合使用,用于解决循环引用问题实现对象的非强拥有关系

  • weak_ptr 是一种不参与引用计数的智能指针。
  • 它不会改变所指向对象的生命周期,仅仅是一个“弱引用”。
  • 常用于观察由 shared_ptr 管理的对象,而不会影响其销毁时机。

基本用法:

  • weak_ptr 必须从 shared_ptr 初始化,不能直接管理动态分配的内存。
  • 通过 weak_ptr 无法直接访问对象,需要调用 lock() 方法将其转换为 shared_ptr
    lock() 方法返回一个指向相同对象的 shared_ptr,如果对象已被释放,则返回一个空指针。

shared_ptr 的区别

特性shared_ptrweak_ptr
是否参与引用计数
是否影响生命周期
访问对象方式直接使用 *->需调用 lock() 转换为 shared_ptr
应用场景强拥有关系,负责对象生命周期弱引用,避免循环引用或临时访问

总结

  • weak_ptr 是一种轻量级的智能指针,用于观察对象,不参与对象生命周期管理。
  • 在设计需要临时引用或防止循环引用的场景中,weak_ptr 是一个非常重要的工具。
  • 配合 shared_ptr 使用,能够更好地管理复杂对象间的依赖关系。

拜拜,下期再见😏

摸鱼ing😴✨🎞
请添加图片描述

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

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

相关文章

Elasticsearch vs 向量数据库:寻找最佳混合检索方案

图片来自Shutterstock上的Bakhtiar Zein 多年来&#xff0c;以Elasticsearch为代表的基于全文检索的搜索方案&#xff0c;一直是搜索和推荐引擎等信息检索系统的默认选择。但传统的全文搜索只能提供基于关键字匹配的精确结果&#xff0c;例如找到包含特殊名词“Python3.9”的文…

探索ai一键生成PPT的未来

在当今快节奏的工作环境中&#xff0c;如何高效地完成PPT制作任务&#xff0c;已经成为了许多人追求的目标。无论是职场精英&#xff0c;还是创业者&#xff0c;每个人都希望通过智能工具来提高工作效率。而AI生成PPT&#xff0c;尤其是利用一些先进的自动生成PPT技术&#xff…

vue-router查漏补缺

一、动态路由匹配 1.带参数的动态路由匹配 import User from ./User.vue// 这些都会传递给 createRouter const routes [// 动态字段以冒号开始{ path: /users/:efg, component: User }, ]这种方式的路由会匹配到/users/abc或者/users/123,路径参数用冒号:表示&#xff0c;并…

013路由协议-OSPF

OSPF具有更适用于规模较大的网络环境&#xff0c;收敛更快速、依据带宽来计算路径成本等。 计算方式&#xff1a; 100M/当前端口的带宽 如果小于1就按照1来计算 例如&#xff1a; 当前端口的带宽是1.54M 路径成本 100/1.54 65 当前端口的带宽是 1000M 路径成本 100/100 0.…

快捷构建AI大模型,源码自取可直接运行

Node.js 和 WebSocket 实现一个基于kimi&#xff08;Moonshot 月之暗大模型&#xff09;的AI工具 前端&#xff1a;前端界面比较容易&#xff0c;只需要简单的额css js即可&#xff0c;本文使用vue作为作为demo。 后端&#xff1a;我java很垃圾&#xff0c;写不出好的代码&am…

探索云原生安全解决方案的未来

我们是否充分意识到云端所面临的网络安全威胁&#xff1f; 在当今互联互通的世界中&#xff0c;维护安全的环境至关重要。云的出现扩大了潜在威胁的范围&#xff0c;因为它催生了机器身份&#xff08;称为非人类身份 (NHI)&#xff09;及其秘密。随着组织越来越多地转向云原生…

关于利用 EtherNet/IP 转 Profinet 网关模块实现罗克韦尔变频器接入西门子 PLC 的配置范例

在现代工业自动化领域&#xff0c;不同品牌设备之间的通信兼容性问题一直是企业面临的挑战之一。某智能工厂为了优化生产流程&#xff0c;提高设备的协同工作效率&#xff0c;决定对其生产线上的控制系统进行升级改造。该生产线中&#xff0c;AB罗克韦尔PowerFlex变频器作为关键…

Ajax--实现检测用户名是否存在功能

&#xff08;一&#xff09;什么是Ajax Ajax&#xff08;Asynchronous Javascript And XML&#xff09; 翻译成中文就是“异步JavaScript和XML”&#xff0c;即使用JavaScript与服务器进行异步交互&#xff0c;传输的数据为XML。 AJAX还可以在浏览器实现局部刷新的效果&#xf…

【LC】160. 相交链表

题目描述&#xff1a; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&…

算法设计1_分治

递归的概念 递归算法&#xff1a;一个直接或间接地调用自身的算法递归函数&#xff1a;使用函数自身给出定义的函数递归方程&#xff1a;对于递归算法&#xff0c;一般可把时间代价表示为一个递归方程解递归方程最常用的方法是进行递归扩展 阶乘函数 边界条件递归关系 n ! {…

基于yolov8的SAR影像目标检测系统,支持图像、视频和摄像实时检测【pytorch框架、python源码】

更多目标检测、图像分类识别、目标追踪等项目可看我主页其他文章 功能演示&#xff1a; 基于yolov8的SAR影像目标检测系统&#xff0c;支持图像、视频和摄像实时检测【pytorch框架、python源码】_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于yolov8的SAR影像目标…

uni-app 设置缓存过期时间【跨端开发系列】

&#x1f517; uniapp 跨端开发系列文章&#xff1a;&#x1f380;&#x1f380;&#x1f380; uni-app 组成和跨端原理 【跨端开发系列】 uni-app 各端差异注意事项 【跨端开发系列】uni-app 离线本地存储方案 【跨端开发系列】uni-app UI库、框架、组件选型指南 【跨端开…

复现论文:PromptTA: Prompt-driven Text Adapter for Source-freeDomain Generalization

github&#xff1a;zhanghr2001/PromptTA: Source-free Domain Generalization 论文&#xff1a;[2409.14163] PromptTA: Prompt-driven Text Adapter for Source-free Domain Generalization 自己标注&#xff1a;PromptTA: Prompt-driven Text Adapter for Source-free Domai…

Dos脚本中的start命令

0 Preface/Foreword 1 Start介绍 start是用来启动一个应用或者一个bat脚本文件。 1.1 %*传递参数 %*&#xff1a;表示运行命令时传入的所有参数。 1.2 %processor_architecture% 系统处理器架构&#xff0c;内置变量。 echo %processor_architecture% 1.3 示例 echo He…

HTML笔记()蜘蛛纸牌之卡牌拖拽

效果 代码 <!DOCTYPE html> <html><head><style>body{display: flex;justify-content: center;align-items: center;height: 100vh;background-color: #2b2b2b;position: relative;}.card{/*设置卡牌的外观*/width: 150px;height: 200px;background-…

基于SSM的线上考试系统的设计与实现(计算机毕业设计)+万字说明文档

系统合集跳转 源码获取链接 一、系统环境 运行环境: 最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以 tomcat环境&#xff1a; Tomcat 7.x,8.x,9.x版本均可 操作系统…

vue 封装全局方法及使用

1.找到项目中的utils定义js&#xff0c;这个js存放全局可使用的方法 2.去项目中main.js中引入注册 import publicFun from ./utils/test Vue.prototype.$publicFun publicFun;3.项目使用 ddd(){this.$publicFun.testwen()},

微信小程序中使用miniprogram-sm-crypto实现SM4加密攻略

在微信小程序开发过程中&#xff0c;数据安全至关重要。本文将为大家介绍如何在微信小程序中使用miniprogram-sm-crypto插件进行SM4加密&#xff0c;确保数据传输的安全性。 一、SM4加密简介 SM4是一种对称加密算法&#xff0c;由国家密码管理局发布&#xff0c;适用于商密领…

【论文阅读】相似误差订正方法在风电短期风速预报中的应用研究

文章目录 概述&#xff1a;摘要1. 引言2. 相似误差订正算法&#xff08;核心&#xff09;3. 订正实验3.1 相似因子选取3.2 相似样本数试验3.3 时间窗时长实验 4. 订正结果分析4.1 评估指标对比4.2 风速曲线对比4.3 分风速段订正效果评估4.4 风速频率统计 5. 结论与讨论 概述&am…

高中数学:计数原理-二项式定理

文章目录 一、二项式定理与通项公式二、二项式系数的性质 一、二项式定理与通项公式 我们先来看完全平方公式 二、二项式系数的性质