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

[C++]智能指针用法

一、智能指针存在的意义

智能指针主要解决以下问题:

(1)内存泄漏:内存手动释放,使用智能指针可以自动释放。

(2)共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题。

C++里面有四个智能指针:auto_ptr、share_ptr、unique_ptr、weak_ptr。其中后三个是C++11支持的,并且第一个已经在C++11弃用;所以,重点讲解share_ptr、unique_ptr、weak_ptr。

它们的特点:

(1)unique_ptr独占对象的所有权,由于没有引用计数,性能较好。

(2)share_ptr共享对象的所有权,但性能略差。

(3)weak_ptr配合share_ptr,解决循环引用问题。

二、shared_ptr

std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。

sharedd_ptr共享被管理的对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象销毁时,被管理对象自动销毁。

简单的说,shared_ptr实现包含了两个部分:

(1)一个指向堆上创建的对象的裸指针 raw_ptr。

(2)一个指向内部隐藏的、共享的管理对象 shared_count_object。其中use_count是当前这个堆上对象被多少对象引用了,简单来说就是引用计数。

2.1、shared_ptr内存模型

shared_ptr内部包含两个指针,一个指向对象,一个指向控制块。控制块包含一个引用计数、一个弱计数和其他数据(比如删除器、分配器等)。

其中reference count会累计对象的使用者数量。

std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2=p1;

上例中,p1和p2的内存模型关系就是:

2.2、shared_ptr使用场景

(1)使用智能指针可以自动释放占用的内存。

// Buffer对象分配在堆上,但能自动释放
shared_ptr<Buffer> buf=make_shared<Buffer>("auto free memory");// Buffer对象分配在堆上,但需要手动delete释放
Buffer *buf2=new Buffer("free memory");

(2)共享所有权指针的传播和释放。

同样的数据,但不同的业务处理不一样。使用shared_ptr智能指针,可以减少内存拷贝,因为有引入计数的存在,当引入计数变为 0 时才真正去释放内存。

2.3、shared_ptr的基本使用和常用函数

(1)s.get():返回shared_ptr中保存的裸指针。

(2)s.reset(...):重置shared_ptr。

  • reset()不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针s不是唯一指向该对象的指针,则引用计数减一,同时将s置为空。
  • reset()带参数时,若智能指针s是唯一指向该对象的指针,则释放并指向新的对象。若智能指针s不是唯一指向该对象的指针,则引用计数减一,并指向新的对象。

例如:

auto s=make_shared<int>(100);
s.reset(new int(200));

(3)s.use_count():返回shared_ptr的强引用计数。

(4)s.unique():若use_count为1返回true,否则返回false。

2.3.1、初始化 make_shared / reset

通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr,代码如下:

std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2=p1;
std::shared_ptr<int> p3;
p3.reset(new int(1));

应该优先使用make_shared来构造智能指针,因为它更高效。

auto p1=make_shared<int>(100);shared_ptr<int> p2=make_shared<int>(100);// 相当于
shared_ptr<int> p1(new int(100));

不能将原始指针直接赋给一个智能指针。例如,下面这种方法是错误的:

std::shared_ptr<int> p=new int(1);

shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数和辅助方法来初始化。

对于一个未初始化的智能指针,可以通过reset方法来初始化;
当智能指针有值的时候调用reset会引起引用计数减1。

另外智能指针可以通过重载的bool类型操作符来判断。

#include <iostream>
#include <memory>
using namespace std;
int main()
{std::shared_ptr<int> p1;p1.reset(new int(1));std::shared_ptr<int> p2 = p1;// 引用计数此时应该是2cout << "p2.use_count() = " << p2.use_count()<< endl;p1.reset();cout << "p1.reset()\n";// 引用计数此时应该是1cout << "p2.use_count()= " << p2.use_count() << endl;if(!p1) {cout << "p1 is empty\n";}if(!p2) {cout << "p2 is empty\n";}p2.reset();cout << "p2.reset()\n";cout << "p2.use_count()= " << p2.use_count() << endl;if(!p2) {cout << "p2 is empty\n";}return 0;
}

