让内存无处可逃:智能指针[C++11]

智能指针

文章目录

  • 智能指针
    • 前言
    • RAII
    • 什么是智能指针
      • 智能指针的应用示例
    • C++98的auto_ptr
    • 共享型智能指针:shared_ptr
      • shared_ptr的使用
        • 初始化
        • 获取原生指针
        • 指定删除器
          • 默认删除器default_delete
          • 指定删除器
          • 指定删除器管理动态数组
      • shared_ptr的伪实现
      • shared_ptr的注意事项
        • 避免一个原始指针初始化多个shared_ptr
        • 不要在函数实参中创建shared_ptr
        • 不要通过shared_ptr返回this指针
        • 避免循环引用
    • 独占型智能指针:unique_ptr
      • unique_ptr的删除器
        • 默认删除器
        • 指定删除器
    • 弱引用的智能指针:weak_ptr
      • weak_ptr的基本用法
        • 获取检测资源引用计数
        • 判断是否释放
        • 获取监测的shared_ptr
      • weak_ptr返回this指针
      • weak_ptr解决循环引用问题
    • 智能指针管理第三方库分配内存
    • 总结

前言

C#和Java中有自动垃圾回收机制,因此,在C#和Java中,内存管理不是大问题。但是C++语言没有垃圾回收机制,必须自己去释放分配的堆内存,否则就会内存泄露。相信大部分C++开发人员都遇到过内存泄露的问题,而查找内存泄露的问题往往要花大量的精力。为了解决这个问题C++11提供了3种智能指针: shared_ptr、unique_ptr和weak_ptr,使用时需要引用头文件<memory>,本文将分别介绍这3种智能指针。

RAII

在这里插入图片描述

RAII(Resource Acquisition Is Initialization)是由c++之父Bjarne Stroustrup提出的,他说:使用局部对象来管理资源的技术称为资源获取即初始化(RAII);这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显式地释放资源。

  • 采用这种方式,对象所需的资源在其生命期内始终保持有效

什么是智能指针

智能指针是一个模板类,支持创建任意类型的指针对象,当对象生命周期结束时,自动调用其析构函数释放资源。

下面给出了一份智能指针的框架,当然它显然不能满足我们对于避免内存泄露的需求。

template <class T>
class SmartPtr
{
public:SmartPtr(T *ptr) : _ptr(ptr) {}T &operator*(){return *_ptr;}T *operator->(){return &(*_ptr);}~SmartPtr(){if (_ptr){cout << "delete ptr" << endl;::delete _ptr;}}private:T *_ptr;
};

智能指针的应用示例

尽管我们上面给出的样例代码不够完善,但在某些情境下已经可以处理一些问题了。

我们用智能指针创建一个对象,不手动释放内存空间,观察程序结束时的结果。

#include <iostream>
#include <memory>
using namespace std;
#include "SmartPointer.h"int divide(int x, int y)
{if (!y){throw invalid_argument("Divide Zero Error\n");}return x / y;
}
int main()
{SmartPtr<int> p(new int(10));int x = 1, y = 0;try{cout << divide(x, y);}catch (exception &e){cout << e.what();}return 0;
}

运行结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于对象在栈上,当其生命周期结束时,都会调用其析构函数,而我们内存的释放托管给了析构函数,从而避免了手动释放内存空间。

那么我们上面智能指针的缺陷在哪呢?

int main()
{int *pt = new int(10);SmartPtr<int> p1(pt);SmartPtr<int> p2(p1);return 0;
}

当两个智能指针管理同一块内存空间时,两次析构对应两次delete,导致了程序崩溃,可见是智能指针绝非我们样例中的那么简单,那么C++11是如何解决的呢?

C++98的auto_ptr

对于内存泄漏问题早在C++98中就有了解决方案,它在委托释放的基础上加了一条规则——管理权转移,即,当出现像我们前面智能指针的拷贝时,被拷贝对象的指针就会被置空。(这种处理方案显然也有漏洞)

下面是auto_ptr拷贝构造函数的代码

//关于release的定义
release() throw()
{element_type* __tmp = _M_ptr;_M_ptr = 0;return __tmp;
}
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }

可见当调用拷贝构造时,被拷贝对象的指针被赋空,我们很容易找到其破绽。

int main()
{int *pt = new int(10);std::auto_ptr p1(pt);std::auto_ptr p2(p1);cout << *p1;return 0;
}

