【C++学习笔记】RAII思想——智能指针
智能指针
- 1 内存泄漏问题
- 2 RAII(Resource Acquisition Is Initialization)
- 2.1 使用RAII思想设计的SmartPtr类
- 2.2 智能指针的原理
- 2.3 小总结智能指针原理
- 3 智能指针的拷贝问题
- 3.1 std::auto_ptr
- 3.2 std::unique_ptr
- 3.3 std::shared_ptr
- 3.3.1 拷贝构造函数
- 3.3.2 赋值函数
- 3.4 std::shared_ptr的线程安全问题
- 3.4 .1智能指针引用计数操作加锁
- 3.4.2 外界内存资源是非线程安全的
- 3.5 std::shared_ptr 引发的循环引用问题
- 4 std::weak_ptr 解决循环引用问题
- 4.1 weak_ptr解决循环引用的原理
- 4.2 简易版weak_ptr的实现
- 5 删除器
- 6 C++11和boost中智能指针的关系
- 7 总结
1 内存泄漏问题
在讲智能指针之前,我们先来看下面这段代码有什么问题?
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;
}int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
这段代码有内存泄漏问题。
1.对p1来说,如果new空间失败了,它会抛出异常直接跳转到main函数进行处理。所以,如果只是p1抛异常不会造成内存泄漏问题。
2.如果p1申请空间成功,对p2来说,如果new空间失败了,它也会抛出异常直接跳转到main函数进行处理,但是p1的内存空间并没有得到释放,会造成内存泄漏问题。
未使用智能指针的解决方式如下:
int* p1 = new int;int* p2 = nullptr;try{p2 = new int;}catch(...)//表示捕获任意异常{delete p1;//p2抛异常的话,在这里释放掉p1占的内存throw;//重新抛出异常给main函数}cout << div() << endl;delete p1;delete p2;
3.假如p1,p2都申请内存成功,并未抛出异常。但是div()函数抛出了异常,直接跳转到main函数,会导致p1和p2的内存空间都没有释放,造成内存泄漏。
未使用智能指针的解决方式如下:
int* p1 = new int;int* p2 = nullptr;try{p2 = new int;}catch(...)//表示捕获任意异常{delete p1;throw;}try {cout << div() << endl;}catch (...){delete p1;delete p2;throw;}delete p1;delete p2;
}
以上的的解决方法虽然解决了抛异常可能会造成的内存泄漏问题,但是从代码上来说太过繁琐,不够优雅。
可以用RAII的思想来解决问题。
2 RAII(Resource Acquisition Is Initialization)
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
1.不需要显式地释放资源。
2.采用这种方式,对象所需的资源在其生命期内始终保持有效。
2.1 使用RAII思想设计的SmartPtr类
首先先设计一个SmartPtr类,类中有构造函数和析构函数,如果想申请空间和销毁空间,分别交给构造函数和析构函数即可。
template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}
private:T* _ptr;
};
完整代码如下:
template <class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}
private:T* _ptr;
};
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{SmartPtr<int> p1(new int);//申请空间,因为有析构函数出了作用域自动释放SmartPtr<int> p2(new int);cout << div() << endl;
}int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
2.2 智能指针的原理
上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。
template <class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
使用案例:
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
struct Date //为了方便演示定义一个日期类
{int year;int month;int day;
};
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{SmartPtr<int> p1(new int);*p1 = 10;cout << *p1 << endl;SmartPtr<Date> p2(new Date);//这里本来应该是p2.operator->()->year=2023;//语法上可以省略括号:p2->->year=2023;//为了语法可读性,可以写成p2->year=2023;p2->year = 2023;p2->month = 7;p2->day = 4;cout << p2->year << " " << p2->month << " " << p2->day << " " << endl;cout << div() << endl;
}int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
2.3 小总结智能指针原理
RAII特性
重载operator*和opertaor->,具有像指针一样的行为。
3 智能指针的拷贝问题
在智能指针的基础上,来看一下下面这段代码:
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
int main()
{SmartPtr<int> sp1(new int(1));SmartPtr<int> sp2(sp1);return 0;
}
运行该代码,程序会直接崩溃,原因是同一块空间,析构了两次。
原因是这里的默认的拷贝构造函数是浅拷贝,但是这里不能用深拷贝来解决问题,因为C++原本的指针赋值就是浅拷贝。
C++提供以下这些库来解决浅拷贝问题
3.1 std::auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原理
std::auto_ptr官方文档
// C++98 管理权转移 auto_ptrtemplate<class T>class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){// 管理权转移sp._ptr = nullptr;//把被拷贝的指针置空了}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};// 结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
int main()
{std::auto_ptr<int> sp1(new int);std::auto_ptr<int> sp2(sp1); // 管理权转移
// sp1被置空*sp2 = 10;cout << *sp2 << endl;cout << *sp1 << endl;return 0;
//}
主要的思想就是为了防止浅拷贝带来的重复析构同一块空间问题,auto_ptr这个库在处理的时候,拷贝完成后,就把被拷贝的指针给置空了,这样就不会出现重复析构了问题了。
但是这种方式显然是不合理的。
接下来重点介绍以下三个库
3.2 std::unique_ptr
C++11中开始提供更靠谱的unique_ptr
std::unique_ptr官方文档
C++11库才更新智能指针实现
C++11出来之前,boost搞出了更好用scoped_ptr/shared_ptr/weak_ptr
C++11将boost库中智能指针精华部分吸收了过来C++11->unique_ptr/shared_ptr/weak_ptr
unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理
template<class T>class unique_ptr{
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}//因为类里面会默认写出拷贝构造函数和赋值函数,//所以这里要手动声明一下//后面加个 =delete 就是防止被拷贝的意思unique_ptr(const unique_ptr<T>&sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;
private:T* _ptr;
};
这里直接用库里的测试一下
确实是不让拷贝
3.3 std::shared_ptr
C++11中开始提供更靠谱的并且支持拷贝的shared_ptr
std::shared_ptr官方文档
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
由于要使用到引用计数,如果有多个对象指向同一块空间,这里我们叫做空间a,需要统计有几个对象。
这里采用的方案是再开辟一块空间给引用计数,这块空间我们叫做空间b,当空间a被一个对象指向时,空间b就计数++,当一个对象不再指向该空间时,空间b就减1。当减到0时,说明这是最后一个对象指向空间a了,可以析构了。这样就避免了重复析构的问题。
如下图演示:
当有新增对象指向_ptr时,_count++;
当sp2不再指向_ptr时,_count-=1;
当sp1不再指向_ptr时,_count-=1,此时_count==0,可以析构了。
3.3.1 拷贝构造函数
这里拷贝构造函数代码分析如上图所示
template<class T>class my_shared_ptr{public:my_shared_ptr(T* ptr):_ptr(ptr),_count(new int(1)){}my_shared_ptr(const my_shared_ptr<T>& sp):_ptr(sp._ptr),_count(sp._count){add();//(*_count)++}void release()//--(*_count)并判断是否等于0{if (--(*_count) == 0){delete _ptr;delete _count;}}void add(){(*_count)++;}~my_shared_ptr(){release();//--(*_count)==0,才能析构}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _count;//开辟一块引用计数的空间};
}
3.3.2 赋值函数
假如现在sp1,sp2,sp3指向了同一块空间,sp4指向另一空间。
现在要求赋值
sp1=sp4或者sp4=sp1
分析如下:
sp1=sp4
就是说sp1指向了sp4的空间。那么原来sp1指向的_count就要减去1
然后把sp1指向sp4即可,同时右边的_count要++
sp4=sp1
就是sp4指向的空间改为sp1指向的空间,然后sp4的_count-=1,由于_count==0,所以要析构掉图片右边的两个空间。左边_count++
代码如下:
my_shared_ptr<T>& operator=(my_shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_count = sp._count;add();}return *this;}void release(){if (--(*_count) == 0){delete _ptr;delete _count;}}void add(){(*_count)++;}
3.4 std::shared_ptr的线程安全问题
接下来测试shared_ptr的线程安全问题。需要注意的是shared_ptr的线程安全分为两方面:
- 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时
++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错
乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁
的,也就是说引用计数的操作是线程安全的。- 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
3.4 .1智能指针引用计数操作加锁
namespace ffg
{template<class T>class my_shared_ptr{public:my_shared_ptr(T* ptr):_ptr(ptr),_count(new int(1)),_mtx(new mutex){}my_shared_ptr(const my_shared_ptr<T>& sp):_ptr(sp._ptr),_count(sp._count),_mtx(sp._mtx){add();}my_shared_ptr<T>& operator=(my_shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_count = sp._count;_mtx = sp._mtx;add();}return *this;}void release()//凡是涉及到++,--的操作都需要加锁处理。注意这里锁的内存空间释放问题{//不能提前delete _mtx,因为_count可能还大于0_mtx->lock();bool flag = false;if (--(*_count) == 0){delete _ptr;delete _count;flag = true;}_mtx->unlock();if (flag){delete _mtx;}}void add()//加锁{_mtx->lock();(*_count)++;_mtx->unlock();}~my_shared_ptr(){release();}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count(){return *_count;}private:T* _ptr;int* _count;mutex* _mtx;};
}
测试代码:
3.4.2 外界内存资源是非线程安全的
外界资源未加锁前:
struct Date
{int _year = 0;int _month = 0;int _day = 0;
};
void SharePtrFunc(ffg::my_shared_ptr<Date>& sp, size_t n, mutex& mtx)
{cout << sp.get() << endl;for (size_t i = 0; i < n; ++i){// 这里智能指针拷贝会++计数,智能指针析构会--计数,加锁了,这里是线程安全的。//但是管理的资源并不是线程安全的,外界需要自己加锁处理,所以不是线程安全的。ffg::my_shared_ptr<Date> copy(sp);//mtx.lock();sp->_year++;sp->_month++;sp->_day++;//mtx.unlock();}
}
int main()
{ffg::my_shared_ptr<Date> p(new Date);cout << p.get() << endl;const size_t n = 10000;mutex mtx;thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx));thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx));t1.join();t2.join();cout << p->_year << endl;cout << p->_month << endl;cout << p->_day << endl;cout << p.use_count() << endl;return 0;
}
这里的输出应该p->_year,p->_month,p->_day都应该是20000,但是由于线程安全问题,可能会导致结果出现错误。
所以,需要程序员在外界加上锁。
void SharePtrFunc(ffg::my_shared_ptr<Date>& sp, size_t n, mutex& mtx)
{cout << sp.get() << endl;for (size_t i = 0; i < n; ++i){// 这里智能指针拷贝会++计数,智能指针析构会--计数,加锁了,这里是线程安全的。//但是管理的资源并不是线程安全的,外界需要自己加锁处理,所以不是线程安全的。ffg::my_shared_ptr<Date> copy(sp);mtx.lock();sp->_year++;sp->_month++;sp->_day++;mtx.unlock();}
}
int main()
{ffg::my_shared_ptr<Date> p(new Date);cout << p.get() << endl;const size_t n = 10000;mutex mtx;thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx));thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx));t1.join();t2.join();cout << p->_year << endl;cout << p->_month << endl;cout << p->_day << endl;cout << p.use_count() << endl;return 0;
}
多次执行:
直接使用C++库的shared_ptr也同理。
3.5 std::shared_ptr 引发的循环引用问题
先来看以下这两段代码:
建立了两个链表节点,n1,n2相互连接
struct ListNode
{ffg::my_shared_ptr<ListNode> _pre;ffg::my_shared_ptr<ListNode> _next;~ListNode(){cout << "~ListNode" << endl;}
};int main()
{ffg::my_shared_ptr<ListNode> n1(new ListNode);ffg::my_shared_ptr<ListNode> n2(new ListNode);n1->_next = n2;//这里注释掉任意一行都能成功析构//n2->_pre = n1;return 0;
}
上面这段代码,在main函数里的
当有n1->_next=n2,或者n2->_pre=n1,不同时存在的时候n1,n2就能成功析构。
但是当这两行代码同时存在的时候,就不能成功析构。
先来分析以下第一种情况
这里假设是
n1->_next = n2;
如下图,当n2和左边的_next 指向右边的ListNode空间时,引用计数为2
然后n2析构了以后,右边的引用计数为1。
接下来到n1析构,引用计数为0,左边的_next随着ListNode的析构也被释放掉了,所以右边的引用计数也变为0,右边的ListNode也被析构了,_prve也不存在了。
接下来分析第二种情况,也就是循环引用的问题
n1->_next = n2;
n2->_pre = n1;
首先是n2先析构,右边的引用计数变为1
接下来是n1析构,左边的引用计数变为1
此时循环引用问题就来了。
n2和n1析构,引用计数都减到1,但是_next还是指向下一个节点,_prve还是指向上一个节点。
也就是说_next析构了,右边的ListNode就能释放,右边的_prev也能释放。
_prev析构了,左边的ListNode就能释放,左边的_next也能释放掉。
所以这两个ListNode互相牵制了,谁也不会释放。
左边的释放条件是右边释放,右边释放条件是左边释放。相互矛盾了。
这就是循环引用问题。
4 std::weak_ptr 解决循环引用问题
注意:
1.weak_ptr是专门设计出来,解决shard_ptr的循环引用问题。
2.它不是常规的智能指针,不支持RAII,不支持资源管理
3.支持像指针一样的功能
这里先使用库里面的weak_ptr看看效果
成功解决了循环引用问题。
4.1 weak_ptr解决循环引用的原理
原理很简单,就是:n1->_next = n2;和n2->_prev = n1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
4.2 简易版weak_ptr的实现
class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const my_shared_ptr<T>& sp):_ptr(sp.get()){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;};
}
测试:
struct ListNode
{ffg::weak_ptr<ListNode> _pre;ffg::weak_ptr<ListNode> _next;~ListNode(){cout << "~ListNode" << endl;}
};int main()
{ffg::my_shared_ptr<ListNode> n1(new ListNode);ffg::my_shared_ptr<ListNode> n2(new ListNode);cout << n1.use_count() << endl;//1cout << n2.use_count() << endl;//1n1->_next = n2;n2->_pre = n1;return 0;
}
从图中可以看出引用计数变为1了,可以说明,_next和_prev不会在增加引用计数,这样就解决了循环引用问题。
5 删除器
如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题。
库里面的删除器测试:
6 C++11和boost中智能指针的关系
- C++ 98 中产生了第一个智能指针auto_ptr.
- C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
- C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
- C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost 的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
7 总结
智能指针一开始是为了解决某些情况下因抛异常而导致内存泄漏问题。
由RAII思想设计的智能指针,会管理资源的内存和释放。它支持像指针一样的功能
但是从而由智能指针引发了诸如拷贝问题,线程安全问题,循环引用问题。
智能指针是线程安全的,因为库里设计的时候加锁处理了,但是外界的资源不是线程安全的,需要程序员处理。
一开始的C++98库中的auto_ptr解决拷贝问题是不合理的,于是有了unique_ptr(强行让智能指针不能做拷贝),shared_ptr(引用计数解决拷贝问题中的重复析构),weak_ptr是配合shared_ptr使用了,解决了循环引用问题。
相关文章:

