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

智能指针|巨巨巨详细

智能指针 shared_ptrshared_ptr的基本用法使用shared_ptr要注意的问题 unique_ptr独占的智能指针weak_ptr弱引用的智能指针weak_ptr的基本用法weak_ptr返回this指针weak_ptr解决循环引用问题 weak_ptr使用注意事项

智能指针

C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己 管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的 概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常 时内存泄露等问题等,使用智能指针能更好的管理堆内存。

C++里面的四个智能指针: auto_ptr,unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持, 并且第一个已经被C++11弃用。

shared_ptr

共享的智能指针 std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析 构的时候,内存才会被释放。 shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个 shared_ptr对象销毁时,被管理对象自动销毁。 简单来说,shared_ptr实现包含了两部分,

  • 一个指向堆上创建的对象的裸指针:raw_ptr

  • 一个指向内部隐藏的、共享的管理对象:share_count_object

第一部分没什么好说的,第二部分是需要关注的重点. use_count:当前这个堆上对象被多少对象引用了,简单来说就是引用计数。

shared_ptr的基本用法

  1. 初始化

通过构造函数、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));if(p3) {cout << "p3 is not null";}

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

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

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

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

shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数和辅助方法来初始化。 对于一个未初始化的智能指针,可以通过reset方法来初始化,当智能指针有值的时候调用reset会引起 引用计数减1。另外智能指针通过重载的bool类型操作符来判断是否为空。

#include <iostream>#include <memory>using namespace std;void test(shared_ptr<int> sp) {// sp在test里面的作用域cout << "test sp.use_count()" <<sp.use_count() << endl;}int main(){auto sp1 = make_shared<int>(100); // 优先使用make_shared来构造智能指针//相当于shared_ptr<int> sp2(new int(100));//    std::shared_ptr<int> p = new int(1);  // 不能将一个原始指针直接赋值给一个智能指针std::shared_ptr<int> p1;p1.reset(new int(1));  // 有参数就是分配资源std::shared_ptr<int> p2 = p1;// 引用计数此时应该是2cout << "p2.use_count() = " << p2.use_count()<< endl;  // 2cout << "p1.use_count() = " << p1.use_count()<< endl;  // 2p1.reset();   // 没有参数就是释放资源cout << "p1.reset()\n";// 引用计数此时应该是1cout << "p2.use_count()= " << p2.use_count() << endl;  // 1cout << "p1.use_count() = " << p1.use_count()<< endl;  // 2if(!p1) {  // p1是空的cout << "p1 is empty\n";}if(p2) { // p2非空cout << "p2 is not empty\n";}p2.reset();cout << "p2.reset()\n";cout << "p2.use_count()= " << p2.use_count() << endl;if(!p2) {cout << "p2 is empty\n";}shared_ptr<int> sp5(new int(100));test(sp5);cout << "sp5.use_count()" << sp5.use_count() << endl;return 0;}/*p2.use_count() = 2p1.use_count() = 2p1.reset()p2.use_count()= 1p1.use_count() = 0p1 is emptyp2 is not emptyp2.reset()p2.use_count()= 0p2 is emptytest sp.use_count()2sp5.use_count()1*/
  1. 获取原始指针

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

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

谨慎使用p.get()的返回值,如果你不知道其危险性则永远不要调用get()函数。 

p.get()的返回值就相当于一个裸指针的值,不合适的使用这个值,上述陷阱的所有错误都有可能发生, 遵守以下几个约定: 不要保存p.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的 保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针 不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误

  1. 指定删除器

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

#include <iostream>#include <memory>using namespace std;void DeleteIntPtr(int *p) {cout << "call DeleteIntPtr" << endl;delete p;}int main(){std::shared_ptr<int> p(new int(1), DeleteIntPtr);std::shared_ptr<int> p2(new int(1), [](int *p) {cout << "call lambda1 delete p" << endl;delete p;});std::shared_ptr<int> p3(new int[10], [](int *p) {cout << "call lambda2 delete p" << endl;delete [] p; // 数组删除});return 0;}/*call lambda2 delete pcall lambda1 delete pcall DeleteIntPtr*/

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

std::shared_ptr<int> p(new int(1), [](int *p) {cout << "call lambda delete p" << endl;delete p;});

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

std::shared_ptr<int> p3(new int[10], [](int *p) { delete [] p;});
  • 智能指针什么时候需要指定删除器

在需要 delete 以外的析构行为的时候用. 因为 shared_ptr 在引用计数为 0 后默认调用 delete ptr; 如果不满足需求就要提供定制的删除器.