当p2拷贝p1后,我们再访问p1管理的内容,程序直接就崩溃了。那么C++11是如何解决的呢?

共享型智能指针:shared_ptr

shared_ptr的解决方案是引用计数,每一个shared_ptr支持拷贝,每一个拷贝都指向相同的内容,当最后一个shared_ptr对象析构时才会释放管理的内存。

我们可以在shared_ptr基类的源码内找到如下代码段:

//shared_ptr_base.h
__shared_count<_Lp>  _M_refcount;    // Reference counter.

可见shared_ptr底层是专门封装了一个引用计数器类的,我们其实可以通过指针来伪实现一下。

shared_ptr的使用

初始化

可以通过构造函数、std::make_shared<T>辅助函数和reset来初始化shared_ptr。

构造函数、std::make_shared<T>初始化

std::make_shared<T>相对来说更为高效

    std::shared_ptr<int> p(new int(1));std::shared_ptr<int> p1 = p;std::shared_ptr<int> ptr;ptr.reset(new int(1));if (ptr){std::cout << "ptr isn't null" << std::endl;}std::shared_ptr<int> p2 = std::make_shared<int>(1);std::cout << "value of p2 is : " << *p2;//ptr isn't null//value of p2 is : 1

不要直接将原生指针赋值给智能指针。

//错误用法
std::shared_ptr<int> p = new int(1);

reset初始化

shared_ptr调用reset接口时,如果该shared_ptr有值,那么引用计数-1

    std::shared_ptr<A> p = std::make_shared<A>(A());p.reset();system("pause");//~A()
//~A()

我们可以看到reset的源码,其实就是用一个临时对象去和当前shared_ptr做swap,函数结束,临时对象自动析构

//shared_ptr_base.h
void reset() noexcept
{ __shared_ptr().swap(*this); }
获取原生指针

原生指针我们可以调用get接口来获取。

    std::shared_ptr<int> p = std::make_shared<int>(1);int *p1 = p.get();std::cout << *p1;system("pause");
指定删除器
默认删除器default_delete

shared_ptr当引用计数为0时,会调用删除器来释放托管内存,默认删除器为default_delete,它只是对delete做了一层封装

指定删除器

shared_ptr初始化时还可以指定删除器。当shared_ptr的引用计数为1时会自动调用该删除器,删除器可以是函数也可以是lambda表达式。

//void MyDeleter(int *p)
//{
//    delete p;
//    std::cout << "delete p" << std::endl;
//}std::shared_ptr<int> p(new int(1), MyDeleter);std::shared_ptr<int> p1(new int(1), [](int *p){    delete p;std::cout << "delete p" << std::endl; });
//delete p
//delete p
指定删除器管理动态数组

由于default_delete只是对delete的浅封装,这也就意味着当我们用智能指针管理动态数组时需要对删除器进行指定。

当然,我们也可以仍使用default_delete,只不过要将模板参数改为数组

class A
{
public:~A(){std::cout << "~A()" << std::endl;}
};
std::shared_ptr<A> p(new A[10], std::default_delete<A[]>());
std::shared_ptr<A> p1(new A[10], [](A *p){ delete[] p; });

shared_ptr的伪实现

我们可以用指针来伪替代stl库中的引用计数器,同时加上互斥锁保证线程安全从而伪实现shared_ptr。

template <class T>
class shared_ptr
{
public:shared_ptr(T *p) : _p(p), _counter(new int(1)), _pmtx(new std::mutex){}shared_ptr(const shared_ptr<T> &sp) : _p(sp._p), _counter(sp._counter), _pmtx(sp._pmtx){add_ret_count();}shared_ptr<T> operator=(const shared_ptr<T> &sp){if (this != &sp){release();_p = sp._p;_counter = sp._counter;_pmtx = sp._pmtx;add_ret_count();}return *this;}T *operator->(){return _p;}T operator*(){return *_p;}T *get(){return _p;}void add_ret_count(){_pmtx->lock();(*_counter)++;_pmtx->unlock();}int &use_count(){return *(_counter);}void release(){bool flag = false;_pmtx->lock();if (--(*_counter) == 0){delete _p;delete _counter;_p = _counter = nullptr;flag = true;std::cout << "delete _p" << std::endl;}_pmtx->unlock();if (flag){delete _pmtx;_pmtx = nullptr;}}~shared_ptr(){release();}private:T *_p;std::mutex *_pmtx;int *_counter;
};

