深入篇【C++】总结智能指针的使用与应用意义(auto_ptr/unique_ptr/shared_ptr/weak_ptr)底层原理剖析+模拟实现

深入篇【C++】总结智能指针的使用与应用意义&&(auto_ptr/unique_ptr/shared_ptr/weak_ptr)底层原理剖析+模拟实现

  • 智能指针的出现
  • 智能指针的使用
  • 应用意义/存在问题
  • 智能指针原理剖析+模拟实现
    • auto_ptr
    • unique_ptr
    • shared_ptr
    • weak_ptr

智能指针的出现

首先我们要理解智能指针是什么。为什么要有智能指针。什么场景会用到智能指针。
首先我们知道C++的异常有很大的缺陷,那就是执行流会乱跳,这样就可能会造成内存泄露问题,比如在new和delete之间出现异常,那么就会出现资源没有释放,内存泄露。


int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void func()
{div();
}
int main()
{try{func();}catch(const exception& e)//抛基类异常,用父类来接受{cout << e.what() << endl;}
}

在这里插入图片描述
首先这是正常的使用异常处理,当div函数抛异常时,就会直接跳到catch捕获的地方,进行处理。

//异常的缺点:内存泄露,在new和delete之间抛异常
#include <vector>
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void func()
{vector<int>* p1 = new vector<int>;div();delete p1;cout << "delete:" << p1 << endl;
}
//正常情况下,如果不抛异常,就不会内存泄露,但抛异常后,就会泄露,解决方法
//是再套一层异常判断,捕获的异常不处理,继续抛出(在抛出之前将资源释放)
int main()
{try{func();}catch (const exception& e)//抛基类异常,用父类来接受{cout << e.what() << endl;}
}

在这里插入图片描述

那如果是这样的场景呢,在new和delete之间如果div()抛异常了,那么开辟的空间p1就无法释放。最终会造成内存泄露的。
而想要处理这样的问题,就需要对div函数套一层异常判断,如果出现异常,先释放资源了,再将异常抛出给外面的捕获处理。

//异常的缺点:内存泄露,在new和delete之间抛异常
#include <vector>
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void func()
{vector<int>* p1 = new vector<int>;//如果p1抛异常就 没有问题,没有资源的申请,所以也不需要释放vector<int>* p2 = new vector<int>;//如果p2抛异常,p1就内存泄露vector<int>* p3 = new vector<int>;//如果p3抛异常,p1和p2就内存泄露//…………//所以异常出现后,很容易造成内存泄露,有什么办法可以解决呢?try{div();}catch (...){delete p1;cout << "delete" << p1 << endl;throw;//再抛出}delete p1;cout << "delete:" << p1 << endl;
}
//正常情况下,如果不抛异常,就不会内存泄露,但抛异常后,就会泄露,解决方法
//是再套一层异常判断,捕获的异常不处理,继续抛出(在抛出之前将资源释放)
int main()
{try{func();}catch (const exception& e)//抛基类异常,用父类来接受{cout << e.what() << endl;}
}

在这里插入图片描述
只不过这样做还是有缺陷,如果有很多个资源申请呢?或者有连续的资源申请呢?比如资源1申请,如果出现异常,那么就直接跳出去,如果资源2异常那么就需要对这个操作套一层异常处理,需要先将资源1的资源释放了,然后再重新抛异常给外面。如果资源3申请时出现异常呢?…………这样是不是每次申请资源时都需要套上异常处理呢?这样也太麻烦了吧!但你又不得不这样做,因为你要保证内存安全啊!