一些场景:

  • 资源不是 new 出来的(一般也意味着不能 delete), 比如可能是 malloc 出来的

  • 资源是被第三方库管理的 (第三方提供 资源获取 和 资源释放 接口, 那么要么写一个 wrapper 类要么就提供定制的 deleter)

  • 资源不是 RAII 的, 意味着析构函数不会把资源完全释放掉...也就是单纯 delete 还不够, 还得做额外的操作比如你的 end_connection 的例子. 虽然我觉得这个是 bad practice

使用shared_ptr要注意的问题

  1. 不要用一个原始指针初始化多个shared_ptr,例如下面错误范例:

int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); // 逻辑错误
  1. 不要在函数实参中创建shared_ptr,对于下面的写法:

function(shared_ptr<int>(new int), g()); //有缺陷

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

shared_ptr<int> p(new int);
function(p, g());
  • 形参和实参的区别和联系

  1. 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。

  2. 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。

  3. 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。

  4. 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。

请看下面的例子:

#include <stdio.h>//计算从m加到n的值
int sum(int m, int n) {int i;for (i = m+1; i <= n; ++i) {m += i;}return m;
}int main() {int a, b, total;printf("Input two numbers: ");scanf("%d %d", &a, &b);total = sum(a, b);printf("a=%d, b=%d\n", a, b);printf("total=%d\n", total);return 0;
}
/*
运行结果:
Input two numbers: 1 100
a=1, b=100
total=5050
*/

在这段代码中,函数定义处的 m、n 是形参,函数调用处的 a、b 是实参。通过 scanf() 可以读取用户输入的数据,并赋值给 a、b,在调用 sum() 函数时,这份数据会传递给形参 m、n。

从运行情况看,输入 a 值为 1,即实参 a 的值为 1,把这个值传递给函数 sum() 后,形参 m 的初始值也为 1,在函数执行过程中,形参 m 的值变为 5050。函数运行结束后,输出实参 a 的值仍为 1,可见实参的值不会随形参的变化而变化。

  1. 通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针 本质上是一个裸指针,因此,这样可能会导致重复析构,看下面的例子。