shared_ptr的注意事项

避免一个原始指针初始化多个shared_ptr
    int *p = new int(1);std::shared_ptr<int> p1(p);std::shared_ptr<int> p2(p);

显然这样会导致重复析构的问题

不要在函数实参中创建shared_ptr
func(std::shared_ptr<int>(new int(1)), g());

我们C++很多时候遵循stdcall,也就是从右到左的调用约定,但有时也会从左到右,所以很可能先创建了new int,然后调用g(),但是g()发生了异常,这就导致内存泄漏了。

std::shared_ptr<int> p(new int(1));
func(p, g());
不要通过shared_ptr返回this指针

下面的例子,由于用同一个this构造了两个shared_ptr,这其实就和我们第一种情况类似了,两个智能指针各自析构导致了重复析构

class A 
{
public:std::shared_ptr<A> getSelf(){return shared_ptr<A>(this);}~A(){std::cout << "~A()" << std::endl;}
};int main()
{std::shared_ptr<A> p(new A());std::shared_ptr<A> pa = p->getSelf();return 0;
}

如果想要获取this,正确方法是令管理的类继承std::enable_shared_from_this<T>

用enable_shared_from_this的原因,我们后面讲解weak_ptr时会解释。

class A : public std::enable_shared_from_this<A>
{
public:std::shared_ptr<A> getSelf(){return std::shared_from_this();}~A(){std::cout << "~A()" << std::endl;}
};int main()
{std::shared_ptr<A> p(new A());std::shared_ptr<A> pa = p->getSelf();return 0;
}
避免循环引用

循环引用问题是shared_ptr很容易踩的一个陷阱,前面几项逻辑上的错误还是很明显的,容易规避,但是循环引用却很容易让我们落入陷阱。

如下例,我们在实现某些数据结构时用到了三叉链结构,那么三叉链的父子关系就会导致循环引用,此时parent的左孩子为child,child的父节点为parent,导致了parent和child的引用计数都为2,当程序结束,parent和child自动调用析构函数,此时就会导致二者的引用计数都为1,没有释放内存。

对于循环计数的解决方案,C++11专门又引入了weak_ptr来进行解决,我们在后面会介绍。

struct Node
{std::shared_ptr<Node> _parent;std::shared_ptr<Node> _left;std::shared_ptr<Node> _right;~Node(){std::cout << "~Node()" << std::endl;}
};
int main()
{std::shared_ptr<Node> parent(new Node), child(new Node);parent->_left = child;child->_parent = parent;return 0;
}
//无输出

独占型智能指针:unique_ptr

从名称就可以看出,unique_ptr做的很绝,直接ban掉左值拷贝构造函数和左值赋值函数

在源码中,我们可以找到如下代码,禁用了左值构造和左值赋值。

      // Disable copy from lvalue.unique_ptr(const unique_ptr&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;

但是我们可以通过移动语义将其转化为右值来进行赋值。当然,移动语义后原先的unique_ptr自然就失去了对原先资源的管理权。

    std::unique_ptr<int> p(new int(1));std::unique_ptr<int> p1 = std::move(p);//p放弃管理权std::cout<< *p1;

C++11时还未引入类似于make_shared的make_unique来构建unique_ptr,C++14才提供了make_unique,但是我们可以自己实现一个make_unique方法。

template<class T, class... Args> inline
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(Args&&... args)
{return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}//动态数组
template<class T> inline
typename std::enable_if<std::is_array<T>::value && std::extent<T>::value == 0, std::unique_ptr<T>>::type
make_unique(size_t size)
{typedef typename std::remove_extent<T>::type U;return std::unique_ptr<T>(new U[size]());
}//过滤掉静态数组
template<class T, class... Args> inline
typename std::enable_if<std::extent<T>::value != 0, void>::type
make_unique(Args&&... args) = delete;
int main()
{std::unique_ptr<int[]> p = make_unique<int[]>(10);for (int i = 0; i < 10; i++)p[i] = i;for (int i = 0; i < 10; i++)std::cout << p[i] << " ";return 0;
}
//0 1 2 3 4 5 6 7 8 9

unique_ptr的删除器

默认删除器

unique_ptr也使用了默认删除器default_delete,但是由于析构时处理方式不同,所以unique_ptr是可以管理动态数组的。

但是如果是shared_ptr就不行了。

	std::unique_ptr<A[]> p(new A[10]);
