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

【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 小总结智能指针原理

  1. RAII特性

  2. 重载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对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。

  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

  4. 如果不是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. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时
    ++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错
    乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁
    的,也就是说引用计数的操作是线程安全的。
  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。
blog.csdnimg.cn/d7700d19400649b2a79f977aaed83bf1.png)

接下来到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中智能指针的关系

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. 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&#xff08;Resource Acquisition Is Initialization&#xff09;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常用指令 视频学习资料来源这里&#xff0c;点击本行文字即可跳转&#xff0c;讲的比较详细&#xff0c;不过比较老&#xff0c;跟最新的肯定是有一些差异的 Dockerfile官网文档的话点击这里 中文文档可以看看这个&#xff0c;不过没有详细的代码demo 或者是看这个 或…...

Matlab/simulink与dsp28335联合开发教程

一&#xff0e;入门篇&#xff08;开发环境搭建&#xff09; 1.1 Code Composer Studio 软件安装1.2 MATLAB 软件安装1.3 Control_SUIT3.4 软件安装1.4 C2000 Simulink 开发工具箱安装1.5 Visual_Studio_Professional 二. 基础篇&#xff08;片内外设使用&#xff09; 2.1 G…...

新项目搞完啦!!!

大家好&#xff0c;我是鱼皮。 经过了 7 场直播&#xff0c;总时长近 20 小时&#xff0c;我在 自己的编程导航 的第 5 个 全程直播开发 的项目 —— 智能 BI 项目&#xff0c;完结啦&#xff01; 我在这里对该项目做一个简单的总结&#xff0c;希望让更多需要它的同学看到&am…...

分享一个可交互的小场景(二)

先看效果&#xff1a; 可互动的小场景 再看代码&#xff1a; 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&#xff1a;Domain Name SystemDNS分布式层次式数据库DNS根域名服务器TLD和权威域名解析服务器本地域名解析服务器 DNS 查询迭代查询递归查询 DNS记录缓存和更新 DNS&#xff1a;Domain Name System Internet上主机/路由器的识别问题 IP地址域…...

基于STM32麦克风阵列音频信号处理系统设计

v hezkz17进数字音频系统研究开发交流答疑 附录: ADAU1452音频处理系统...

《重构》:Extract Class and Inline Class

hey&#xff0c;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. 提取类 对立&#xff1a;内联类 目的&#xff1a;将大类分成小类 场景&a…...

腾讯云对象存储联合DataBend云数仓打通数据湖和数据仓库

随着数字化进程不断深入&#xff0c;数据呈大规模、多样性的爆发式增长。为满足更多样、更复杂的业务数据处理分析的诉求&#xff0c;湖仓一体应运而生。在Gartner发布的《Hype Cycle for Data Management 2021》中&#xff0c;湖仓一体&#xff08;Lake house&#xff09;首次…...

ExceptionLess windows部署。

前言 windows部署 1、一个api项目&#xff0c;里面包含了所有api。 2、一个elasticsearch项目&#xff0c;用来存储相关数据。 3、一个UI项目&#xff0c;也就是查看异常、设置新异常邮件通知等操作的后台。 异常在客户端提交的代码原理&#xff1a;一个异常被提交&#xff0c;…...

使用python实现1DCNN-GRU回归预测

要实现1DCNN-GRU进行回归预测&#xff0c;您可以使用以下Python代码作为参考&#xff1a; 首先&#xff0c;导入所需的库&#xff1a; import numpy as np import tensorflow as tf from tensorflow.keras.layers import Conv1D, MaxPooling1D, GlobalAveragePooling1D, GRU,…...

移动端数据可视化设计

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

Linux文件系统概述

本文已收录至《Linux知识与编程》专栏&#xff01; 作者&#xff1a;ARMCSKGT 演示环境&#xff1a;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作业运行的基本原理之后&#xff0c;对资源相关的参数就容易理解了。所谓的Spark资源参数调优&#xff0c;其实主要就是对Spark运行过程中各个使用资源的地方&#xff0c;通过调节各种参数&#xff0c;来优化资源使用的效…...

高级SQL语句

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

IDE /skipping incompatible xxx_d.dll when searching for -lxxx_d

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

C语言学习准备-编辑器选择

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

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...