//异常的缺点:内存泄露,在new和delete之间抛异常
#include <vector>
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void func()
{vector<int>* p1 = new vector<int>;//如果p1抛异常就 没有问题,没有资源的申请,所以也不需要释放vector<int>* p2 = new vector<int>;//如果p2抛异常,p1就内存泄露,需要给该步骤套上异常处理,释放p1然后再将异常抛出vector<int>* p3 = new vector<int>;//如果p3抛异常,p1和p2就内存泄露,需要给该步骤套上异常处理,释放p1,p2然后再将异常抛出//…………//所以异常出现后,很容易造成内存泄露,有什么办法可以解决呢?try{div();}//如果div()抛异常,,p1,p2,p3都得释放catch (...){delete p1;delete p2;delete p3;cout << "delete" << p1 << endl;throw;//再抛出}delete p1;cout << "delete:" << p1 << endl;
}
//正常情况下,如果不抛异常,就不会内存泄露,但抛异常后,就会泄露,解决方法
//是再套一层异常判断,捕获的异常不处理,继续抛出(在抛出之前将资源释放)
int main()
{try{func();}catch (const exception& e)//抛基类异常,用父类来接受{cout << e.what() << endl;}
}

这时,智能指针就出现了!
智能指针有三大特性:

1.RAII
2.可以像指针一样
3.存在拷贝问题。

一.RAII,是一种利用对象生命周期来控制程序资源的技术。
我们在对象构造的时候,将开辟的资源交给对象,那么这样在对象生命周期内,该资源一直存在,然后在该对象的析构函数里,进行释放资源。这样对象析构,资源也就释放了。
也就是我们将资源交给一个对象进行管理,当对象的生命周期还在时,资源就存在,当对象销毁时,资源就被释放,这样,我们就将管理资源的责任托管给了对象。
好处:

1.释放了双手,不需要我们显示的释放资源。就不用怕资源最后没有释放。
2.采用这种方式,对象所需的资源在其生命周期内始终保持有效。

二.智能指针,从指针二字我们就应该能意识到它是具备指针的特性的,而指针有哪些特性呢?

1.可以解引用。通过解引用访问资源。
2.可以使用→运算符来访问资源里的内容。

三.所以说智能指针本质上也是一个指针,那指针之间也是可以赋值,拷贝的。并且是值拷贝。

智能指针的使用

智能指针的实现其实很简单,智能指针底层就是封装着该类型的指针。原理(RAII)就是将申请的资源托管给一个对象管理,所以在对象构造时,将资源给对象即可。当对象析构时,就将资源释放。
然后还要实现*运算符重载和→运算符重载。

