C++中智能指针的使用及其原理 -- RAII,内存泄漏,shared_ptr,unique_ptr,weak_ptr
目录
1.智能指针的使用场景分析
2.RAII和智能指针的设计思路
3.C++标准库智能指针的使用
4.智能指针的原理以及模拟实现
5.2weak_ptr的原理和部分接口
5.3weak_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的线程安…...
Linux服务器安装mongodb
因为项目需要做评论功能,领导要求使用mongodb,所以趁机多学习一下。 在服务器我们使用docker安装mongodb 1、拉取mongodb镜像 docker pull mongo (默认拉取最新的镜像) 如果你想指定版本可以这样 docker pull mongo:4.4&#…...
Android11修改摄像头前后置方法,触觉智能RK3568开发板演示
本文介绍在Android11系统下,修改摄像头前后置属性的方法。使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统&#…...
leetcode 212. 单词搜索 II
给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。 单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一…...
Taro 鸿蒙技术内幕系列(三) - 多语言场景下的通用事件系统设计
基于 Taro 打造的京东鸿蒙 APP 已跟随鸿蒙 Next 系统公测,本系列文章将深入解析 Taro 如何实现使用 React 开发高性能鸿蒙应用的技术内幕 背景 在鸿蒙生态系统中,虽然原生应用通常基于 ArkTS 实现,但在实际研发过程中发现,使用 C…...
《Docker Registry(镜像仓库)详解》
一、引言 在容器化技术日益普及的今天,Docker 已成为众多开发者和企业的首选工具。而 Docker Registry(镜像仓库)作为 Docker 生态系统中的重要组成部分,负责存储和分发 Docker 镜像。本文将深入探讨 Docker Registry 的概念、功能…...
AI前景分析展望——GPTo1 SoraAI
引言 人工智能(AI)领域的飞速发展已不仅仅局限于学术研究,它已渗透到各个行业,影响着从生产制造到创意产业的方方面面。在这场技术革新的浪潮中,一些领先的AI模型,像Sora和OpenAI的O1,凭借其强大…...
超级详细讲解转义字符,\? \‘ \f \0 \t等等!!!
C语言有一组特殊的字符称作转义字符,顾名思义,转变原来的意思 1 \? ??)是一个三字母词,在以前的编译器它会被编译为] (??会被编译为[ 因此在以前输入(are you ok ??)就会被编译为are you ok ] 解决这个问题只要在问号前输入\…...
微信小程序数据请求教程:GET与POST请求详解
微信小程序数据请求教程:GET与POST请求详解 引言 在微信小程序的开发过程中,数据请求是至关重要的一部分。通过与后端服务器进行通信,小程序能够获取动态数据,实现丰富的功能。在这篇文章中,我们将深入探讨微信小程序中的数据请求,重点介绍GET和POST请求的使用方法、示…...
Linux系统管理基础指南--习题
目录 一、基础知识与命令 二、 Linux的用户接口 三、文件权限与目录管理 四、shell相关知识 五、软件安装与网络 六、网络进程管理 一、基础知识与命令 1. (操作题)分别执行下述命令 ls -al cd ~ cd man -f man man –k cd man --help cal --help date --help bc --he…...
JVM(JAVA虚拟机)内存溢出导致内存不足,Java运行时环境无法继续
1、先贴出服务最后打印出来的日志,意思就是给虚拟机分配的内存被用完了,没有可用的内存了,服务运行不了了,被动停服了。详细的日志记录在了/home/user/zx/tomcat/apache-tomcat-8.5.82/bin/hs_err_pid147951.log文件里。 Java Ho…...
IOC控制反转详解
IOC(控制反转) component的衍生注解 前面曾经提到,若想要把某个对象交给IOC容器管理,就需要在其声明上加上Component注解。但是Spring中有三层架构,为了更加清晰的标注对象是属于哪一层的,提供了三个Comp…...
Qml-TabBar类使用
Qml-TabBar类使用 TabBar的概述 TabBar继承于Container 由TabButton进行填充,可以与提供currentIndex属性的任何容器或布局控件一起使用,如StackLayout 或 SwipeView;contentHeight : real:TabBar的内容高度,用于计算标签栏的隐…...
C# 常量
文章目录 前言一、整数常量(一)合法与非法实例对比(二)不同进制及类型示例 二、浮点常量三、字符常量四、字符串常量五、定义常量 前言 在 C# 编程的世界里,常量是一类特殊的数据元素,它们如同程序中的 “定…...
diffusion model: prompt-to-prompt 深度剖析
参考:diffusion model(十四): prompt-to-prompt 深度剖析-CSDN博客 P2P提出的Motivation 目前大火的文生图技术(text to image),给定一段文本(prompt)和随机种子,文生图模型会基于这两者生成一张图片。生…...
uniapp实现APP版本升级
App.vue 直接上代码 <script>export default {methods: {//APP 版本升级Urlupload() {// #ifdef APP-PLUSplus.runtime.getProperty(plus.runtime.appid, (info) > {// 版本号变量持久化存储getApp().globalData.version info.version;this.ToLoadUpdate(info.versi…...
uniapp强制修改radio-group内单选组件的状态方法
在uniapp开发中,需要在radio-group内部切换时做判断,提醒客户是否要变换radio的值,但是大家知道radio是单选组件,往往你点击后,是不能再修改状态的,就算你在点击后做判断,修改current的值&#…...
学习python的第十四天之函数——高阶函数和偏函数
学习python的第十四天之函数——高阶函数和偏函数 高阶函数 高阶函数是指那些可以接受一个或多个函数作为参数,或者返回一个函数作为结果的函数。高阶函数是函数式编程范式中的一个重要概念,它们使得代码更加灵活和模块化。 sorted() sorted()函数用于对…...
数据结构之二叉树详解:从原理到实现
1. 什么是二叉树? 二叉树(Binary Tree)是一种树形数据结构,其中每个节点最多有两个子节点,分别被称为左子节点和右子节点。二叉树可以用来表示层次关系,如文件目录、组织结构,或用于快速查找、…...
iOS 系统中使用 webView 打印 html 的打印边距问题
需求是使用系统提供的打印功能将HTML代码打印出来 1、使用CSS page 设置边距(iOS不生效) page {margin: 0;padding: 0;size: A6 portrait; }在 Android 中边距设置生效的,但是在 iOS 系统使用CSS page规则是不生效的 当从 iOS 系统打印网页…...
如何在ubuntu上调试core dump
启用core dump 确认ulimit 状态 ulimit -c 如果输出是0,表示core dump被禁用了 运行 ulimit -c unlimited 再次运行 ulimit -c 确认输出是ulimited 设置core dump路径和文件名格式 下面命令表示设置core dump文件在当前目录(%e表示程序名&#x…...
基于 JNI + Rust 实现一种高性能 Excel 导出方案(上篇)
每个不曾起舞的日子,都是对生命的辜负。 ——尼采 一、背景:Web 导出 Excel 的场景 Web 导出 Excel 功能在数据处理、分析和共享方面提供了极大的便利,是许多 Web 应用程序中的重要功能。以下是一些典型的场景: 数据报表导出&am…...
【Maven】依赖管理
4. Maven的依赖管理 在 Java 开发中,项目的依赖管理是一项重要任务。通过合理管理项目的依赖关系,我们可以有效的管理第三方库,模块的引用及版本控制。而 Maven 作为一个强大的构建工具和依赖管理工具,为我们提供了便捷的方式来管…...
springboot/ssm高校超市管理系统Java商品出入库供应商管理系统web源码wms
springboot/ssm高校超市管理系统Java商品出入库供应商管理系统web源码wms 基于springboot(可改ssm)vue项目 开发语言:Java 框架:springboot/可改ssm vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库&a…...
小程序-基于java+SpringBoot+Vue的微信小程序养老院系统设计与实现
项目运行 1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境:Tomcat 7.x,8.x,9.x版本均可 4.硬件环境:…...
宠物电商对接美团闪购:实现快速配送与用户增值
随着宠物行业的快速发展,宠物电商市场也在不断扩张。消费者的需求不再局限于传统的线上购物模式,越来越多的人开始追求更快捷的配送服务和更优质的购物体验。为了适应这一趋势,许多宠物电商平台开始寻求与本地配送平台合作,以提供…...
Vue中使用<Transition>与<TransitionGroup>
目录 介绍 CSS过渡类 为过渡效果命名 CSS的transition CSS的transform 性能考量 出现时过渡 元素间过渡 过渡模式 使用Key属性过渡 和的区别 进入/离开动画 移动动画 一个购物车飞跃例子 介绍 传统HTML中,我们可以使用CSS属性:“animation”…...
Algorithms and Data Structures in C++ by Mohammed Yasir Eramangadan
MP4 创建 |视频:h264、1280720 |音频:AAC,44.1 KHz,2 通道 类型:在线学习 |语言:英文 字幕 |持续时间: 159 讲座 ( 10h 43m ) |大小: 3.5 GB “通过专家制作…...
2024广东省职业技能大赛云计算——构建CICD 部署2048小游戏
构建CI/CD 前言 题目如下: 构建CI/CD 编写流水线脚本.gitlab-ci.yml触发自动构建,具体要求如下: (1)基于镜像maven:3.6-jdk-8构建项目的drone分支; (2)构建镜像的名称:…...
React 条件渲染
React 条件渲染 React 条件渲染是一种在 React 应用程序中根据不同的条件显示不同组件或内容的技巧。它是 React 响应用户输入、状态变化或数据变化的核心机制之一。本文将深入探讨 React 条件渲染的概念、用法和最佳实践。 目录 条件渲染的基本概念使用 JavaScript 运算符进…...
深圳大眼睛网站建设/b2b电子商务网站
SpringCloud(第 018 篇)Zuul 服务 API 网关微服务之代理与反向代理 - 一、大致介绍 1、API 服务网关顾名思义就是统一入口,类似 nginx、F5 等功能一样,统一代理控制请求入口,弱化各个微服务被客户端记忆功能࿱…...
政府单位网站建设改版方案/西安 做网站
golang 模板(template)的常用基本语法 模板 在写动态页面的网站的时候,我们常常将不变的部分提出成为模板,可变部分通过后端程序的渲染来生成动态网页,golang提供了html/template包来支持模板渲染。 这篇文章不讨论golang后端的模板读取及渲染…...
wordpress get_search_form()/什么是百度搜索推广
MMA7660加速计驱动 1、MMA7660介绍 MMA7660FC 是一款数字输出 IC、超低功耗、薄型电容式微加工加速度计,具有低通滤波器、零重力偏移和增益误差补偿以及用户可配置输出数据转换为六位数字值速度。 该器件可通过中断引脚 (INT) 用于传感器数据更改、产品方向和手势检测。 I2C…...
俱乐部网站模板/产品推广平台有哪些
刚刚偶然之间在书上看到了关于如何进行raid配置的内容,就顺便给大家截取下来了,大家有兴趣的可以看看...
中国建设工程监理网站/亚马逊查关键词搜索量的工具
文管王文书档案管理系统 属性资源版本:V8.2软件授权:共享软件软件类型:国产软件软件语言:简体中文应用平台:Winxp/vista/win7/2000/2003软件评分:5星软件大小:7.84MB文管王文书档案管理系统 下载…...
厦门集团网站建设/免费手机网页制作
Nginx的编译安装 #!/bin/bash#解决软件的依赖关系,需要安装的软件包 yum -y install zlib zlib-devel openssl openssl-devel pcre pcre-devel gcc gcc-c autoconf automake make #useradd ouzhe id ouzhe|| useradd ouzhe#download nginx mkdir -p /nginx cd /ng…...