【C++学习笔记】RAII思想——智能指针
智能指针 1 内存泄漏问题2 RAII(Resource Acquisition Is Initialization)2.1 使用RAII思想设计的SmartPtr类2.2 智能指针的原理2.3 小总结智能指针原理 3 智能指针的拷贝问题3.1 std::auto_ptr3.2 std::unique_ptr3.3 std::shared_ptr3.3.1 拷贝构造函数…...
ubantu配置python环境
安装python 参考博客 安装pycharm 博客 创建Pycharm快捷方式 博客 ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1, currently the ‘ssl’ module is compiled with File “/home/r00t/IdeaProjects/data/venv/lib/python3.9/site-packages/urllib3/init.py”…...

单向/双向V2G环境下分布式电源与电动汽车充电站联合配置方法(matlab代码)
目录 1 主要内容 目标函数 电动汽车负荷建模 算例系统图 程序亮点 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序复现博士文章《互动环境下分布式电源与电动汽车充电站的优化配置方法研究》第五章《单向/双向V2G环境下分布式电源与电动汽车充电站联合配置方法》…...

dockerfile常用指令
Dockerfile常用指令 视频学习资料来源这里,点击本行文字即可跳转,讲的比较详细,不过比较老,跟最新的肯定是有一些差异的 Dockerfile官网文档的话点击这里 中文文档可以看看这个,不过没有详细的代码demo 或者是看这个 或…...
Matlab/simulink与dsp28335联合开发教程
一.入门篇(开发环境搭建) 1.1 Code Composer Studio 软件安装1.2 MATLAB 软件安装1.3 Control_SUIT3.4 软件安装1.4 C2000 Simulink 开发工具箱安装1.5 Visual_Studio_Professional 二. 基础篇(片内外设使用) 2.1 G…...