namespace tao
{template<class T>class smater_ptr
{
public:smater_ptr( T* ptr)//将资源交给对象管理:_ptr(ptr){}~smater_ptr()//对象销毁时就将资源释放{cout << "delete:" << _ptr << endl;delete _ptr;}//像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

那么我们就可以处理上面遗留的问题了:


#include <vector>
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void func()
{//vector<int>* p1 = new vector<int>;如果p1抛异常就 没有问题,没有资源的申请,所以也不需要释放//vector<int>* p2 = new vector<int>;如果p2抛异常,p1就内存泄露//vector<int>* p3 = new vector<int>;如果p3抛异常,p1和p2就内存泄露//…………//所以异常出现后,很容易造成内存泄露,有什么办法可以解决呢?smater_ptr<vector<int>> p1(new vector<int>);//将资源给对象p1管理smater_ptr<vector<int>> p2(new vector<int>);smater_ptr<vector<int>> p3(new vector<int>);smater_ptr<string> ps(new string("小陶来咯"));div();cout << *ps << endl;cout << ps->size() << endl;
}int main()
{try{func();}catch (const exception& e)//抛基类异常,用父类来接受{cout << e.what() << endl;}
}

在这里插入图片描述
这里再怎么抛异常都不会影响资源的释放,因为资源是被智能指针对象管理着,当对象销毁时,管理的资源肯定会释放的。
我们也不用担心连续的申请资源会出现异常的情况了。

应用意义/存在问题

一般正常使用智能指针,就可以避免大多数的内存泄露问题,但不排除乱用的。智能指针的应用能帮助我们很好的处理因为异常出现而导致的内存泄露问题。不过初期的智能指针还存在着问题,比如拷贝问题。C++98时期就已经存在智能指针auto_ptr.不过吐槽点很多,现在公司基本禁止使用auto_ptr.
随着C++的发展,又出现其他的智能指针比如:uniqe_ptr
,shared_ptr,weak_ptr(本质不是)。

那智能指针存在什么问题呢?我们来分析分析:


int main()
{smater_ptr<string> sp1(new string("xioatao"));smater_ptr<string> sp2(new string("xioyao"));//存在这样的场景:smater_ptr<string> sp2(sp1);//指针之间的拷贝就应该是浅拷贝,我们不用写编译器会自动生成。//但浅拷贝会出现什么问题呢?//1.同一块资源被释放两次 2.内存泄露return 0;
}

在这里插入图片描述
呐,这就是智能指针的拷贝问题,指针之间的拷贝肯定是值拷贝,因为是内置类型,不存在深拷贝。那浅拷贝我们不写,编译器生成的拷贝构造就是浅拷贝,所以我们就不用写了吗?
首先,我们要分析,确实是浅拷贝,但是如果指针浅拷贝了,就会出现这样的问题:①指向同一块的空间被释放两次②有一块空间没有释放,内存泄露。

那我们来看看C++库里是如何处理这些问题的。

智能指针原理剖析+模拟实现

auto_ptr

C++98库里提供的auto_ptr智能指针,处理这种问题的原理是:管理权转移。
什么叫管理权转移呢?就比如sp3(sp1),将sp1拷贝给sp3。也就是用sp1构造sp3。首先我们要明白,sp1是管理着一块资源的,sp3还没有实例化,没有管理资源。这里直接将sp1管理资源的权力转移给sp3。然后sp1就没有权力管理资源了也就是不需要管理资源了。不管理资源是如何做到的呢?直接将智能指针对象里面的指针置空即可。

namespace tao
{template <class T>class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}~auto_ptr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}//sp3(sp1)auto_ptr(auto_ptr<T>& p):_ptr(p._ptr){//将资源转移后,被拷贝对象再置空p._ptr = nullptr;//如果不置空,就会释放两次}private:T* _ptr;};

这就是C++98提供的auto_ptr智能指针。这个版本被吐槽的很多,因为你将sp1对象管理资源的权转移后,sp1就悬空了。如果有人要再次访问sp1呢?就是那个不管理资源的智能指针。(里面的指针必须置空,不然就会释放两次。)


//我们写一个类,方便观察资源创建和释放
#include "smater_ptr.h"
class B
{
public:B(int b = 0):_b(b){cout << "b=0" << endl;}~B(){cout << this;cout << "~B()" << endl;}private:int _b;
};
//自定义类型构造会调用它的构造函数,和析构函数。
int main()
{tao::auto_ptr<B> b1(new B(1));tao::auto_ptr<B> b2(new B(2));//这个是auto_ptr C++98时期就出现,但不好,很多公司严禁不给使这个tao::auto_ptr<B> b3(b1);//因为存在严重的不合理地方,当出现智能指针拷贝赋值的地方//auto_ptr处理的方式是:直接转移资源的管理权。//拷贝时,会把被拷贝对象的资源管理转移给拷贝对象。而被被拷贝对象就没有资源管理,直接置空//存在问题:管理权转移后,再次访问被拷贝对象//b1这个智能指针已经没有资源可以管理了,里面的指针直接置空了,不能再访问了//b1->_b++;b3->_b++;
}

unique_ptr

由于auto_ptr的设计太不合理,C++11中开始提供更靠谱的unique_ptr智能指针。
unique_ptr的原理其实很简单,四个字:简单粗暴。
直接不给拷贝,简单粗暴的防止拷贝。利用delete关键字,将函数定义为删除函数,不能使用。指针赋值的操作也被禁止。