//~A() ~A() ~A() ~A() ~A() ~A() ~A() ~A() ~A() ~A()
指定删除器

unique_ptr删除器由于参数模板是要传类型过去,所以不能直接写lambda表达式。

    std::unique_ptr<int,std::function<void(int*)>> p1(new int(1), [](int* x) {delete x; });std::unique_ptr<int, MyDeleter> p2(new int(1), MyDeleter());

关于shared_ptr 和unique_ ptr 的使用场景要根据实际应用需求来选择,如果希望只有一个智能指针管理资源或者管理数组就unique_ptr,如果希望多个智能指针管理同-一个资源就用shared_ptr。

弱引用的智能指针:weak_ptr

弱引用指针weak_ ptr 是用来监视shared_ptr的,不会使引用计数加1它不管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命周期,更像是shared_ptr的一个助手。weak_ ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构也不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中管理的资源是否存在weakptr还可以用来返回this指针和解决循环引用的问题。

weak_ptr的基本用法

获取检测资源引用计数

use_count接口可以获取引用计数

    std::shared_ptr<int> p(new int(1));std::weak_ptr<int> guard(p);std::cout << guard.use_count();//1
判断是否释放

expired接口返回bool值来判断资源是否释放。

    std::shared_ptr<int> p(new int(1));std::weak_ptr<int> guard(p);if (guard.expired())std::cout << "resource has expired" << std::endl;elsestd::cout << "resource is still valid" << std::endl;[]()//resource is still valid
获取监测的shared_ptr

lock接口返回监测的shared_ptr

std::weak_ptr<int> guard;
void foo()
{if (guard.expired()){std::cout << "resource has expired" << std::endl;}else{auto pp = guard.lock();std::cout << *pp << std::endl;}
}
int main()
{{std::shared_ptr<int> p(new int(1));guard = p;foo();}foo();return 0;
}
//1
//resource has expired

weak_ptr返回this指针

我们前面介绍shared_ptr的时候提到了不能够直接用shared_ptr返回this指针,但是如果类为enable_shared_from_this的派生类就可以通过shared_from_this获取this指针,因为enable_shared_from_this中有一个weak_ptr,shared_from_this实际上是通过weak_ptr的lock接口来获取shared_ptr

class A : public std::enable_shared_from_this<A>
{
public:int a = 1;std::shared_ptr<A> getSelf(){return shared_from_this();}~A(){std::cout << "~A()" << std::endl;}
};int main()
{std::shared_ptr<A> p(new A());std::shared_ptr<A> pa = p->getSelf();std::cout << pa->a << std::endl;return 0;
}
//1
//~A()

weak_ptr解决循环引用问题

再回到前面的三叉链的例子里,由于循环引用,导致parent和child的引用计数都是2,生命周期计数也只能减少到1,所以导致内存泄漏。

struct Node
{std::shared_ptr<Node> _parent;std::shared_ptr<Node> _left;std::shared_ptr<Node> _right;~Node(){std::cout << "~Node()" << std::endl;}
};
int main()
{std::shared_ptr<Node> parent(new Node), child(new Node);parent->_left = child;child->_parent = parent;return 0;
}
//无输出

解决方案就是把三叉链的指针域改为weak_ptr

struct Node
{std::weak_ptr<Node> _parent;std::weak_ptr<Node> _left;std::weak_ptr<Node> _right;~Node(){std::cout << "~Node()" << std::endl;}
};
int main()
{std::shared_ptr<Node> parent(new Node), child(new Node);parent->_left = child;child->_parent = parent;return 0;
}
//~Node()
//~Node()

智能指针管理第三方库分配内存

智能指针可以很方便地管理当前程序库动态分配的内存,还可以用来管理第三方库分配的内存。第三方库分配的内存一般需要通过第三方库提供的释放接口才能释放,由于第三方库返回的指针一般都是原始指针,在用完之后如果没有调用第三方库的释放接口,就很容易造成内存泄露。比如下面的代码:

    void *p = GetHnadle()->Create();// do somethingGetHnadle()->Release(p);

这段代码逻辑其实是很危险的,因为在调用第三方库分配内存的过程中,可能忘记调用Release接口,也可能不小心返回了,也可能出现了异常,导致无法调用Release接口,但是如果是智能指针的话就不必有这样的担心了,因为离开作用域自动释放。

    void *p = GetHnadle()->Create();std::shared_ptr<void> sp(p, [&](void* p){ GetHnadle()->Release(p); });// do something