//1-1-shared_from_this
#include <iostream>
#include <memory>using namespace std;class A
{
public:shared_ptr<A>GetSelf(){return shared_ptr<A>(this); // 不要这么做}~A(){cout << "Deconstruction A" << endl;}
};int main()
{shared_ptr<A> sp1(new A);shared_ptr<A> sp2 = sp1->GetSelf();cout << "sp1.use_count() = " << sp1.use_count()<< endl;cout << "sp2.use_count() = " << sp2.use_count()<< endl;return 0;
}/*
sp1.use_count() = 1
sp2.use_count() = 1
Deconstruction A
Deconstruction A
*/

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

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

//1-1-shared_from_this2
#include <iostream>
#include <memory>using namespace std;class A: public std::enable_shared_from_this<A>
{
public:shared_ptr<A>GetSelf(){return shared_from_this(); //}~A(){cout << "Deconstruction A" << endl;}
};int main()
{// auto spp = make_shared<A>();shared_ptr<A> sp1(new A);shared_ptr<A> sp2 = sp1->GetSelf();  // ok//    shared_ptr<A> sp2;//    {//        shared_ptr<A> sp1(new A);//        sp2 = sp1->GetSelf();  // ok//    }cout << "sp1.use_count() = " << sp1.use_count()<< endl;cout << "sp2.use_count() = " << sp2.use_count()<< endl;return 0;
}
/*
sp1.use_count() = 2
sp2.use_count() = 2
Deconstruction A
*/

weak_ptr章节我们继续讲解使用shared_from_this()的原因。

  1. 避免循环引用。循环引用会导致内存泄漏,比如:

#include <iostream>
#include <memory>
using namespace std;class A;
class B;class A {
public:std::shared_ptr<B> bptr;~A() {cout << "A is deleted" << endl;}
};class B {
public:std::shared_ptr<A> aptr;~B() {cout << "B is deleted" << endl;  // 析构函数后,才去释放成员变量}
};int main()
{std::shared_ptr<A> pa;{std::shared_ptr<A> ap(new A);std::shared_ptr<B> bp(new B);ap->bptr = bp;bp->aptr = ap;pa = ap;// 是否可以手动释放呢?
//        ap.reset();ap->bptr.reset(); // 手动释放成员变量才行}cout<< "main leave. pa.use_count()" << pa.use_count() << endl;  // 循环引用导致ap bp退出了作用域都没有析构return 0;
}/*
B is deleted
main leave. pa.use_count()1
A is deleted
*/

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

解决的办法是把A和B任何一个成员变量改为weak_ptr,具体方法见weak_ptr章节。

  • 什么是循环引用?

循环引用(circular reference)是指在编程中,两个或多个对象之间形成一个循环的引用关系,导致这些对象之间的内存无法被正确释放,从而引发内存泄漏。这种情况也被称为循环依赖或循环关联。

unique_ptr独占的智能指针

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

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

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

unique_ptr<T> my_ptr(new T); // 正确
unique_ptr<T> my_other_ptr = std::move(my_ptr); // 正确
unique_ptr<T> ptr = my_ptr; // 报错,不能复制

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

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

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

除了unique_ptr的独占性, unique_ptr和shared_ptr还有一些区别,比如

  • unique_ptr可以指向一个数组,代码如下所示

std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
std::shared_ptr<int []> ptr2(new int[10]); // 这个是不合法的
  • 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*)> ptr5(new int(1), [](int *p){delete p;}); //正确

关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。如果希望只有一个智能指针管 理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。

#include <iostream>
#include <memory>
using namespace std;struct MyDelete
{void operator()(int *p){cout << "delete" << endl;delete p;}
};int main()
{auto upw1(std::make_unique<int[]>(2));     // with make func  make_unique需要设置c++14std::unique_ptr<int> upw2(new int); // without make funcupw1[21] = 1;std::unique_ptr<int,MyDelete> ptr2(new int(1));
//    auto ptr(std::make_unique<int, MyDelete>(1));cout << "main finish! " << upw1[1] << endl;return 0;
}/*
main finish! 0
delete
*/

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 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr 是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引 用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数, 和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得 shared_ptr。 

weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得 资源的监测权,它的构造不会增加引用计数,它的析构也不会减少引用计数,纯粹只是作为一个旁观者 来监视shared_ptr中管理的资源是否存在。weak_ptr还可以返回this指针和解决循环引用的问题。

weak_ptr的基本用法

  1. 通过use_count()方法获取当前观察资源的引用计数,如下所示:

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; //结果讲输出1
  1. 通过expired()方法判断所观察资源是否已经释放,如下所示:

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())cout << "weak_ptr无效,资源已释放";
elsecout << "weak_ptr有效";
  1. 通过lock方法获取监视的shared_ptr,如下所示:

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

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返回,再看 前面的范例

//1-1-shared_from_this2
#include <iostream>
#include <memory>using namespace std;class A: public std::enable_shared_from_this<A>
{
public:shared_ptr<A>GetSelf(){return shared_from_this(); }~A(){cout << "Deconstruction A" << endl;}
};int main()
{// auto spp = make_shared<A>();shared_ptr<A> sp1(new A);shared_ptr<A> sp2 = sp1->GetSelf();  // ok//    shared_ptr<A> sp2;//    {//        shared_ptr<A> sp1(new A);//        sp2 = sp1->GetSelf();  // ok//    }cout << "sp1.use_count() = " << sp1.use_count()<< endl;cout << "sp2.use_count() = " << sp2.use_count()<< endl;return 0;
}
/*
sp1.use_count() = 2
sp2.use_count() = 2
Deconstruction A
*/

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

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

weak_ptr解决循环引用问题

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

#include <iostream>
#include <memory>
using namespace std;class A;
class B;class A {
public:std::weak_ptr<B> bptr; // 修改为weak_ptrint *val;A() {val = new int(1);}~A() {cout << "A is deleted" << endl;delete  val;}
};class B {
public:std::shared_ptr<A> aptr;~B() {cout << "B is deleted" << endl;}
};//weak_ptr 是一种不控制对象生命周期的智能指针,
void test()
{std::shared_ptr<A> ap(new A);std::weak_ptr<A> wp1 = ap;std::weak_ptr<A> wp2 = ap;cout<< "ap.use_count()" << ap.use_count()<< endl;
}void test2()
{std::weak_ptr<A> wp;{std::shared_ptr<A> ap(new A);wp = ap;}cout<< "wp.use_count()" << wp.use_count() << ", wp.expired():" << wp.expired() << endl;if(!wp.expired()) {// wp不能直接操作对象的成员、方法std::shared_ptr<A> ptr = wp.lock(); // 需要先lock获取std::shared_ptr<A>*(ptr->val) = 20;  }
}int main()
{test2();
//    {
//        std::shared_ptr<A> ap(new A);
//        std::shared_ptr<B> bp(new B);
//        ap->bptr = bp;
//        bp->aptr = ap;
//    }cout<< "main leave" << endl;return 0;
}
/*
A is deleted
wp.use_count()0, wp.expired():1
main leave
*/

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

weak_ptr使用注意事项

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

weak_ptr<int> wp;
{shared_ptr<int> sp(new int(1)); //sp.use_count()==1wp = sp; //wp不会改变引用计数,所以sp.use_count()==1shared_ptr<int> sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
}
shared_ptr<int> sp_null = wp.lock(); //sp_null .use_count()==0;

因为上述代码中sp和sp_ok离开了作用域,其容纳的K对象已经被释放了。 得到了一个容纳NULL指针的sp_null对象。在使用wp前需要调用wp.expired()函数判断一下。 因为wp还仍旧存在,虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息。直到最后 一个weak_ptr对象被析构,这块“堆”存储块才能被回收。

如果shared_ptr sp_ok和weak_ptr wp;属于同一个作用域呢?如下所示:

weak_ptr<int> wp;
shared_ptr<int> sp_ok;
{shared_ptr<int> sp(new int(1)); //sp.use_count()==1wp = sp; //wp不会改变引用计数,所以sp.use_count()==1sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
}
if(wp.expired()) {cout << "shared_ptr is destroy" << endl;
} else {cout << "shared_ptr no destroy" << endl;
}/*
shared_ptr no destroy
*/

相关文章:

智能指针|巨巨巨详细

智能指针 shared_ptrshared_ptr的基本用法使用shared_ptr要注意的问题 unique_ptr独占的智能指针weak_ptr弱引用的智能指针weak_ptr的基本用法weak_ptr返回this指针weak_ptr解决循环引用问题 weak_ptr使用注意事项 智能指针 C程序设计中使用堆内存是非常频繁的操作&#xff0…...

硬件知识(2) 手机的传感器-sensor

#灵感# 看看小米在干啥 手机型号&#xff1a;Redmi Note 13 Pro&#xff0c;解读一下它宣传的手机卖点。 目录 宣传1&#xff1a;1/1.4" 大底&#xff0c;f/1.65 大光圈&#xff0c; 宣传2&#xff1a;支持 2 亿像素超清直出&#xff0c;分辨率高达 16320 x 12240 宣…...

Kotlin快速入门系列9

Kotlin对象表达式和对象声明 对象表达式 有时&#xff0c;我们想要创建一个对当前类有些许修改的对象同时又不想重新声明一个子类。如果是Java&#xff0c;可以用匿名内部类的概念来解决这个问题。kotlin的对象表达式和对象声明就是为了实现这一点(创建一个对某个类做了轻微改…...

nginx+nginx-rtmp-module+ffmpeg进行局域网推流rtmp\m3u8

局域网推流的简单方式 这里以ubuntu为例 一、先下载安装包 nginx、nginx-rtmp-module&#xff0c;再一起安装 # 下载nginx # 这里我安装的是 nginx-1.10.3 版本 cd /usr/software wget http://nginx.org/download/nginx-1.25.0.tar.gz tar -zxvf nginx-1.25.0.tar.gz# 下载ng…...

PMP备考笔记:模拟考试知识点总结

1. 答题思路&#xff1a;优先看问题&#xff0c;可节省时间。 2. 考试就按照考试的套路来做&#xff0c;不要过多考虑。 开发团队只专注当前冲刺目标&#xff0c;产品负责人对PB排优先级。 收集需求工具-原型法&#xff1a;能够让用户提前体验&#xff0c;减少返工的风险。 …...

docker程序镜像的安装

目录 一、流程 二、总结 一、 流程 对文中脚本测试前提默认系统已安装docker docker程序部署命令脚本 加载已打包的docker程序 docker load < sto...p.tar创建网络 名称为c…m子网subnet 172.27.16.1/24网关gateway 172.27.16.254-d 指定网络驱动程序 docker network cre…...

openssl3.2 - .pod文件的查看方法

文章目录 .pod文件的查看方法概述笔记初步的解决方法备注 - pod2html.bat的详细用法好像Perl就自带这个BATEND .pod文件的查看方法 概述 看到openssl源码目录下有很多.pod文件, 软件发布的帮助内容都在里面. 当make install后, 大部分的.pod都会转成html文件, 但是有一部分…...

力扣238. 除自身以外数组的乘积(前后缀和)

Problem: 238. 除自身以外数组的乘积 文章目录 题目描述思路复杂度Code 题目描述 思路 思路1&#xff1a; 1.先求取数组的包括当前下标值得前后缀乘积&#xff08;利用两个数组记录下来分别为leftProduct和rightProduct&#xff09; 2.当求取一个下标为i的数组中的元素&#x…...

Java玩转《啊哈算法》排序之快速排序

心无挂碍&#xff0c;无挂碍故&#xff0c;无有恐怖&#xff0c;远离颠倒梦想&#xff0c;究竟涅槃。 地图 引子代码地址快速排序核心代码优劣完整代码演示 课后习题 引子 搭嘎好&#xff01;本人最近看的《啊哈算法》这本书写的确实不错&#xff0c;生动形象&#xff0c;在保…...

静态代理IP该如何助力Facebook多账号注册运营?

在Facebook运营中&#xff0c;充分利用静态代理IP是多账号运营的关键一环。通过合理运用静态代理IP&#xff0c;不仅可以提高账号安全性&#xff0c;还能有效应对Facebook的算法和限制。以下是这些关键点&#xff0c;可以帮助你了解如何运用静态代理IP进行Facebook多账号运营&a…...

npm 淘宝镜像正式到期

由于node安装插件是从国外服务器下载&#xff0c;如果没有“特殊手法”&#xff0c;就可能会遇到下载速度慢、或其它异常问题。 所以如果npm的服务器在中国就好了&#xff0c;于是我们乐于分享的淘宝团队干了这事。你可以用此只读的淘宝服务代替官方版本&#xff0c;且同步频率…...

【Spring Boot 3】【@Scheduled】多线程执行定时任务

【Spring Boot 3】【@Scheduled】多线程执行定时任务 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新…...

TypeScript 基础学习

TypeScript是具有类型语法的JavaScript。 是JavaScript超集&#xff0c;可以编译为纯JavaScript&#xff0c;构建安全可靠的代码&#xff0c;进行类似 babel 的转换。 一种基于JavaScript的强类型、静态的编程语言&#xff0c;提供了类型检测的工具。 特性&#xff1a; 易读…...

《CSS3》田字网格背景(外实线内虚线)的实现

一、前言 记录一些有趣的CSS实现方式&#xff0c;总所周知&#xff0c;当一段效果可以通过CSS实现的时候&#xff0c;绝不使用Javascript来实现&#xff0c;因此记录一些有意思的CSS效果&#xff0c;一来是方便自己学习&#xff0c;另一来是方便以后在需要使用到的时候能快速找…...

图书管理系统(ArrayList和LinkedList)--versions3.0

目录 一、项目要求&#xff1a; 二、项目环境 三、项目使用的知识点 四、项目代码 五、项目运行结果 六、项目难点分析 图书管理系统--versions1.0&#xff1a; 图书管理系统--versions1.0-CSDN博客文章浏览阅读981次&#xff0c;点赞29次&#xff0c;收藏17次。本文使用…...

游戏开发丨基于Pygame的AI版贪吃蛇小游戏

文章目录 写在前面需求分析程序设计程序分析运行结果系列文章写在后面 写在前面 本期内容 基于pygame的AI版贪吃蛇小游戏 所需环境 pythonpycharm或anacondapygame 下载地址 https://download.csdn.net/download/m0_68111267/88789665 需求分析 本游戏使用Pygame模块开…...

qt-C++笔记之QStringList、QList<QString>、QString、QChar、QList<QChar>区别

qt-C笔记之QStringList、QList、QString、QChar、QList区别 —— 杭州 2024-01-30 凌晨0:27 参考博文&#xff1a;qt-C笔记之QStringList code review! 文章目录 qt-C笔记之QStringList、QList<QString>、QString、QChar、QList<QChar>区别1.Qt的字符容器类1.QSt…...

python爬虫学习之解析_BeautifulSoup

目录 一、bs4的基本使用 &#xff08;1&#xff09;导入 &#xff08;2&#xff09;创建对象 二、节点定位 1、根据标签名查找节点 2、基本函数使用 &#xff08;1&#xff09;find &#xff08;2&#xff09;find_all &#xff08;3&#xff09;select 三、节点信息 1、获取节…...

2024美赛数学建模赛题解读常用模型算法

回归拟合预测 拟合预测是建立一个模型去逼近实际数据序列的过程&#xff0c;适用于发展性的体系。建立模型时&#xff0c;通常都要指定一个有明确意义的时间原点和时间单位。而且&#xff0c;当t趋向于无穷大时&#xff0c;模型应当仍然有意义。将拟合预测单独作为一类体系研究…...

NoSQL 数据库管理系统和模型的比较

前些天发现了一个人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;最重要的屌图甚多&#xff0c;忍不住分享一下给大家。点击跳转到网站。 NoSQL 数据库管理系统和模型的比较 介绍 当大多数人想到数据库时&#xff0c;他们通常会想到传统的关系数据库…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...