	template <class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(unique_ptr<T>& p) = delete;//直接删除掉,不能拷贝private:T* _ptr;};

在这里插入图片描述

shared_ptr

但是如果就是存在智能指针间的拷贝,那该怎么办呢?unique_ptr肯定不能使用了。
所以C++11又提供了一个可以允许拷贝的智能指针,那就是shared_ptr.
shared_ptr智能指针实现的原理是:<引用计数>

原理
①当有一个对象管理资源时,计数器就显示1.当有两个对象指向资源时,计数器就显示2.当有n个对象指向资源时,计数器就显示n。
②当指向资源的对象生命周期结束时,首先将该资源的上的计数器减减。然后判断计数器是否为0.如果不为0,说明还存在对象管理着资源。如果为0,就说明没有对象管理资源了,该资源就可以释放了。

那这个计数器应该如何设计呢?是设计成普通计数器就可以吗?还是设计成静态的呢?
如果设计成普通的计数器,就是在智能指针内部存一个计数器。这就表明每个对象都有一个计数器。这合理吗?在这里插入图片描述
这肯定不合理啊,指向相同资源时如何进行计数呢?这样不能根据计数器来判断一个资源上有多少对象管理了。一个资源就一个计数器就可以了。那设计成静态计数器呢?

在这里插入图片描述

设计成静态计数器,如果只有一块资源的话,也不是不可以,但我们不知道会有多少资源啊,当有新的资源开辟后,该资源上应该只有一个对象管理,但使用静态计数器后,所有对象共享该计数器,就造成了该资源上的计数不对。

我们想要的计数器应该是要伴随着资源的申请而生成。当有一个资源生成被智能指针管理后,就会生成一个计数器,来计算该资源受管理的个数。当有两个资源时,就会有两个计数器,各计算各的,互不影响。所以这里的计数器应该是动态计数器。
当有资源申请时并给对象管理时(也就是调用对象的构造函数)时,就会动态生成一个计数器。
在这里插入图片描述
计数器实现完后,我们就可以利用引用计数来实现拷贝。比如用p2(p1),用p1拷贝构造p2.那么p2就要指向p1指向的资源,并且资源上的计数器要加加。这两个智能指针都管理着资源。不过当p2销毁时,资源并不会释放,计数器首先会减减,然后判断计数器是否为0,只有计数为0了,才可以将资源释放。

template <class T>
class shared_ptr
{
public:shared_ptr(T* ptr=nullptr):_ptr(ptr),_pcount(new int(1)){}//当有资源申请并交给对象管理时,计数器才会生成。//只有对象管理一个资源时,才会生成计数器。不然不会生成。//当拷贝时(没有资源的生成管理)就让其他对象的指针向生成的计数器。~shared_ptr(){//当有对象要销毁时,首先先减减计数器//只有最后一个对象管理时才可以释放资源if (--(*_pcount) == 0){cout << "delete" << _ptr << endl;delete _ptr;delete _pcount;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}shared_ptr(shared_ptr<T>& ps):_ptr(ps._ptr), _pcount(ps._pcount)//这个单纯指向没有再管理一个新的资源,所以不会生成计数器,只需要指向原来生成的计数器。{++(*_pcount);//然后原来的计数器加加即可,表面该资源上多了一个对象管理}T* get()const//获取_ptr {return _ptr;}
private:T* _ptr;int* _pcount;//动态计数器
};

shared_ptr除了支持智能指针间的拷贝,还支持指针间的赋值。那么赋值重载是如何实现的呢?
首先赋值是两个都已经存在的对象进行赋值。而拷贝构造是已存在的拷贝给不存在的对象。
假设两个存在的对象,各自都管理着一块资源,当两个对象进行赋值,会发生什么呢?
在这里插入图片描述
不过这里要注意两个细节,细节一就是赋值对象的计数器减减后,需要进行判断,是否为0.如果计数器为0了,就说明该资源上没有对象管理,那么该资源就可以释放了。如果不是0,那就没事。
细节二,就是自己给自己赋值的场景会有bug存在。
在这里插入图片描述

//shared_ptr的赋值运算符重载
shared_ptr<T>& operator=(const shared_ptr<T>& ps)
{//自己给自己赋值的场景要避免if (_ptr == ps._ptr)return *this;//首先要对赋值对象管理的资源的计数器减减,要注意被赋值对象管理的资源是否只有一个智能指针控制if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_ptr = ps._ptr;_pcount = ps._pcount;//正常转移指向即可//被赋值对象的计数器++即可(*ps._pcount)++;return *this;
}

验证一下:

int main()
{tao::shared_ptr<B> b1(new B(1));tao::shared_ptr<B> b2(b1);tao::shared_ptr<B> b3(b1);tao::shared_ptr<B> b4(new B(2));tao::shared_ptr<B> b5(b4);b1 = b5;}

在这里插入图片描述

weak_ptr

shared_ptr几乎已经完美了,既具有智能指针的特性,又允许拷贝和赋值。但还是具有缺点的,具有什么缺点呢?
该问题就是:循环计数
当出现循环计数时,shared_ptr是没有办法解决。什么叫循环计数问题呢?

当存在这种需求时:动态开辟的节点需要链接起来时。我们使用智能指针来管理资源会发生什么呢?


struct Node
{B _val;Node* _next;Node* _prev;
};int main()
{tao::shared_ptr<Node> sp1(new Node);tao::shared_ptr<Node> sp2(new Node);//sp1->_next = sp2;//类型不匹配,sp1->next的类型是Node而sp2的类型是shared_ptr类型所以这样无法链接起来。
//	//所以Node节点里存的应该是shared_ptr类型的指针,这样才可以链接起来
}

我们发现无法链接起来,因为节点里next是Node*类型的,而sp2是shared_ptr类型的。所以为了能够链接起来,节点里存的应该是shared_ptr类型的next。

struct Node
{B _val;shared_ptr<Node> _next;shared_ptr<Node> _prev;
};int main()
{tao::shared_ptr<Node> sp1(new Node);tao::shared_ptr<Node> sp2(new Node);
//	//所以Node节点里存的应该是shared_ptr类型的指针,这样才可以链接起来sp1->_next = sp2;sp2->_prev = sp1;}

这样两个节点就链接起来了。可是链接起来后就出现问题了:
在这里插入图片描述
我们发现两个智能指针管理的资源都没有释放,这是为什么呢?
在这里插入图片描述
在这里插入图片描述
C++中是如何解决循环计数的呢?C++11提供了weak_ptr专门用来处理shared_ptr出现的循环计数问题。
那么weak_ptr解决循环计数的原理是什么呢?
首先我们需要明白引起循环计数的原因是什么,要理解什么场景下会发生循环计数。
1.主要原因就是因为智能指针定义在节点的内部。
2.然后就是因为计数器要等于1时才可以释放资源。

就是因为内部的智能指针参与了资源的管理,导致计数器增加,外面管理的资源的对象销毁后资源也无法销毁,需要里面的智能指针对象销毁才可以销毁,而里面的对象销毁又需要资源先销毁才可以销毁。所以最主要原因就是内部的智能指针管理资源。
所以weak_ptr实现的原理就是让节点里面的智能指针不管理资源,就单纯的链接节点,不参与资源的管理,但可以访问资源。
这样计数器就不会增加,当外面的对象销毁,资源就会正常销毁。
所以正常操作应该是这样:

struct Node
{B _val;weak_ptr<Node> _next;weak_ptr<Node> _prev;//weak_ptr不管理资源,不会增加计数器
};
int main()
{shared_ptr<Node> sp1(new Node);shared_ptr<Node> sp2(new Node);sp1->_next = sp2;sp2->_prev = sp1;
}

在这里插入图片描述
所以weak_ptr严格上来说不是智能指针,它不具备RAII特性。不管理资源。它主要是提供支持由shared_ptr类型转换成weak_ptr类型的拷贝构造和赋值。而不是管理资源。

	template <class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}weak_ptr(const shared_ptr<T>& sp)//在类外无法访问到sp的保护成员_ptr,所以徐娅get函数来获取。:_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}private:T* _ptr;};
};

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

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