我们可以专门写一个函数来进行这种资源释放的委托。

std::shared_ptr<void> Guard(void *p)
{return std::shared_ptr<void>(p, [&](void* p){ GetHnadle()->Release(p); });
}
int main()
{void *p = GetHnadle()->Create();auto sp = Guard(p);// do somethingreturn 0;
}

但是还是不够安全,因为总有小可爱程序员会写成这样:

std::shared_ptr<void> Guard(void *p)
{return std::shared_ptr<void>(p, [&](void* p){ GetHnadle()->Release(p); });
}
int main()
{void *p = GetHnadle()->Create();Guard(p);//直接提前析构了可还行// do somethingreturn 0;
}

那么我们可以更进一步,用宏来解决这个问题:

#define GUARD(p) std::shared_ptr<void> p##p(p, [&](void*p) { GetHnadle()->Release(p); });
int main()
{void *p = GetHnadle()->Create();GUARD(p);// do somethingreturn 0;
}

总结

智能指针是为没有垃圾回收机制的语言解决可能的内存泄露问题的利器,但是在实际应用中使用智能指针有一些需要注意的地方,好在这些问题都可以解决。

  • shared_ptr 和unique_ptr 使用时如何选择:如果希望只有一个智能指针管理资源或者管理数组,可以用unique_ptr ;如果希望多个智能指针管理同一个资源,可以用shared_ptr。

  • weak_ptr是shared_ptr的助手,只是监视shared_ptr管理的资源是否被释放,本身并不操作或者管理资源。用于解决shared_ptr 循环引用和返回this指针的问题。

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

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

相关文章

【Docker】进阶之路:(五)Docker引擎

【Docker】进阶之路&#xff1a;&#xff08;五&#xff09;Docker引擎 Docker引擎简介Docker引擎的组件构成runccontainerd Docker引擎简介 Docker引擎是用来运行和管理容器的核心部分。Docker首次发布时&#xff0c;Docker 引擎由LXC 和 Docker daemon 两个核心组件构成。 …

linux驱动开发——内核调试技术

目录 一、前言 二、内核调试方法 2.1 内核调试概述 2.2 学会分析内核源程序 2.3调试方法介绍 三、内核打印函数 3.1内核镜像解压前的串口输出函数 3.2 内核镜像解压后的串口输出函数 3.3 内核打印函数 四、获取内核信息 4.1系统请求键 4.2 通过/proc 接口 4.3 通过…

算法:有效的括号(入栈出栈)

时间复杂度 O(n) 空间复杂度 O(n∣Σ∣)&#xff0c;其中 Σ 表示字符集&#xff0c;本题中字符串只包含 6 种括号 /*** param {string} s* return {boolean}*/ var isValid function(s) {const map {"(":")","{":"}","["…

python格式化内容