新项目搞完啦!!!
大家好,我是鱼皮。 经过了 7 场直播,总时长近 20 小时,我在 自己的编程导航 的第 5 个 全程直播开发 的项目 —— 智能 BI 项目,完结啦! 我在这里对该项目做一个简单的总结,希望让更多需要它的同学看到&am…...
分享一个可交互的小场景(二)
先看效果: 可互动的小场景 再看代码: JS部分 <script>var rotateDiv document.getElementById(rot);var rotateIcons document.getElementById(rot-icons);var clickRotateDiv document.getElementById(click-rot);var angle 0;clickRotateDi…...

2.5 DNS 应用 -- 1. DNS 概述
2.5 DNS 应用 -- 1. DNS 概述 DNS:Domain Name SystemDNS分布式层次式数据库DNS根域名服务器TLD和权威域名解析服务器本地域名解析服务器 DNS 查询迭代查询递归查询 DNS记录缓存和更新 DNS:Domain Name System Internet上主机/路由器的识别问题 IP地址域…...

基于STM32麦克风阵列音频信号处理系统设计
v hezkz17进数字音频系统研究开发交流答疑 附录: ADAU1452音频处理系统...
《重构》:Extract Class and Inline Class
hey,gays lets go on to refator those sh!t . i fork a rep, this the link GitHub - TIMPICKLE/refator-code: 重构 - 改善既有代码的设计 all right, lets see the genel description. 提取类 对立:内联类 目的:将大类分成小类 场景&a…...