相关文章

vscode ssh linux C++ 程序调试

vscode调试c++程序相比vs2022要复杂很多,vs2022可以"一键运行调试",vscode则需要自己配置。 ​vscode调试程序时,会在当前工作目录产生.vscode 目录, 该目录有两个重要文件launch.json和tasks.json, 下面介绍两种调试方法: 手动调试和自动调试。 手动调试 不管…

VUE树结构实现

实现效果: 数据库表结构如下: 要求:需要有parentId,id。parentId就是父记录的id 表数据要求:一定不要让一条记录的parentid和id相同 前端代码: 注意:el-table标签里面需要加上属性,才可以有下拉箭头的样式 <el-table v-loading="listLoading" :data

Spring Boot中的异步编程:解决的问题与应用场景

Spring Boot中的异步编程&#xff1a;解决的问题与应用场景 在现代Web应用程序中&#xff0c;高并发和性能是至关重要的。为了处理大量的请求和任务&#xff0c;异步编程成为了不可或缺的一部分。Spring Boot提供了强大的异步编程支持&#xff0c;可以显著提高应用程序的吞吐量…

【Java 进阶篇】JavaScript Math对象详解

在JavaScript编程中&#xff0c;Math对象是一个非常有用的工具&#xff0c;用于执行各种数学运算。它提供了许多数学函数和常数&#xff0c;可以用于处理数字、执行几何运算、生成随机数等。在本篇博客中&#xff0c;我们将深入探讨JavaScript中Math对象的各种功能和用法。 什…

