当前位置: 首页 > news >正文

C++中智能指针的使用及其原理 -- RAII,内存泄漏,shared_ptr,unique_ptr,weak_ptr

目录

1.智能指针的使用场景分析

2.RAII和智能指针的设计思路

3.C++标准库智能指针的使用

4.智能指针的原理以及模拟实现 

5.shared_ptr循环引用问题和weak_ptr 

5.1shared_ptr循环引用问题

5.2weak_ptr的原理和部分接口

5.3weak_ptr的简单模拟实现

6. shared_ptr的线程安全问题

7.C++11和boost中智能指针的关系

8.内存泄漏

8.1内存泄漏的定义及其危害

8.2如何避免内存泄漏


1.智能指针的使用场景分析

        下⾯程序中我们可以看到,new了以后,我们需要delete,但是因为抛异常导致后⾯的delete没有得到执⾏,所以就内存泄漏了。所以我们需要new以后捕获异常,捕获到异常后delete掉申请的内存,再把异常重新抛出,但是因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起来很⿇烦。

#include <iostream>
using namespace std;double Divide(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Divide by zero condition!";}else{return (double)a / (double)b;}
}void Func()
{// 如果array2 = new int[10] 的时候抛异常就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案是智能指针int* array1 = new int[10];int* array2 = new int[10];try{int len, time;cin >> len >> time;cout << Divide(len, time) << endl;}catch(...){//捕获到异常之后也要释放申请的空间cout << "抛出异常后的delete []" << array1 << endl;cout << "抛出异常后的delete []" << array2 << endl;delete[] array1;delete[] array2;throw;	//异常重新抛出到外层}cout << "没有抛异常的delete []" << array1 << endl;delete[] array1;cout << "没有抛异常的delete []" << array2 << endl;delete[] array2;
}int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (const exception& e)	//捕捉标准库中的异常{cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}return 0;
}

2.RAII和智能指针的设计思路

        RAII是Resource Acquisition Is Initialization的缩写,是⼀种管理资源的类的设计思想,本质是⼀种利⽤对象⽣命周期来管理获取到的动态资源,避免资源泄漏,这⾥的资源可以是内存、⽂件指针、⽹络连接、互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,资源在对象的⽣命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。

        智能指针类除了满⾜RAII的设计思路,还要⽅便资源的访问,所以智能指针类还会像迭代器类⼀样,重载 operator*/operator->/operator[] 等运算符,方便访问资源。

        下面代码演示用智能指针类创建对象来管理申请的资源:

#include <iostream>
using namespace std;template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout << "delete[] " << _ptr << endl;delete[] _ptr;}//重载运算符,模拟指针的行为,方便访问资源T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t i){return _ptr[i];}
private:T* _ptr;
};double Divide(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Divide by zero condition!";}else{return (double)a / (double)b;}
}void Func()
{// 这⾥使⽤RAII的智能指针类管理new出来的数组SmartPtr<int> sp1 = new int[10];SmartPtr<int> sp2 = new int[10];for (size_t i = 0; i < 10; i++){sp1[i] = sp2[i] = i;}int len, time;cin >> len >> time;cout << Divide(len, time) << endl;
}int main()
{while (1){try{Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (const exception& e){cout << e.what() << endl;}catch (...){cout << "Unknow exception" << endl;}}return 0;
}

         上述代码不管是否抛出异常,都会在Func()函数结束的时候对内部的对象进行销毁,这样的话就会自动调用智能指针类的析构函数,对其指向的资源进行释放。

3.C++标准库智能指针的使用

     1.C++标准库中的智能指针都在<memory>这个头⽂件下面。智能指针有好⼏种,除了weak_ptr以外,其他的智能指针都符合RAII和像指针⼀样访问的⾏为,原理上主要是解决智能指针拷⻉时的思路不同。

        2.auto_ptr是C++98时设计出来的智能指针,他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给拷⻉对象,这是⼀个⾮常糟糕的设计,因为它会使被拷⻉对象悬空,访问时会报错,C++11设计出新的智能指针后,强烈建议不要使⽤auto_ptr。其实C++11出来之前很多公司也是明令禁⽌使⽤这个智能指针。

        这里先给出一个日期类的简单实现,后续的测试都使用这个类进行测试。

struct Date
{int _year;int _month;int _day;Date(int year = 1, int month = 1, int day = 1):_year(year),_month(month),_day(day){}~Date(){cout << "~Date()" << endl;}
};

        使用auto_ptr指针进行拷贝构造,则使得被拷贝的对象悬空,访问时会报错。

int main()
{auto_ptr<Date> ap1(new Date);//拷贝时,管理权限转移,被拷贝对象ap1悬空auto_ptr<Date> ap2(ap1);cout << ap1->_year << endl;;return 0;
}

        3.unique_ptr是C++11设计出来的智能指针,翻译出来是唯⼀指针,他的特点是不⽀持拷⻉,只⽀持移动。如果不需要拷⻉的场景就非常建议使⽤他。

int main()
{unique_ptr<Date> up1(new Date);//不支持拷贝//unique_ptr<Date> up2(up1);//支持移动,但是移动后up1也悬空,所以使用移动要谨慎//一般移动的都是将亡值,而不是对左值进行moveunique_ptr<Date> up3(move(up1));return 0;
}

        这里本质上只有一个Date对象被创建,所以只析构了一次。 

        4.shared_ptr也是C++11设计出来的智能指针,翻译出来是共享指针,他的特点是⽀持拷⻉,也⽀持移动。如果需要拷⻉的场景就需要使⽤他了。底层是用引用计数的方式实现的。

int main()
{shared_ptr<Date> sp1(new Date);//支持拷贝shared_ptr<Date> sp2(sp1);shared_ptr<Date> sp3(sp2);cout << sp1.use_count() << endl;sp1->_year++;cout << sp1->_year << endl;cout << sp2->_year << endl;cout << sp3->_year << endl;return 0;
}

        注:shared_ptr也支持移动,移动之后被移动的智能指针对象也会被悬空,所以使用移动要谨慎。

        5.weak_ptr是C++11设计出来的智能指针,翻译出来是弱指针,他完全不同于上⾯的智能指针,他不⽀持RAII,也就意味着不能⽤它直接管理资源,weak_ptr的产⽣本质是要解决shared_ptr的⼀个循环引⽤导致内存泄漏的问题。这个在后面第五节进行介绍。

        6.智能指针析构时默认是进⾏delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。智能指针支持在构造时给⼀个删除器,删除器本质就是⼀个可调用对象,这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调用删除器去释放资源。因为new[]经常使⽤,所以为了简洁⼀点,unique_ptr和shared_ptr都特化了⼀份[]的版本

        下列用智能指针管理 new 类型[] 申请的资源在析构的时候会报错:

int main()
{//智能指针析构的时候默认进行delete释放资源,如果是new出来的数组//智能指针析构的时候就会崩溃unique_ptr<Date> up1(new Date[10]);shared_ptr<Date> sp1(new Date[10]);return 0;
}

         解决方法1:因为new[]经常使⽤,所以unique_ptr和shared_ptr实现了⼀个特化版本,这个特化版本析构时⽤的delete[]。

        解决方法2: 使用一个可调用对象作为删除器,智能指针在析构的时候调用删除器按你想要的方式进行析构。

         综上所述:当unique_ptr需要使用删除器进行资源的释放时,推荐还是使用仿函数作为其删除器。

        下列代码表示实现其他资源管理的删除器:

        7.shared_ptr 除了⽀持⽤指向资源的指针构造,还⽀持 make_shared ⽤初始化资源对象的值直接构造。用make_shared构造出shared_ptr的好处是可以把资源的空间和引用计数开的空间开到一段连续的空间上,这样可以有效的防止内存碎片的问题。

int main()
{shared_ptr<Date> sp1(new Date(2024, 11, 20));//make_shared方式进行构造shared_ptr对象shared_ptr<Date> sp2 = make_shared<Date>(2024, 11, 25);return 0;
}

        8.shared_ptr 和 unique_ptr 都⽀持了operator bool的类型转换,如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

        9.shared_ptr 和 unique_ptr 构造函数都使⽤explicit 修饰,防⽌普通指针隐式类型转换成智能指针对象。不支持使用 " = "的方式进行拷贝构造。

// 报错
shared_ptr<Date> sp5 = new Date(2024, 9, 11);
unique_ptr<Date> sp6 = new Date(2024, 9, 11);

4.智能指针的原理以及模拟实现 

        下面模拟实现auto_otr和unique_ptr的核心功能,这两个智能指针的实现比较简单,了解即可。auto_ptr的思路是拷贝时转移资源管理权,但是不建议使用。unique_ptr的思路是不支持拷贝。

namespace xiaoc
{template<class T>class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}//拷贝构造 -- 简单的值拷贝auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){//管理权转移ap._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){//检查是否为自己给自己赋值if (this != &ap){//释放当前对象中的资源if (_ptr)delete _ptr;//转移sp中的资源到当前对象中_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}~auto_ptr(){if (_ptr){cout << "auto_ptr() delete:" << _ptr << endl;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};template<class T>class unique_ptr{public:explicit unique_ptr(T* ptr):_ptr(ptr){}//不支持拷贝,所以没有拷贝构造和拷贝赋值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;}~unique_ptr(){if (_ptr){cout << "unique_ptr() delete:" << _ptr << endl;delete _ptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

        重点是shared_ptr的模拟实现,尤其是引用计数的设计。这里一份资源就需要一个引用计数的变量来记录该资源被多少个shared_ptr管理。引用计数的本质就是记录一份相同的资源被多少个智能指针进行管理。

        错误实现1:在shared_ptr中添加一个int变量_count来记录次数,假如现在有一个shared_ptr对象sp1,管理一块资源,sp2是通过sp1拷贝构造生成的,拷贝构造的时候sp1中的_count先++然后拷贝到sp2中,这时候sp1和sp2中的_count都是2,现在析构sp2时,sp2._count为2,不释放空间,再析构sp1时,sp1._count也是2,这时两个对象都析构了,但是都没有释放空间,所以这样的实现也是不可取的。

        错误实现2:如果使用静态成员的方式实现引用计数,则两个shared_ptr分别管理两个不同的资源的时候,这时候期待的是每个shared_ptr中的引用计数为1,由于是静态成员变量,所以实际都是2,不符合期待的要求。

        正确实现:share_ptr对象中添加一个int*类型的指针pcount,在构造智能指针对象的时候在堆上开辟4个字节的空间,用来存放引用计数的数值,初始化为1。每个shared_ptr都用pcount指针指向这个空间,在拷贝构造或者拷贝赋值的时候,就执行(*pcount)++即可。析构shared_ptr的时候先判断count是否为1,不为1,则将(*pcount)--,为1则释放资源。

        这里直接给出简单的模拟实现的代码,重点关注拷贝构造和拷贝赋值以及析构调用的release()函数,具体细节看代码中的注释。

#include <functional>namespace xiaoc
{template<class T>class shared_ptr{public: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(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del){//将原来的引用计数拷贝过来,然后再++++(*_pcount);}void release(){//如果只有一个智能指针进行管理,释放资源//如果不止一个智能指针进行管理//if判断时就把*_pcount减1即可if (--(*_pcount) == 0){cout << "shared_ptr() delete:" << _ptr << endl;_del(_ptr);delete _pcount;_ptr = nullptr;_pcount = nullptr;}}shared_ptr<T>& operator=(const shared_ptr<T>& sp){//这里一定要判断是否是自己对自己进行赋值//如果不判断,当引用计数为1的时候进入release函数//先把资源释放掉,sp._ptr指向的资源为空,然后//自己再给自己赋值,这样最后自己就变成了空if (_ptr != sp._ptr){//先释放掉之前管理的资源release();_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);_del = sp._del;}return *this;}~shared_ptr(){release();}T* get() const{return _ptr;}int use_count() const{return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; };};
}

        这里的function是一个包装器,将其仿函数,函数指针,lambda表达式包装成一个可调用的对象,具体可以参考:

C++11语法介绍(2) -- 可变参数模板,default和delete,final和override,lambda表达式,包装器

int main()
{xiaoc::shared_ptr<Date> sp1(new Date);sp1 = sp1;	//检测当引用计数为1时,自己给自己赋值是否出错// ⽀持拷⻉xiaoc::shared_ptr<Date> sp2(sp1);xiaoc::shared_ptr<Date> sp3(sp2);cout << sp1.use_count() << endl;sp1->_year++;cout << sp1->_year << endl;cout << sp2->_year << endl;cout << sp3->_year << endl;return 0;
}

5.shared_ptr循环引用问题和weak_ptr 

5.1shared_ptr循环引用问题

        shared_ptr⼤多数情况下管理资源⾮常合适,⽀持RAII,也⽀持拷⻉。但是在循环引⽤的场景下会导致资源没得到释放而内存泄漏,所以要认识循环引⽤的场景和资源没释放的原因,并且⽤weak_ptr解决这种问题。

        如下图所述场景,用智能指针n1管理一个双链表的节点,用智能指针n2管理另一个双链表的节点,然后这两个节点连接起来,智能指针中的引用计数就会变为2。

        右边的节点什么时候释放呢,左边节点中的_next(一个shared_ptr对象)管着,_next析构后,右边的节点就释放了。_next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。左边节点什么时候释放呢,左边节点由右边节点中的_prev管着呢,_prev析构后,左边的节点就释放了。_prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。⾄此逻辑上形成回旋镖似的循环引⽤,谁都不会释放就形成了循环引⽤,导致内存泄漏。

struct ListNode
{int _data;shared_ptr<ListNode> _next;shared_ptr<ListNode> _prev;// 这⾥改成weak_ptr,当n1->_next = n2;绑定shared_ptr时// 不增加n2的引⽤计数,不参与资源释放的管理,就不会形成循环引⽤了/*std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;*/~ListNode(){cout << "~ListNode()" << endl;}
};int main()
{shared_ptr<ListNode> n1(new ListNode);shared_ptr<ListNode> n2(new ListNode);cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;return 0;
}

         这里程序结束,析构shared_ptr对象时,并没有调用ListNode的析构函数。

        main()函数也可以看作一个函数,当一个函数结束的时候会自动调用内部对象的析构函数,所以这里main()函数结束,调用了shared_ptr对象的析构函数,但是shared_ptr中没有调用ListNode的析构函数,所以这两个节点是没有释放的,导致内存泄漏。

        注:main()函数与其他函数的区别是main()函数结束之后,这个程序就结束了,程序结束就会自动回收内部申请的资源,所以内存泄漏是对于运行中的程序来说的。

        这里用原生指针不行,因为一个shared_ptr对象不能赋值给ListNode*指针,用shared_ptr又会导致循环引用的问题。所以把ListNode结构体中的_next和_prev改成weak_ptr,weak_ptr绑定到shared_ptr时不会增加它的引⽤计数,_next和_prev不参与资源释放管理逻辑,就成功打破了循环引用。下列是使用weak_ptr的结果:

5.2weak_ptr的原理和部分接口

        weak_ptr不⽀持RAII,也不⽀持访问资源,所以weak_ptr构造时不⽀持绑定到资源,只⽀持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。

        weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的。

        weak_ptr⽀持expired检查指向的资源是否过期,如果已过期,则返回true,没过期则返回false。use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

int main()
{shared_ptr<string> sp1(new string("xiaoc"));shared_ptr<string> sp2(sp1);weak_ptr<string> wp = sp1;cout << wp.expired() << endl;cout << wp.use_count() << endl;cout << endl;sp1 = shared_ptr<string>(new string("1111"));cout << wp.expired() << endl;cout << wp.use_count() << endl;cout << endl;sp2 = shared_ptr<string>(new string("2222"));cout << wp.expired() << endl;cout << wp.use_count() << endl;cout << endl;return 0;
}

5.3weak_ptr的简单模拟实现

namespace xiaoc
{template<class T>class weak_ptr{public:weak_ptr(){}//支持用shared_ptr进行构造weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}private:T* _ptr = nullptr;};
}

6. shared_ptr的线程安全问题

        shared_ptr的引⽤计数对象在堆上,如果多个shared_ptr对象在多个线程中,进⾏shared_ptr的拷⻉析构时会访问修改引⽤计数,就会存在线程安全问题,所以shared_ptr引⽤计数是需要加锁或者原⼦操作保证线程安全的。

        shared_ptr指向的对象也是有线程安全的问题的,但是这个对象的线程安全问题不归shared_ptr管,它也管不了,应该由外层使⽤shared_ptr的⼈进⾏线程安全的控制。

7.C++11和boost中智能指针的关系

        Boost库是为C++语⾔标准库提供扩展的⼀些C++程序库的总称,Boost社区建⽴的初衷之⼀就是为C++的标准化⼯作提供可供参考的实现。在Boost库的开发中,Boost社区也在这个⽅向上取得了丰硕的成果,C++11及之后的新语法和库有很多都是从Boost中来的。

        C++ boost给出了更实⽤的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等。C++ 11,引⼊了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

8.内存泄漏

8.1内存泄漏的定义及其危害

        内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失,⽽是应⽤程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。

        内存泄漏的危害:普通程序运⾏⼀会就结束了出现内存泄漏问题也不⼤,进程正常结束,⻚表的映射关系解除,物理内存也可以释放。⻓期运⾏的程序出现内存泄漏,影响很⼤,如操作系统、后台服务、⻓时间运⾏的客⼾端等等,不断出现内存泄漏会导致可⽤内存不断变少,各种功能响应越来越慢,最终卡死。

8.2如何避免内存泄漏

        1.⼯程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要智能指针来管理才有保证。

        2.尽量使⽤智能指针来管理资源,如果⾃⼰场景⽐较特殊,采⽤RAII思想⾃⼰造个轮⼦管理。

        3.定期使⽤内存泄漏⼯具检测。

        解决⽅案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测⼯具。

相关文章:

C++中智能指针的使用及其原理 -- RAII,内存泄漏,shared_ptr,unique_ptr,weak_ptr

目录 1.智能指针的使用场景分析 2.RAII和智能指针的设计思路 3.C标准库智能指针的使用 4.智能指针的原理以及模拟实现 5.shared_ptr循环引用问题和weak_ptr 5.1shared_ptr循环引用问题 5.2weak_ptr的原理和部分接口 5.3weak_ptr的简单模拟实现 6. shared_ptr的线程安…...

DICOM医学影像应用篇——伪彩色映射 在DICOM医学影像中的应用详解

目录 引言 伪彩色映射的概念 基本原理 查找表&#xff08;Look-Up Table, LUT&#xff09; 步骤 示例映射方案 实现伪彩色映射的C代码 代码详解 伪彩色处理效果展示 总结 扩展知识 LUT 的基本概念 LUT 在伪彩色映射中的应用 示例 引言 在医学影像处理中&#xff0c…...

注意http-proxy-middleware要解决跨域问题,想修改origin请求头不要设置changeOrigin=true

在使用http-proxy-middleware的时候&#xff0c;有一个配置是“changeOrigin”&#xff0c;通过名字来看这个字段是用来控制是否修改origin的&#xff0c;但是实际使用下来&#xff0c;你会发现&#xff0c;当设置为true的时候&#xff0c;header中的origin的值并不会修改&…...

SpringBoot宠物领养平台:设计与实现

摘 要 如今社会上各行各业&#xff0c;都在用属于自己专用的软件来进行工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。互联网的发展&#xff0c;离不开一些新的技术&#xff0c;而新技术的产生往往是为了解决现有问题而产生的。针对于宠物领养…...

qt 的udp发送和接收

udp要在.pro中加入 QT network udp接收 //QUdpSocket *udp1; udp1 new QUdpSocket(this); udp1->bind(2024,QUdpSocket::ShareAddress); connect(udp1,SIGNAL(readyRead()),this,SLOT(ReadyOut())); void MainWindow::ReadyOut() { while(udp1->hasPend…...

极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【五】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…...

中科亿海微SoM模组——波控处理软硬一体解决方案

本文介绍的波控处理软硬一体解决方案主要是面向相控阵天线控制领域&#xff0c;波控处理通过控制不同天线组件的幅相来调整天线波束的方向和增益&#xff0c;实现高精度角度控制和高增益。本方案由波控处理板、波控处理控制软件算法和上位机软件共同构成。波控处理SoM模组原型样…...

vue页面跟数据不同步this.$set

“Vue页面跟数据同步this.$set”可能是指在某些情况下&#xff0c;需要确保数据的响应式特性&#xff0c;即当数据发生变化时&#xff0c;页面上的显示也能实时更新。 如果你遇到数据已经设置&#xff0c;但页面没有更新&#xff0c;可能是因为你没有正确使用 Vue 的响应式系统…...

黑马2024AI+JavaWeb开发入门Day02-JS-VUE飞书作业

视频地址&#xff1a;哔哩哔哩 讲义作业飞书地址&#xff1a;飞书 一、作业1 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge">&l…...

(0基础保姆教程)-JavaEE开课啦!--12课程(Spring MVC注解 + Vue2.0 + Mybatis)-实验10

一、常见的SpringMVC注解有哪些&#xff1f; 1.Controller&#xff1a;用于声明一个类为 Spring MVC 控制器。 2.RequestMapping&#xff1a;用于将 HTTP 请求映射到特定的处理方法上。可以指定请求类型&#xff08;GET、POST等&#xff09;和URL路径。 3.GetMapping&#xff…...

未成年人模式护航,保障安全健康上网

为保护未成年人的上网环境&#xff0c;预防未成年人沉迷网络&#xff0c;帮助未成年人培养积极健康的用网习惯&#xff0c;HarmonyOS SDK 提供未成年人模式功能&#xff0c;在华为设备上加强对面向未成年人的产品和服务的管理。 场景介绍&#xff08;应用跟随系统未成年人模式…...

【实体配置】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列 &#x1f…...

MySQL中Update在什么情况下行锁会升级成表锁

MySQL中Update在什么情况下行锁会升级成表锁 在MySQL中&#xff0c;特别是使用InnoDB存储引擎时&#xff0c;行锁&#xff08;row-level locking&#xff09;通常用于提高并发性能。然而&#xff0c;在某些特定情况下&#xff0c;行锁可能会升级为表锁&#xff08;table-level…...

应急响应靶机——easy溯源

载入虚拟机&#xff0c;开启虚拟机&#xff1a; &#xff08;账户密码&#xff1a;zgsfsys/zgsfsys&#xff09; 解题程序.exe是额外下载解压得到的&#xff1a; 1. 攻击者内网跳板机IP地址 2. 攻击者服务器地址 3. 存在漏洞的服务(提示:7个字符) 4. 攻击者留下的flag(格式…...

使用Compose Multiplatform开发跨平台的Android调试工具

背景 最近对CMP跨平台很感兴趣&#xff0c;为了练手&#xff0c;在移动端做了一个Android和IOS共享UI和逻辑代码的天气软件&#xff0c;简单适配了一下双端的深浅主题切换&#xff0c;网络状态监测&#xff0c;刷新调用振动器接口。 做了两年多车机Android开发&#xff0c;偶…...

LabVIEW实现TCP通信

目录 1、TCP通信原理 2、硬件环境部署 3、云端环境部署 4、TCP通信函数 5、程序架构 6、前面板设计 7、程序框图设计 8、测试验证 本专栏以LabVIEW为开发平台&#xff0c;讲解物联网通信组网原理与开发方法&#xff0c;覆盖RS232、TCP、MQTT、蓝牙、Wi-Fi、NB-IoT等协议。 结合…...

Realtek网卡MAC刷新工具PG8168.exe Version:2.34.0.4使用说明

本刷新工具虽然文件名叫PG8168.EXE&#xff0c;但不是只有RTL8168可用&#xff0c;是这一个系列的产品都可以使用。实验证明RTL8111也可以使用。 用法&#xff1a; PG8168 [/h][/?][/b][/c HexOffsetHexValue][/d NICNumber][/l][/r][/w][/v] [/# NICNumber] [/nodeidHexNOD…...

【maven】配置下载私有仓库的快照版本

1、setting.xml配置 <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/SETTINGS/1.0.0https://maven.apache.org/xsd/settings-1.0.0.…...

基于pytorch使用仿真数据集来训练一个深度学习模型进行相位解包裹

使用 PyTorch 来训练一个深度学习模型进行相位解包裹是一种常见的方法。下面是一个详细的示例&#xff0c;展示如何生成仿真数据集并在 PyTorch 中训练模型。 1. 生成仿真数据集 首先&#xff0c;我们生成一些仿真数据集&#xff0c;包含多个包裹相位图和对应的解包裹相位图。…...

【排序算法】之快速排序篇

思想&#xff1a; 分而治之&#xff0c;通过选定某一个元素作为基准值&#xff0c;将序列分为两部分&#xff0c;左边的序列小于基准值&#xff0c;右边的序列大于基准值&#xff0c; 然后再分别将左序列和右序列进行递归排序&#xff0c;直至每部分有序。 性质&#xff1a;这…...

WebSocket

握手 1 客户端发起握手请求&#xff1a;客户端向服务器发送一个特殊的HTTP请求&#xff0c;其中包含一个Upgrade字段&#xff0c;表明客户端希望将该连接从HTTP协议升级为WebSocket协议。请求的关键部分包括&#xff1a; GET请求&#xff1a;客户端使用GET方法请求与WebSocket…...

适配器模式

适配器模式&#xff08;Adapter Pattern&#xff09;详解 定义 适配器模式是一种结构型设计模式&#xff0c;通过将一个类的接口转换为客户期望的另一个接口&#xff0c;使得原本接口不兼容的类可以一起工作。适配器模式又称“包装器&#xff08;Wrapper&#xff09;”。 适配…...

Jmeter最新详细安装及修改中文教程(附安装包)

目录 初识&#xff1a;Jmeter 一、下载&#xff1a;Jmeter 二、安装前必要的配置 1.桌面点击菜单栏搜索【cmd】&#xff0c;然后打开命令提示符 2.输入java -version命令 三、安装&#xff1a;Jmeter 1.首先在D盘创建【Jmeter】文件夹&#xff0c;把下载的【Jmeter】压缩…...

Java 语言的起源发展与基本概念(JDK,JRE,JVM)

Java语言的起源 源起 Java语言最初是由Sun Microsystems公司&#xff08;该公司于2009年被Oracle公司收购&#xff09;开发的一种编程语言。其创造者是詹姆斯高斯林&#xff08;James Gosling&#xff09;&#xff0c;他是一位加拿大计算机科学家。其前身名为Oak&#xff08;橡…...

利用dockerCompose一键部署前后端分离项目

1.Docker Compose介绍 2.将自己准备好的docker-compose.yml文件上传到宿主机 3.查看docker-compose.yml文件 宿主机的文件内容可参考&#xff1a; 项目部署-通过docker手动部署前后端分离项目&#xff08;全网超级详细 的教程&#xff09;-CSDN博客 修改宿主机的nginx.conf …...

redis大key和热key

redis中大key、热key 什么是大key大key可能产生的原因大key可能会造成什么影响如何检测大key如何优化删除大key时可能的问题删除大key的策略 热key热key可能导致的问题解决热key的方法 什么是大key 大key通常是指占用内存空间过大或包含大量元素的键值对。 数据量大&#xff…...

在 Linux 系统中根据pid查找软件位置

在 Linux 系统中,如果您知道一个进程的 PID(进程标识符),并且想要找到该进程对应的可执行文件的位置,可以使用以下几种方法: 方法一:使用 ps 命令 ps 命令可以显示进程的详细信息,包括可执行文件的路径。假设您的 PID 是 1234,可以使用以下命令: ps -p 1234 -o co…...

Python开发环境搭建+conda管理环境

下载Miniconda 推荐从清华镜像下载安装包 Index of /anaconda/miniconda/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 打开网页后&#xff0c;下拉到最后找到Miniconda3-latest前缀的文件&#xff0c;或者网页中直接搜索Miniconda3-latest&#xff0c;都可以找…...

Java 8新特性详解与实战

目录 引言 1. Lambda 表达式&#xff08;Lambda Expressions&#xff09; 2. 函数式接口&#xff08;Functional Interfaces&#xff09; 3. 流 API&#xff08;Stream API&#xff09; 4. 默认方法&#xff08;Default Methods&#xff09; 5. Optional 类 6. 新的时间日…...

K8s内存溢出问题剖析:排查与解决方案

文章目录 一、背景二、排查方案&#xff1a;1. 可能是数据量超出了限制的大小&#xff0c;检查数据目录大小2. 查看是否是内存溢出2.1 排查数据量&#xff08;查看数据目录大小是否超过limit限制&#xff09;2.2 查看pod详情发现问题 三、解决过程 一、背景 做redis压测过程中…...

Network Link Conditioner Mac 上模拟网络环境工具的安装和使用

前言 Xcode 的模拟器本身是不支持模拟网络环境的&#xff0c;在开发界面的时候&#xff0c;设计会出无网、弱网这种情况的设计图&#xff0c;为了方便在开发过程中实现这些情况的代码逻辑&#xff0c;Network Link Conditioner 就是模拟网络环境的好帮手。 安装 Network Lin…...

SeggisV1.0 遥感影像分割软件【源代码】讲解

在此基础上进行二次开发&#xff0c;开发自己的软件&#xff0c;例如&#xff1a;【1】无人机及个人私有影像识别【2】离线使用【3】变化监测模型集成【4】个人私有分割模型集成等等&#xff0c;不管是您用来个人学习 还是公司研发需求&#xff0c;都相当合适&#xff0c;包您满…...

电子应用设计方案-27:智能淋浴系统方案设计

智能淋浴系统方案设计 一、系统概述 本智能淋浴系统旨在为用户提供舒适、便捷、个性化的淋浴体验&#xff0c;通过集成多种智能技术&#xff0c;实现水温、水流、淋浴模式的精准控制以及与其他智能家居设备的联动。 二、系统组成 1. 喷头及淋浴杆 - 采用可调节角度和高度的设计…...

旋转图像(java)

题目描述&#xff1a; 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 代码思路&#xff1a; class Solution {public void ro…...

单片机知识总结(完整)

1、单片机概述 1.1. 单片机的定义与分类 定义&#xff1a; 单片机&#xff08;Microcontroller Unit&#xff0c;简称MCU&#xff09;是一种将微处理器、存储器&#xff08;包括程序存储器和数据存储器&#xff09;、输入/输出接口和其他必要的功能模块集成在单个芯片上的微型…...

蓝桥杯备赛笔记(一)

这里的笔记是关于蓝桥杯关键知识点的记录&#xff0c;有别于基础语法&#xff0c;很多内容只要求会用就行&#xff0c;无需深入掌握。 文章目录 前言一、编程基础1.1 C基础格式和版本选择1.2 输入输出cin和cout&#xff1a; 1.3 string以下是字符串的一些简介&#xff1a;字符串…...

Spring Boot【四】

单例bean中使用多例bean 1.lookup-method方式实现 当serviceB中调用getServiceA的时候&#xff0c;系统自动将这个方法拦截&#xff0c;然后去spring容器中查找对应的serviceA对象然后返回 2.replaced-method&#xff1a;方法替换 我们可以对serviceB这个bean中的getServiceA…...

linux基础1

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…...

DAMODEL丹摩|部署FLUX.1+ComfyUI实战教程

本文仅做测评体验&#xff0c;非广告。 文章目录 1. FLUX.1简介2. 实战2. 1 创建资源2. 1 ComfyUI的部署操作2. 3 部署FLUX.1 3. 测试5. 释放资源4. 结语 1. FLUX.1简介 FLUX.1是由黑森林实验室&#xff08;Black Forest Labs&#xff09;开发的开源AI图像生成模型。它拥有12…...

Python语法基础(三)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 我们这篇文章来说一下函数的返回值和匿名函数 函数的返回值 我们先来看下面的这一段函数的定义代码 # 1、返回值的意义 def func1():print(111111111------start)num166print…...

计算分数的浮点数值

计算分数的浮点数值 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 两个整数a和b分别作为分子和分母&#xff0c;既分数 a/b &#xff0c;求它的浮点数值&#xff08;双精度浮点数&#xff0c;保留小数点…...

Staircase mesh” 和 Conformal mesh区别

一、Staircase Mesh&#xff08;阶梯状网格&#xff09; 1.1 含义 阶梯状网格就像是用一个个小方块或者矩形拼接起来的网格。在对几何形状进行划分网格时&#xff0c;它会以一种比较简单直接的方式&#xff0c;使得网格边界呈现出像楼梯台阶一样的形状。比如在模拟一个圆形物体…...

探索未来工业的核心:数字孪生技术深度解析

经过数十年的发展&#xff0c;建模和模拟已成为工程和科学的基石。人们针对改进建模的计算方法进行了大量的研究和开发工作。这些计算机模型对系统设计非常有用&#xff0c;可以削减实验和测试的高昂成本。然而在实操中&#xff0c;还需要跟踪系统随时间的演变情况&#xff0c;…...

dns 服务器简单介绍

dns 服务器分类&#xff1a; 根域名服务器顶级域名服务器权威域名服务器本地域名服务器 dns 的查询过程 国内优秀公共域名 腾讯&#xff1a;DNSPod-免费智能DNS解析服务商-电信_网通_教育网,智能DNS-烟台帝思普网络科技有限公司 119.29.29.29 和 182.254.118.118 阿里&#xf…...

SQL基础入门——C++与SQL连接实践

在开发中&#xff0c;C与SQL数据库的连接和交互是非常常见的需求。通过将C与SQL数据库连接起来&#xff0c;我们可以轻松地执行数据存取、查询、更新等操作。C与数据库的集成通常依赖于数据库的连接器或驱动程序&#xff0c;本章节将详细讲解如何在C中使用MySQL Connector与SQL…...

对max_seq_length参数的理解,基于open-instruct框架:中英文解释

使用open-instruct (https://github.com/allenai/open-instruct )框架&#xff0c;对其中的max_seq_length参数的理解记录下来。 bash脚本内容如下&#xff1a; # 设置模型和训练参数 MODEL_NAMEgoogle/gemma-2-2b MACHINE_RANK0 MAIN_PROCESS_IP127.0.0.1 MAIN_PROCESS_PORT2…...

似然分布(Likelihood Distribution)和似然函数(Likelihood Function)有什么区别?中英双语

中文版 在统计学中&#xff0c;似然分布&#xff08;Likelihood Distribution&#xff09;和似然函数&#xff08;Likelihood Function&#xff09;是两个相关但有所不同的概念。它们都涉及给定参数的情况下&#xff0c;数据出现的概率&#xff0c;但是它们的使用方式和含义有…...

LINUX2.4.x网络安全框架

在分析LINUX2.4.x网络安全的实现之前先简介一下它里面包括的几个重要概念&#xff1a;netfilter、iptables、match、target、nf_sockopt_ops、网络安全功能点的实现。详解会在后面的分析中讲到。 首先是netfilter&#xff0c;它定义了协议栈中的检查点和在检查点上引用的数据结…...

Python毕业设计选题:基于django+vue的智能停车系统的设计与实现

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 车主管理 车辆信息管理 车位信息管理 车位类型管理 系统…...

AI界的信仰危机:单靠“规模化”智能增长的假设,正在面临挑战

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...