腾讯云对象存储联合DataBend云数仓打通数据湖和数据仓库
随着数字化进程不断深入,数据呈大规模、多样性的爆发式增长。为满足更多样、更复杂的业务数据处理分析的诉求,湖仓一体应运而生。在Gartner发布的《Hype Cycle for Data Management 2021》中,湖仓一体(Lake house)首次…...
ExceptionLess windows部署。
前言 windows部署 1、一个api项目,里面包含了所有api。 2、一个elasticsearch项目,用来存储相关数据。 3、一个UI项目,也就是查看异常、设置新异常邮件通知等操作的后台。 异常在客户端提交的代码原理:一个异常被提交,…...
使用python实现1DCNN-GRU回归预测
要实现1DCNN-GRU进行回归预测,您可以使用以下Python代码作为参考: 首先,导入所需的库: import numpy as np import tensorflow as tf from tensorflow.keras.layers import Conv1D, MaxPooling1D, GlobalAveragePooling1D, GRU,…...

移动端数据可视化设计
在做APP设计的时候,难免会遇到一些需要展示数据的场景。使用传统的表格和文档展示数据不仅难看,也影响用户理解数据的含义。而数据可视化设计能将数据以更加直观的方式展现出来,使数据更加客观、更有说服力。 在移动应用中,数据可…...