LiveMedia视频中间件视频隐私打码直播解决方案

一、方案背景 随着科技的发展&#xff0c;视频监控系统已经成为了我们生活中不可或缺的一部分。无论是在公共区域&#xff0c;还是在私人场所&#xff0c;我们都可以看到各种各样的监控设备。这些设备的出现&#xff0c;无疑提高了我们的生活安全&#xff0c;使得我们可以更好地…

Gin,Gorm实现Web计算器

目录 仓库链接0.PSP表格1. 成品展示1.基础运算2. 清零回退3.错误提示4.历史记录拓展功能1.前端可修改的利率计算器2.科学计算器3. 按钮切换不同计算器模式4.用户在一次运算后不清零继续输入操作符&#xff0c;替换表达式为上次答案 2.设计实现过程3.代码说明4.心路历程和收获 仓…

企业级CI/CD 持续集成/交付/发布

jenkins 安装与使用 nmcli g hostname jenkins 加载缓存 yum makecache fast 上传jdk11、jdk8 获取、上传war包 1、jenkins.io/download 2.4.27 2、老师发的 上传 maven 上传tomcat软件包 &#xff08;apache.org-tomcat8-下载&#xff09; 注意8009端口 /usr... vi /etc/pro…

react中ant.design框架配置动态路由

目录 什么是动态路由&#xff1f; 应用场景&#xff1a; ant.design动态路由如何配置&#xff1a; 首先&#xff1a;找到app.tsx文件 然后&#xff1a;找到menuHeaderRender 其次&#xff1a;修改menuHeaderRender为menuDataRender​编辑 最后&#xff1a;在箭头函数里re…

linux内存、cpu、进程、端口、硬盘管理

这里讲解一下linux内存、cpu、进程、端口、硬盘管理命令操作,更多linux常用命令见:一些经常使用的linux命令 一、内存、cup 管理 top 命令 1、top 命令的第一行 top - 10:11:23 up 12:10, 2 users, load average: 0.00, 0.02, 0.05 依次对应&#xff1a; 系统当前时间 10:11:…

Vite与Webpack谁更胜一筹,谁将引领下一代前端工具的发展

你知道Vite和Webpack吗&#xff1f;也许有不少“程序猿”对它们十分熟悉。 Webpack Webpack是一个JavaScript应用程序的静态模块打包工具&#xff0c;它会对整个应用程序进行依赖关系图构建。而这也会导致一个不可避免的情况&#xff0c;使用Webpack启动应用程序的服务器&…

C++指针解读(5)-- 指针和数组(多维数组)