输出:

p2.use_count() = 2
p1.reset()
p2.use_count()= 1
p1 is empty
p2.reset()
p2.use_count()= 0
p2 is empty

2.3.2、获取原始指针 get()

当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下所示:

std::shared_ptr<int> ptr(new int(1));
int *p = ptr.get(); 
// 不小心 delete p;

谨慎使用get()的返回值,如果不清楚其危险性则永远不要调用get()函数。

p.get()的返回值就相当于一个裸指针的值,不合适的使用这个值,上述陷阱的所有错误都有可能发生,

遵守以下几个约定:

不要保存get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的。
保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针。
不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误。

2.3.3、指定删除器

如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。

#include <iostream>
#include <memory>using namespace std;void DeleteIntPtr(int *p)
{cout<< "Call DeleteIntPtr"<<endl;delete p;
}int main(int argc, char **argv)
{shared_ptr<int> p(new int(1),DeleteIntPtr);return 0;
}

当p的引用计数为0时,自动调用删除器DeleteIntPtr来释放对象的内存。删除器可以是一个lambda表达式,上面的写法可以改为:

shared_ptr<int> p(new int(1),[](int *p){cout<< "Call DeleteIntPtr"<<endl;delete p;
});

当使用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数据对象,代码如下:

std::shared_ptr<int> p3(new int[10],[](int *p){delete [] p;});

2.4、shared_ptr使用要注意的问题

(1)不要用一个原始指针初始化多个shared_ptr。

错误示范如下:

int *ptr=new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr);//逻辑错误

(2)不要在函数实参中创建shared_ptr。

错误示范如下:

function(shared_ptr<int>(new int),g());

因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右;所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建,则int内存泄漏;正确的写法应该是先创建智能指针,代码如下:

shared_ptr<int> p1(new int);
function(p1,g());

(3)通过shared_from_this返回this指针。

不要将this指针作为shared_ptr返回回来,因为this指针本质上是一个裸指针,因此,可能会导致重复析构,如下例子:

#include <iostream>
#include <memory>using namespace std;class MyClass
{
public:shared_ptr<MyClass> GetSelf() {return shared_ptr<MyClass>(this);//不要这样做}MyClass() {cout << "MyClass()" << endl;};~MyClass() {cout << "~MyClass()" << endl;};};int main()
{shared_ptr<MyClass> sp1(new MyClass);shared_ptr<MyClass> sp2 = sp1->GetSelf();return 0;
}

运行后调用两次析构:

MyClass()
~MyClass()
~MyClass()
free(): double free detected in tcache 2
已放弃 (核心已转储)

在这个例子中,由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。

正确返回this的shared_ptr的做法是:让目标类继承std::enable_shared_from_this类,然后使用基类的成员函数shared_from_this()返回this的shared_ptr,如下所示:

#include <iostream>
#include <memory>using namespace std;class MyClass: public std::enable_shared_from_this<MyClass>
{
public:shared_ptr<MyClass> GetSelf() {return shared_from_this();//不要这样做}MyClass() {cout << "MyClass()" << endl;};~MyClass() {cout << "~MyClass()" << endl;};};int main()
{shared_ptr<MyClass> sp1(new MyClass);shared_ptr<MyClass> sp2 = sp1->GetSelf();return 0;
}

执行结果:

MyClass()
~MyClass()

(4)避免循环引用。

循环引用会导致内存泄漏。比如:

#include <iostream>
#include <memory>using namespace std;class A;
class B;class B
{
public:shared_ptr<A> aptr;B();~B();private:};B::B()
{cout << "B()" << endl;
}B::~B()
{cout << "B is deleted" << endl;
}class A
{
public:shared_ptr<B> bptr;A();~A();private:};A::A()
{cout << "A()" << endl;
}A::~A()
{cout << "A is deleted" << endl;
}int main()
{shared_ptr<A> ap(new A);shared_ptr<B> bp(new B);ap->bptr = bp;bp->aptr = ap;cout << "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构return 0;
}

循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不回减为0,导致两个指针都不会被析构,产生内存泄漏。

解决的办法是把A和B任何一个成员变量改为weak_ptr。

三、unique_ptr

(1)unique_ptr是一个独占型的智能指针,不能将其复制给另一个unique_ptr。

(2)unique_ptr可以指向一个数组。

(3)unique_ptr需要确定删除器的类型。

3.1、unique_ptr是一个独占型的智能指针

unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。下面的错误示例。

unique_ptr<T> my_ptr(new T);
unique_ptr<T> my_other_ptr=my_ptr;//报错,不能复制

3.2、std::move(...)转移unique_ptr

unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不再拥有原来指针的所有权了。例如:

unique_ptr<T> my_ptr(new T);
unique_ptr<T> my_ptr2=std::move(my_ptr);
unique_ptr<T> my_ptr3=my_ptr2;//报错,不能复制

3.3、make_unique初始化

std::make_shared是c++11的一部分,但std::make_unique不是。它是在c++14里加入标准库的。

auto upw1(std::make_unique<T>());// with make func
std::unique_ptr<T> upw2(new T);  // without make func

使用new的版本重复了被创建对象的键入,但是make_unique函数则没有。重复类型违背了软件工程的一个重要原则:应该避免代码重复,代码中的重复会引起编译次数增加,导致目标代码膨胀。

3.4、unique_ptr与shared_ptr的区别

(1)unique_ptr可以指向一个数组。

std::unique_ptr<int []> ptr(new int[10]);
ptr[9]=9;
std::shared_ptr<int []> ptr2(new int[10]);//这是不合法的

(2)unique_ptr指定删除器和shared_ptr有区别。

std::shared_ptr<int> ptr3(new int(1), [](int *p){delete p;}); // 正确 
std::unique_ptr<int> ptr4(new int(1), [](int *p){delete p;}); // 错误

unique_ptr需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器,可以这样写:

std::unique_ptr<int,void(*)(int *)> ptr(new int(1),[](int *p){delete p;});

3.5、智能指针的选择

关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。

如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。

四、weak_ptr

share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。

4.1、weak_ptr的基本用法

(1)通过use_count()方法获取当前观察资源的引用计数。

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
// 或者
weak_ptr<int> wp2;
wp2=sp;cout << wp.use_count() << endl; //结果讲输出1

(2)通过expired()方法判断所观察资源是否已经释放。

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);if(wp.expired())cout << "weak_ptr无效,资源已释放";
elsecout << "weak_ptr有效";

(3) 通过lock方法获取监视的shared_ptr。

在多线程中,要防止一个线程在使用智能指针,而另一个线程删除指针指针问题,可以使用weak_ptr的lock()方法。

std::weak_ptr<int> gw; 
void f() { auto spt = gw.lock(); if(gw.expired()) { cout << "gw无效,资源已释放"; }else {cout << "gw有效, *spt = " << *spt << endl; } 
}int main() 
{ { auto sp = std::make_shared<int>(42); gw = sp; f(); }f(); return 0; 
}

4.2、weak_ptr返回this指针

shared_ptr中提到不能直接将this指针返回shared_ptr,需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回指针;原因是std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观察this智能指针,调用shared_from_this()方法是,会调用内部这个weak_ptr的lock()方法,将所观察的shared_ptr返回。

再看前面的范例:

std::weak_ptr<int> gw; 
void f() { auto spt = gw.lock(); if(gw.expired()) { cout << "gw无效,资源已释放"; }else {cout << "gw有效, *spt = " << *spt << endl; } 
}int main() 
{ { auto sp = std::make_shared<int>(42); gw = sp; f(); }f(); return 0; 
}

在外面创建MyClass对象的智能指针和通过对象返回this的智能指针都是安全的,因为shared_from_this()是内部的weak_ptr调用lock()方法之后返回的智能指针,在离开作用域之后,sp2的引用计数减为0,A对象会被析构,不会出现A对象被析构两次的问题。

需要注意的是,获取自身智能指针的函数仅在shared_ptr的构造函数被调用之后才能使用,因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。

4.3、weak_ptr解决循环引用问题

在shared_ptr提到智能指针循环引用的问题,因为智能指针的循环引用会导致内存泄漏,可以通过weak_ptr解决该问题,只要将A或B的任意一个成员变量改为weak_ptr。

#include <iostream>
#include <memory>using namespace std;class A;
class B;class B
{
public:shared_ptr<A> aptr;B();~B();private:};B::B()
{cout << "B()" << endl;
}B::~B()
{cout << "B is deleted" << endl;
}class A
{
public:weak_ptr<B> bptr;// 修改为weak_ptrA();~A();private:};A::A()
{cout << "A()" << endl;
}A::~A()
{cout << "A is deleted" << endl;
}int main()
{shared_ptr<A> ap(new A);shared_ptr<B> bp(new B);ap->bptr = bp;bp->aptr = ap;cout << "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构return 0;
}

这样在对B的成员赋值时,即执行bp->aptr=ap;时,由于aptr是weak_ptr,它并不会增加引用计数,所以ap的引用计数仍然会是1,在离开作用域之后,ap的引用计数为减为0,A指针会被析构,析构后其内部的bptr的引用计数会被减为1,然后在离开作用域后bp引用计数又从1减为0,B对象也被析构,不会发生内存泄漏。

4.4、weak_ptr使用注意事项

weak_ptr在使用前需要检查合法性。

weak_ptr<int> wp;
{shared_ptr<int> sp(new int(1));wp=sp;shared_ptr<int> sp_ok = wp.lock();//wp没有重载->操作符。只能这样取所指向的对象
}shared_ptr<int> sp_null=wp.lock();

因为上述代码中sp和sp_ok离开了作用域,其容纳的K对象已经被释放了。得到了一个容纳NULL指针的sp_null对象。在使用wp前需要调用wp.expired()函数判断一下。

因为wp还仍旧存在,虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息。直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。否则weak_ptr无法指到自己所容纳的那个指针资源的当前状态。

如果shared_ptr<int> sp_ok和weak_ptr<int> wp属于同一个作用域:

weak_ptr<int> wp;
shared_ptr<int> sp_ok;
{shared_ptr<int> sp(new int(1));wp = sp;sp_ok = wp.lock();//wp没有重载->操作符。只能这样取所指向的对象
}if (wp.expired())cout << "shared_ptr is destroy" << endl;
elsecout << "shared_ptr not destroy" << endl;

因为sp_ok还没析构,use_count=1。

五、智能指针安全性问题

引用计数本身是安全的,至于智能指针是否安全需要结合实际使用分情况讨论。

(1)多线程代码操作的是同一个shared_ptr的对象,此时是不安全的。

比如std::thread的回调函数,是一个lambda表达式,其中引用捕获一个shared_ptr:

std::thread td([&sp1](){...});

又或者通过回调函数的参数传入的shared_ptr对象,参数类型引用:

void fun(shared_ptr<A> &sp)
{
// ...
}
// ...std::thread td(fun,sp1);

这个时候必然不是线程安全的。

(2)多线程代码操作的不是同一个shared_ptr的对象。

这里指的是管理的数据是同一份,而shared_ptr不是同一个对象。** 比如多线程回调的lambda的是按值捕获的对象。

std::thread([sp1](){...});

另个线程传递的shared_ptr是值传递,而非引用:

void fn(shared_ptr<A>sp) { 
... 
}
..
std::thread td(fn, sp1);

这时候每个线程内看到的sp,他们所管理的是同一份数据,用的是同一个引用计数。但是各自是不同的对象,当发生多线程中修改sp指向的操作的时候,是不会出现非预期的异常行为的。

也就是说,如下操作是安全的:

void fun(shared_ptr<A> sp)
{
// ...if(...){sp=other_sp;}else {sp=other_sp2;}
}

需要注意:所管理数据的线程安全性问题。显而易见,所管理的对象必然不是线程安全的,必然 sp1、sp2、sp3智能指针实际都是指向对象A, 三个线程同时操作对象A,那对象的数据安全必然是需要对象A自己去保证。

总结

智能指针之间不能混用。

weak_ptr要和shared_ptr搭配使用,不能单独使用weak_ptr。

weak_ptr的lock()使用是要先调用lock()再调用expired()。调用lock()之后use_count+1。

相关文章:

[C++]智能指针用法

一、智能指针存在的意义 智能指针主要解决以下问题&#xff1a; &#xff08;1&#xff09;内存泄漏&#xff1a;内存手动释放&#xff0c;使用智能指针可以自动释放。 &#xff08;2&#xff09;共享所有权指针的传播和释放&#xff0c;比如多线程使用同一个对象时析构问题…...

六、行列式基本知识

目录 1、行列式的特性 2、行列式的计算方法: 2.1 通过行列式的定义去计算:对角法则。 2. 2 利用行列式的性质将行列式转化为上三角行列式: ①行列式的性质 : 性质一: 性质二: 性质三: 性质四:行列式之间的加法...

中断系统(详解与使用)

讲解 简介 中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。 假设一个人在家看电视,这时候突然门铃响了,这个人此时就要停止看电视去开门,然后关上门后继续回来…...

uniapp开发微信小程序跳转到另一个小程序中

注意&#xff1a;一开始我的云上务工模块是单独的tabbar界面&#xff0c;但是小程序跳转好像不能直接点击tabbar进行&#xff0c;所以我将这里改成了点击首页中的按钮进行跳转 点击这里进行小程序跳转 目录 基础讲解 uniapp小程序跳转的两个方法 调用说明&#xff08;半屏跳转…...

chatGPT 使用随想

一年前 chatGPT 刚出的时候&#xff0c;我就火速注册试用了。 因为自己就是 AI 行业的&#xff0c;所以想看看国际上最牛的 AI 到底发展到什么程度了. 自从一年前 chatGPT 火出圈之后&#xff0c;国际上的 AI 就一直被 OpenAI 这家公司引领潮流&#xff0c;一直到现在&#x…...

unity Aaimation Rigging使用多个约束导致部分约束失去作用

在应用多个约束时&#xff0c;在Hierarchy的顺序可能会影响最终的效果。例如先应用了Aim Constraint&#xff0c;然后再应用Two Bone Constraint&#xff0c;可能会导致Two Bone Constraint受到Aim Constraint的影响而失效。因此&#xff0c;在使用多个约束时&#xff0c;应该仔…...

什么是ChatGPT

国外有篇文章解释了ChatGPT的开发技术是什么&#xff0c;GPT-3和GPT-4的区别&#xff0c;以及未来的可能性。 截至 2023 年&#xff0c;ChatGPT 等生成式 AI 服务正在全球引起关注&#xff0c;并且正在探索在广泛领域的应用。 您可能想知道 ChatGPT 是使用哪种开发技术制作的&a…...

当我们浪费时我们在浪费什么

世界上的物质和能量不会增加也不会减少&#xff0c;为什么会存在浪费一说呢&#xff1f;是因为人类可以利用和支配的物质和能量是有限的&#xff0c;而且物质和能量的不同组织方式对于人类有着不同的价值。 人类对于世界的事物都有价值评估。例如一个玻璃杯摔碎了&#xff0c;…...

一文搞懂TCP三次握手与四次挥手

什么是TCP协议&#xff1f; TCP&#xff08;Transmission control protocol&#xff09;即传输控制协议&#xff0c;是一种面向连接、可靠的数据传输协议&#xff0c;它是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。 面向连接&#xff1a;数据传…...

FairyGUI × Cocos Creator 3.7.3 引入报错解决

Cocos Creator 3.7.3引入fgui库 package.json添加这个依赖 "devDependencies": {"fairygui-cc": "latest"}执行npm i 报错解决 使用import引入fairygui-cc&#xff0c;就会有报错和警告&#xff0c;简单处理一下。 鼠标随便点一下也会出警告…...

网络原理 - HTTP/HTTPS(5)

HTTPS HTTPS也是一个应用层协议.在HTTP协议的基础上引入了一个加密层. HTTP协议内容都是按照文本的方式明文传输的. 这就导致了在传输过程中出现了一些被篡改的情况. 臭名昭著的"运营商劫持" 下载一个天天动听. 未被劫持的效果,点击下载按钮,就会弹出天天动听的…...

设计模式——抽象工厂模式

定义: 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;提供一个创建一系列或相互依赖对象的接口&#xff0c;而无须指定它们具体的类。 概述:一个工厂可以提供创建多种相关产品的接口&#xff0c;而无需像工厂方法一样&#xff0c;为每一个产品都提供一个具体…...

详解编译和链接!

目录 1. 翻译环境和运行环境 2. 翻译环境 2.1 预处理 2.2 编译 2.3 汇编 2.4 链接 3. 运行环境 4.完结散花 悟已往之不谏&#xff0c;知来者犹可追 创作不易&#xff0c;宝子们&#xff01;如果这篇文章对你们…...

力扣226 翻转二叉树 Java版本

文章目录 题目描述解题思路代码 题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a;root…...

免费的数据恢复软件哪个好?这10个数据恢复软件可以试试

遇到电脑、硬盘或U盘等设备中数据丢失&#xff0c;不用着急&#xff0c;数据恢复软件来帮你。 在遇到数据丢失的问题时&#xff0c;很多朋友都会很着急也不知道该怎么办。作为数据恢复小白&#xff0c;我们可以选择使用数据恢复软件进行扫描恢复。现在市面上的数据恢复软件很多…...

力扣2476二叉搜索树最近节点查询

题目来源 力扣2476二叉搜索树最近节点查询 题目概述 给你一个 二叉搜索树 的根节点 root &#xff0c;和一个由正整数组成、长度为 n 的数组 queries 。 请你找出一个长度为 n 的 二维 答案数组 answer &#xff0c;其中 answer[i] [mini, maxi] &#xff1a; mini 是树中…...

板块一 Servlet编程:第六节 HttpSession对象全解 来自【汤米尼克的JAVAEE全套教程专栏】

板块一 Servlet编程&#xff1a;第六节 HttpSession对象全解 一、什么是HttpSessionSession的本质 二、创建Seesion及常用方法三、Session域对象四、Session对象的销毁 在上一节中&#xff0c;我们学习了Servlet五大对象里的第三个Cookie对象&#xff0c;但Cookie是有大小限制和…...

后端设计PNR一点总结

条条大路通罗马 在追求极致PPA的过程中,时序问题总是可以解决 方法总比困难多 关键问题其实就是控制delay 不多不少,简单总结二十一条(欢迎大家评论区继续发挥): module padding的设置,可以有效解决congestion问题,factor自己try,命令:setPlaceMode -place_global…...

BI 数据分析,数据库,Office,可视化,数据仓库

AIGC ChatGPT 职场案例 AI 绘画 与 短视频制作 PowerBI 商业智能 68集 Mysql 8.0 54集 Oracle 21C 142集 Office 2021实战应用 Python 数据分析实战&#xff0c; ETL Informatica 数据仓库案例实战 51集 Excel 2021实操 100集&#xff0c; Excel 2021函数大全 80集 Excel 2021…...

汽车信息安全--S32K3的HSE如何与App Core通信(1)?

目录 1.S32K3 网络安全架构 2. MU的通信流程 2.1 总体描述 2.2 Host 消息类型 2.3 寄存器概述...

arcgisPro制图输出

1、设置地图底图 2、导入数据 3、 设置图形颜色&#xff0c;如下&#xff1a;右键“浙江省”数据层&#xff0c;选择符号系统 4、在右侧可看到打开的符号系统栏&#xff0c;进行如下设置: 5、移除“其他所有值”项&#xff0c;如下&#xff1a; 6、设置图形轮廓&#xff0c;如下…...

产品化Chatgpt所面临的五大技术挑战

2022年11月&#xff0c;ChatGPT横扫全球&#xff0c;成为应用人工智能&#xff08;AI&#xff09;领域迄今为止最令人惊叹的“哇&#xff01;”时刻&#xff0c;也是当前科技投资激增的催化剂。2023年11月&#xff0c;首席执行官Sam Altman宣布该产品周用户量达到1亿&#xff0…...

8.qt5使用opencv的库函数打开图片

1.配置opencv动态库的环境变量 2.在创建的qt工程中加入如下opencv代码&#xff0c;具体代码如下&#xff1a; 使用opencv库函数显示图片...

学习 python的第四天,顺便分享两首歌:we don‘ talk anymore,You ‘re Still The One

诸君晚上好&#xff0c;现在是&#x1f303;晚上&#xff0c;今天是学习python的第四个学习日&#xff0c;不知不觉学了四天了&#xff0c;还是那句话&#xff1a;不积跬步无以至千里、不积小流无以成江海&#xff01; 暂时回顾下前面的学习日吧&#xff1a; 第一个学习日----…...

uniapp:APP端webview拦截H5页面跳转,华为市场发布需要限制webview的H5页面跳转

在使用uniapp开发APP项目时&#xff0c;华为市场上线APP会被打回来&#xff1a;您的应用内容存在点击跳转至第三方应用市场或游戏中心下载渠道的问题&#xff0c;不符合华为应用市场审核标准。 华为审核指南4.6 因此可以考虑下面的处理方式&#xff0c;通过拦截webview页面的…...

[HTML]Web前端开发技术28(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;佬佬会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…...

计算机网络实验六 OSPF

一、实验目的和要求 1、掌握 OSPF 的基本配置方法; 2、理解 OSPF 的工作原理。见实验指导书 二、实验环境 1、运行 Windows 2008 Server/XP/7 操作系统的 PC 一台。 2、PacketTracer。 三、实验内容与过程(实验题目和代码) 实验内容: 根据以下任务配置网络:某单位拥…...

亿道丨三防平板丨加固平板丨为零售业提供四大优势

随着全球经济的快速发展&#xff0c;作为传统行业的零售业也迎来了绝佳的发展机遇&#xff0c;在互联网智能化的大环境下&#xff0c;越来越多的零售企业选择三防平板电脑作为工作中的电子设备。作为一种耐用的移动选项&#xff0c;三防平板带来的不仅仅是坚固的外壳。坚固耐用…...

RK3568平台开发系列讲解(Linux系统篇)SPI 客户端通信

🚀返回专栏总目录 文章目录 一、spi_transfer二、spi_message三、初始化沉淀、分享、成长,让自己和他人都能有所收获!😄 SPI I/O模型由一组队列消息组成。我们提交一个或多个struct spi_message结构时,这些结构以同步或异步方式处理完成。单个消息由一个或多个struct sp…...

MySql-DQL-聚合函数

目录 聚合函数统计该企业员工数量count&#xff08;字段&#xff09;count&#xff08;常量&#xff09;count&#xff08;*&#xff09; 统计该企业最早入职的员工统计该企业最迟入职的员工统计该企业员工 ID 的平均值统计该企业员工的 ID 之和 聚合函数 之前我们做的查询都是…...