Linux文件系统概述
本文已收录至《Linux知识与编程》专栏! 作者:ARMCSKGT 演示环境:CentOS 7 文件系统概述 前言正文文件与磁盘磁盘介绍与机械硬盘机械硬盘基础结构机械硬盘数据存储与管理 文件操作的细节创建文件访问文件删除文件恢复文件其他情况 最后 前言 …...

go专业数据结构与算法
go语言之专业数据结构与算法 2.数组概念 3.golang实现数组结构 4.golang实现数组迭代器 5.数组栈的高级实现 6.栈模拟低级递归 7.斐波那契数列栈模拟递归 8.递归实现文件夹遍历 9.栈模拟文件递归 10.层级展示文件夹 11.数组队列的实现 12.队列实现遍历文件夹 13.循环队列 14.链…...
Hive on Spark的小文件设置参数
Hive on Spark的小文件设置参数 参数调优 了解完了Spark作业运行的基本原理之后,对资源相关的参数就容易理解了。所谓的Spark资源参数调优,其实主要就是对Spark运行过程中各个使用资源的地方,通过调节各种参数,来优化资源使用的效…...

高级SQL语句
目录 MySQL 高级(进阶) SQL 语句函数数学函数:聚合函数字符串函数: 连接查询inner join(内连接):left join(左连接):right join(右连接): CREATE VIEW(视图)UNION(联集)C…...

IDE /skipping incompatible xxx_d.dll when searching for -lxxx_d
文章目录 概述场景复现用以测试的代码编译器位数不匹配导致?保持编译器类型一致再验证编译器位数的影响MingW下调用OS的库咋不告警?以mingW下使用winSocket为例MingW下网络编程的头文件分析该环境下链接的ws2_32库文件在哪里?mingW为啥可以兼容window下的动态库 概…...

C语言学习准备-编辑器选择
今天继续给大家更新C语言经典案例 今天的案例会比昨天稍微有一些难度,但是同时还是非常经典的案例 本来是想给大家继续更新C语言经典案例,但是有朋友反应C语言编辑器的选择,刚好我自己也是想更换一下C语言的编辑器,跟大家分享一下…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...