相比一维数组&#xff0c;二维数组的概念和相关运算要复杂得多。 1、二维数组的存储及访问 假设有这么一个二维数组&#xff1a; int arr[3][4] {{ 10, 11, 12, 13 },{ 20, 21, 22, 23 },{ 30, 31, 32, 33 } }; 我们可以把二维数组看成数组的数组&#xff1a; &#xff…

Arduino驱动BNO055 10轴绝对定向传感器(惯性测量传感器篇)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 BNO055是实现智能9轴绝对定向的新型传感器IC,它将整个传感器系统级封装在一起,集成了三轴14位加速度计,三轴16位陀螺仪,三轴地磁传感器和一个自带算法处理的32位微控制器。

在 Elasticsearch 中实现自动完成功能 3:completion suggester

在这篇博文中&#xff0c;我们将讨论 complete suggester - 一种针对自动完成功能进行优化的 suggester&#xff0c;并且被认为比我们迄今为止讨论的方法更快。 Completion suggester 使用称为有限状态转换器的数据结构&#xff0c;该结构类似于 Trie 数据结构&#xff0c;并且…

Net6 用imagesharp 实现跨平台图片处理并存入oss

项目要求&#xff1a;生成电子证书 一、模板文件在OSS中&#xff0c;直接加载 二、向模板文件添加二维码 三、向模板文件添加多行文字 四、生成二维码&#xff0c;存入本地&#xff0c; 五、向模板文件添加二维码 代码实现步骤 一、建立.net 6 API项目&#xff0c;安装N…

启航kp OpenHarmony环境搭建

前提 启航kp OpenHarmony环境搭建 搭建好OpenHarmony环境 未搭建好可以参考OpenHarmony docker环境搭建 安装vscode 下载好启航kp所需的开发包和样例 下载地址 搭建过程 进入正确文件夹 首先要进入 /home/openharmony 目录下&#xff0c;如果没有打开在vsc左上角找到文…

JUC并发编程——JUC并发编程概述及Lock锁(重点)(基于狂神说的学习笔记)

基于bilibili狂神说JUC并发编程视频所做笔记 概述 什么是JUC JUC时java.util工具包中的三个包的简称 java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks 业务&#xff1a;普通的线程代码中&#xff0c;我们常使用Runnable接口 但Runnable没有返…

百度开放平台第三方代小程序开发,授权事件、消息与事件通知总结

大家好&#xff0c;我是小悟 关于百度开放平台第三方代小程序开发的两个事件接收推送通知&#xff0c;是开放平台代小程序实现业务的重要功能。 授权事件推送和消息与事件推送类型都以event的值判断。 授权事件推送通知 授权事件推送包括&#xff1a;推送票据、授权成功、取…

【使用教程】在Ubuntu下PMM60系列一体化伺服电机通过SDO跑循环同步位置模式详解

本教程将指导您在Ubuntu操作系统下使用SDO&#xff08;Service Data Object&#xff09;来配置和控制PMM60系列一体化伺服电机以实现循环同步位置模式。我们将介绍必要的步骤和命令&#xff0c;以确保您能够成功地配置和控制PMM系列一体化伺服电机。 01.准备工作 在正式介绍之…

一种更具破坏力的DDoS放大攻击新模式

近日&#xff0c;内容分发网络&#xff08;CDN&#xff09;运营商Akamai表示&#xff0c;一种使网站快速瘫痪的DDoS放大攻击新方法正在被不法分子所利用。这种方法是通过控制数量巨大的中间设备&#xff08;middlebox&#xff0c;主要是指配置不当的服务器&#xff09;&#xf…

【VR】【Unity】白马VR课堂系列-VR开发核心基础03-项目准备-VR项目设置

【内容】 详细说明 在设置Camera Rig前,我们需要针对VR游戏做一些特别的Project设置。 点击Edit菜单,Project Settings,选中最下方的XR Plugin Management,在右边面板点击Install。 安装完成后,我们需要选中相应安卓平台下的Pico VR套件,关于怎么安装PICO VR插件,请参…