1.字符串格式化: 定义列表 [{"姓名": "张三", "年龄": 18, "性别": "男"}, {"姓名": "里斯李四李斯", "年龄": 18, "性别": "男"}, {"姓名": "斯托夫斯基…

上位机与PLC:ModbusTCP通讯之数据类型转换

前请提要: 从PLC读取的数值,不管是读正负整数还是正负浮点数,读取过来后都会变成UInt16,也就是Ushort类型 一、ushort(UInt16)转成 Int32 源代码方法: //ushort类型转Int32类型的方法private int ushortToInt32(ushort[] date, int start){//先进行判断,长度是否正确…

模型评价指标

用训练好的模型结果进行预测&#xff0c;需要采用一些评价指标来进行评价&#xff0c;才可以得到最优的模型 常用的指标&#xff1a; 1.分类任务 ConfusionMatrix 混淆矩阵Accuracy 准确率Precision 精确率Recall 召回率F1 score H-mean值ROC Curve ROC曲线PR …

天池SQL训练营(三)-复杂查询方法-视图、子查询、函数等

-天池龙珠计划SQL训练营 SQL训练营页面地址&#xff1a;https://tianchi.aliyun.com/specials/promotion/aicampsql 3.1 视图 我们先来看一个查询语句&#xff08;仅做示例&#xff0c;未提供相关数据&#xff09; SELECT stu_name FROM view_students_info;单从表面上看起来…

rancher harvester deploy demo 【部署 harvester v1.2.1】

简介 Harvester 是一个现代的、开放的、可互操作的、基于Kubernetes的超融合基础设施(HCI)解决方案。它是一种开源替代方案&#xff0c;专为寻求云原生HCI解决方案的运营商而设计。Harvester运行在裸机服务器上&#xff0c;提供集成的虚拟化和分布式存储功能。除了传统的虚拟机…

SQL SELECT 语句

SELECT 语句用于从数据库中选取数据。 SQL SELECT 语句 SELECT 语句用于从数据库中选取数据。 结果被存储在一个结果表中&#xff0c;称为结果集。 SQL SELECT 语法 SELECT column1, column2, ... FROM table_name; 与 SELECT * FROM table_name; 参数说明&#xff1a; …

二分查找|双指针:LeetCode:2398.预算内的最多机器人数目

作者推荐 本文涉及的基础知识点 二分查找算法合集 滑动窗口 单调队列&#xff1a;计算最大值时&#xff0c;如果前面的数小&#xff0c;则必定被淘汰&#xff0c;前面的数早出队。 题目 你有 n 个机器人&#xff0c;给你两个下标从 0 开始的整数数组 chargeTimes 和 runnin…

算法:最长公共前缀(横向扫描和纵向扫描)

横向扫描 时间复杂度 O(m * n)&#xff0c;空间复杂度O(1) /*** param {string[]} strs* return {string}*/ var longestCommonPrefix function(strs) {// 先把第一个字符串拿出来let str strs[0]// 用 startsWith 检查数组中每个字符串是否以当前字符串为前缀while(!strs.e…

听GPT 讲Rust源代码--src/tools(11)

File: rust/src/tools/rust-analyzer/crates/hir/src/lib.rs 在Rust源代码中&#xff0c;rust/src/tools/rust-analyzer/crates/hir/src/lib.rs文件的作用是定义了Rust语言的高级抽象层次&#xff08;Higher-level IR&#xff0c;HIR&#xff09;。它包含了Rust语言的各种结构和…

智能优化算法应用:基于蜉蝣算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蜉蝣算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蜉蝣算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蜉蝣算法4.实验参数设定5.算法结果6.参考文献7.MA…

JAVA+SSM+springboot+MYSQL企业物资库存进销存管理系统

。该系统从两个对象&#xff1a;由管理员和员工来对系统进行设计构建。主要功能包括首页、个人中心、员工管理、项目信息管理、仓库信息管理、供应商管理、项目计划管理、物资库存管理、到货登记管理、物资出库管理、物资入库管理等功能进行管理。本企业物资管理系统方便员工快…

linux 定时任务

使用 crontab Usage: crontab [-u user] [-e|-l|-r] Crontab 的格式说明如下: * 逗号(‘,’) 指定列表值。如: “1,3,4,7,8″ * 中横线(‘-’) 指定范围值 如 “1-6″, 代表 “1,2,3,4,5,6″ * 星号 (‘*’) 代表所有可能的值 */15 表示每 15 分钟执行一次 # Use the ha…

C++编程法则365天一天一条(24)RTTI运行时类型信息typeid和type_info

文章目录 基本用法编译时或运行时判定 基本用法 typeid 是 C 的一个运算符&#xff0c;它用于获取表达式的类型信息。它返回一个 std::type_info 对象引用&#xff0c;该对象包含有关表达式的类型的信息。 要使用 typeid 运算符&#xff0c;需要包含 <typeinfo> 头文件…

关于振动试验

这是试验的说明&#xff08;来自gbt4710-2009&#xff09; 这是试验的参数&#xff1a; 一、试验方向&#xff1a; 振动试验中有几个方向 除有关规范另有规定外&#xff0c;应在产品的三个互相垂直方向上进行振动试验。 一般定义产品长边为X轴向&#xff0c;短边为Y轴向&…

idea中run和debug是灰色的

【现象】idea中run和debug是灰色的 点击 旁边的Add Configuration…一看都是空白 【解决方法】&#xff1a; npm点开之后 【结果】

转换NC或HDF数据时候转出数据无坐标信息的处理方法

有时候我们在转换NC或HDF数据时&#xff0c;有时候会出现没有坐标信息的情况&#xff01;如下图&#xff1a; 这种情况一般是原始数据将坐标信息存储在说明文件中以便后期做生成坐标信息的处理、或坐标存储的形式比较特殊&#xff0c;造成工具无法读取&#xff01;这种数据处理…