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

智能指针及强相关知识经验总结 --- 移动语义、引用计数、循环引用、move()、自定义删除器等

目录

前言

一、shared_ptr

1. 基本用法和构造方法

2. 引用计数机制

3. weak_ptr 解决循环引用

二、unique_ptr

1. 基本用法和构造方法

2. 独占性

3. 所有权转移

1)unique_ptr :: release()

2)移动语义 和 move()

三、 对比 shared_ptr 和 unique_ptr

unique_ptr:

shared_ptr:

自定义删除器

默认删除器

总结 


前言

        智能指针的主要目的是自动管理内存分配和释放,以减少程序员错误和减轻程序员的负担。它是通过一个特殊的类,封装了一个指针,并添加一些额外的语义来实现的。使用智能指针的好处在于,它可以帮助程序员避免常见的内存错误,如内存泄漏和使用已被释放的内存。智能指针还可以使代码更加清晰和易于理解,因为它们使得内存分配和所有权转移变得显式和清晰。现我们将从概念和实际场景对共享指针独占指针进行理解和使用,掌握如何高效的使用智能指针来便捷整个项目的编码操作。

一、shared_ptr

1. 基本用法和构造方法

对其而言,有两种常用的构造方法,具体使用方法和普通指针类似,下面给出示例:

构造方法:

// 1.创建一个 shared_ptr,指向 int 类型的对象
std::shared_ptr<int> sharedInt = std::make_shared<int>(42);// 2.创建一个 shared_ptr,指向一个动态分配的对象
std::shared_ptr<double> sharedDouble(new double(3.14));

访问指针(使用):

// 1.视作普通指针进行访问
*sharedInt = 10;
std::cout << "Value of sharedInt: " << *sharedInt << std::endl;
// 2.调用get()方法取到普通指针再访问
*sharedInt.get() = 20;
std::cout << "Value of sharedInt: " << *sharedInt.get() << std::endl;

通过上面的代码示例,我们了解到 shared_ptr 的常用构造方法有两种:

(1)使用 make_shared():是推荐的创建 shared_ptr 的方法,它在单次分配中同时创建对象和控制块,效率更高

(2)直接使用 “new” 出的对象初始化:可以直接使用 new 关键字创建动态分配的对象并将其传递给 shared_ptr 构造函数。但这样做可能导致性能损失,因为需要额外的内存用于控制块。

对指针的访问方法明明可以直接对智能指针变量进行访问,那 get() 方法存在的必要性是什么?

当我们需要将指针传给接受普通指针的函数或功能模块时,智能指针是不能被函数接收后自动隐式类型转换为所需类型的,所以 get() 方法很好地解决了这个问题,下面给出示例代码:

print_int(sharedInt);       // 编译报错
print_int(sharedInt.get());

2. 引用计数机制

shared_ptr 特性:

  • 提供共享所有权的智能指针。
  • 使用引用计数来追踪资源的所有者数量。
  • 当最后一个 shared_ptr 指向资源销毁时,资源被释放。

至于共享所有权为了便于对比理解,本文后面再阐述,这里先来理解引用计数的概念:

引用计数:

share_ptr 使用一个控制块(control block)来管理引用计数和其他信息。控制块是在内存中动态分配的,包含引用计数和指向实际对象的指针。当创建新的 shared_ptr 时,会为对象分配一个新的控制块,并将引用计数初始化为 1当共享指针被复制或赋值时,引用计数递增当共享指针被销毁(超出作用域)时,引用计数递减。当引用计数变为零时,说明没有任何指针指向该对象,因此对象和控制块的内存都会被释放。

shared_ptr :: use_count()

shared_ptr :: reset()

为了便于理解,下面利用 shared_ptr 内置的 use_count() 方法,使用代码和运行结果帮助感受引用计数变化的过程: 

// 首次创建一个 shared_ptr,引用计数初始为 1
std::shared_ptr<int> sp1 = std::make_shared<int>(42);
// 打印引用计数
cout << sp1.use_count() << endl;// 创建另一个 shared_ptr,引用计数增加为 2
std::shared_ptr<int> sp2 = sp1;
cout << sp2.use_count() << endl;// 创建另一个 shared_ptr,引用计数增加为 3
std::shared_ptr<int> sp3 = sp2;
cout << sp3.use_count() << endl;// 销毁一个 shared_ptr 对象,引用计数减 1,变为 2
sp2.reset();
cout << sp3.use_count() << endl;

运行结果:

要注意的是,使用引用来引用智能指针变量并不会引起变量引用计数的变化

// 为了避免代码冗杂,下面代码直接续在前面代码后,不再重复给出
auto& ref_sp = sp1;
cout << ref_sp.use_count() << endl;

 运行结果:

ref_sp 仅仅是智能指针 sp1 的一个引用,它们共享相同的引用计数,因此不会增加引用计数。这只是一个别名,没有创建新的智能指针对象。

3. weak_ptr 解决循环引用

weak_ptr 主要用于解决 shared_ptr 的循环引用问题。循环引用可能导致对象无法正常释放,因为 shared_ptr 的引用计数永远不会变为零。通过使用 weak_ptr,可以打破循环引用,允许对象在不再被引用时正常释放。

具体什么是循环引用,下面给出简单示例:

class ObjectB; // 提前声明class ObjectA {
public:std::shared_ptr<ObjectB> objectB;       // 注意这里是 shared_ptrObjectA() {std::cout << "ObjectA constructed" << std::endl;}~ObjectA() {std::cout << "ObjectA destructed" << std::endl;}
};class ObjectB {
public:std::weak_ptr<ObjectA> objectA;        // 注意这里是 weak_ptrObjectB() {std::cout << "ObjectB constructed" << std::endl;}~ObjectB() {std::cout << "ObjectB destructed" << std::endl;}
};

考虑两个对象相互引用的情况,其中 ObjectA 持有 shared_ptr<ObjectB>,而 ObjectB 持有 shared_ptr<ObjectA>。这样的循环引用会导致对象永远无法释放。在此案例中,只需要将两者其中之一改为 weak_ptr 即可解决他俩相互推脱都不释放的循环引用造成的问题。

测试用例:

// 创建 shared_ptr 和 weak_ptr
std::shared_ptr<ObjectA> sharedA = std::make_shared<ObjectA>();
std::shared_ptr<ObjectB> sharedB = std::make_shared<ObjectB>();// 建立关联
sharedA->objectB = sharedB;
sharedB->objectA = sharedA;

运行结果: 

当我们正常创建并初始化上面两类对象时,通过观察控制台窗口打印的构造和析构函数内容,发现 weak_ptr 成功使得两对象被系统释放并调用各自的析构函数。

weak_ptr :: lock()

由于 lock() 函数具有返回其保留的对象的能力,所以我们可以利用其特性,在以 weak_ptr 为类成员的类中自建一个功能,用于判断该 weak_ptr 包含的对象即 shared_ptr 是否存在,有没有被释放掉,不妨我们将其命名为 is_exist() ,将其作为类成员函数,方便我们利用类对象调用从而了解以 weak_ptr 为类型的成员变量包含的对象是否被释放。

class ObjectB {
public:std::weak_ptr<ObjectA> objectA;// 构造函数和析构函数bool is_exist()    // 提供访问方法{// 使用 lock() 获取 shared_ptrif (auto sharedPtr = objectA.lock()) {// 对象存在,可以安全地使用 sharedPtrstd::cout << "Object exist!" << std::endl;return true;}else {// 对象已经被销毁std::cout << "Object has been released" << std::endl;return false;}}
};

调用测试 is_exist() 功能:

void test3()
{// 创建 shared_ptr 和 weak_ptrstd::shared_ptr<ObjectA> sharedA = std::make_shared<ObjectA>();std::shared_ptr<ObjectB> sharedB = std::make_shared<ObjectB>();// 建立关联sharedA->objectB = sharedB;sharedB->objectA = sharedA;sharedB->is_exist();    // 测试B类中的 weak_ptr// 1.reset作用于sharedBsharedB.reset();         // 销毁sharedB 使其恢复刚构造完成的状态// 在使用 sharedB 之前检查是否为空if (sharedB) {sharedB->is_exist();}else {std::cout << "sharedB is null." << std::endl;}
}

运行结果:

接着我们尝试销毁ObjectB类型对象内部的 weak_ptr 指针包含的对象:

void test3()
{// 创建 shared_ptr 和 weak_ptrstd::shared_ptr<ObjectA> sharedA = std::make_shared<ObjectA>();std::shared_ptr<ObjectB> sharedB = std::make_shared<ObjectB>();// 建立关联sharedA->objectB = sharedB;sharedB->objectA = sharedA;sharedB->is_exist();      // reset()销毁前作对比用// 2.reset作用于ObjectB类型对象内部的weak_ptr指针包含的对象sharedB->objectA.reset();sharedB->is_exist();
}

运行结果:

我们发现实现的 is_exist() 功能成功反映了 weak_ptr 对象是否被销毁的状态,以便我们进一步安全操作,由于 reset() 后指针变为悬空指针,通过检测避免出现访问悬空指针的情况。

二、unique_ptr

1. 基本用法和构造方法

std::unique_ptr 是 C++11 引入的智能指针,用于管理动态分配的对象,它独占(unique ownership)所指向的对象。与 shared_ptr 不同,std::unique_ptr 不使用引用计数,因此每个 std::unique_ptr 拥有对对象的唯一所有权。这意味着当 std::unique_ptr 被销毁或通过 std::move 转移所有权时,它所管理的对象会被销毁

构造方法:

比如需要创建一个已有类的智能指针,下面给出示例类的声明:

class MyClass {
public:MyClass() {std::cout << "MyClass constructed" << std::endl;}~MyClass() {std::cout << "MyClass destructed" << std::endl;}void DoSomething() {std::cout << "Doing something..." << std::endl;}
};

 具体构造语句:

// 创建 unique_ptr
std::unique_ptr<MyClass> uniquePtr1 = std::make_unique<MyClass>();
std::unique_ptr<MyClass> uniquePtr2(new MyClass);

使用方法:

// 使用 unique_ptr
uniquePtr1->DoSomething();
uniquePtr1.get()->DoSomething();
uniquePtr2->DoSomething();
uniquePtr2.get()->DoSomething();

运行结果: 

对于 unique_ptr 而言,与 shared_ptr 一致的是存在 get() 方法可以取到普通指针。不同的是没有引用计数,独占所拥有的对象,那独占性又是如何体现呢?

2. 独占性

std::unique_ptr 是一种独占所有权的智能指针,它确保一个对象只能由一个 std::unique_ptr 拥有。这意味着任何时候,只有一个 std::unique_ptr 指向一个特定的动态分配对象。这是通过禁止复制构造函数和赋值运算符来实现的,因为这些操作会导致多个 std::unique_ptr 指向同一个对象,破坏了独占性。

注意:这里的独占性是仅仅允许一对一的情况存在,不可多对一和一对多!

不妨我们下面采用各种方式来验证其独占性的规则:

// 创建 unique_ptr
std::unique_ptr<MyClass> uniquePtr1 = std::make_unique<MyClass>();// 1. 尝试复制构造
// std::unique_ptr<MyClass> uniquePtr2 = uniquePtr1; // 编译错误// 2. 尝试赋值运算符
// std::unique_ptr<MyClass> uniquePtr3;
// uniquePtr3 = uniquePtr1; // 编译错误

具体编译报错形式(注意编译器检查标红):

另外,我们还有通过其他方法验证独占性,由于部分概念还未阐述,具体操作会在后面给出。

3. 所有权转移

        出于编程过程中实际需要,我们定义的 unique_ptr 不得不在其他地方被延续使用下去,但是出于一对一的独占性要求,unique_ptr 是不可复制的(包括上面验证的复制构造函数、赋值运算符)。此时应运而生的就是 “所有权转移” ,既然不能复制那就将自身拥有的对象转移给别的对象进行管理。

下面给出案例,便于理解 “所有权转移” 的使用方法和存在意义:

1)unique_ptr :: release()

现在假定我们需要使用该指针作为函数参数实现功能,而接受的参数类型为C语音风格的普通指针,这时候就有两种选择:

(1)利用unique_ptr :: get()

(2)利用unique_ptr :: release()

        通过上图我们了解到 release() 的本质是转移其拥有指针的所有权,以返回值传递权限,通过接受该函数返回值实现所有权接收,要注意的是 release() 后,unique_ptr 会自动置空。这里需要区分一个概念, release() 仅仅是将 unique_ptr 对象的指针置为 nullptr,并不会影响已经转移的资源(或对象)

这意味着上面两方法的本质区别就在于 release() 该行以后的代码都应避免访问和使用 release() 后的 unique_ptr 指针,因为 release() 后的指针此时变为悬空指针,再次访问会报错或产生未定义行为。

为了便于反映问题,我们对 MyClass 类稍作修改,使其具有一个成员变量 n ,类声明如下:

class MyClass {
public:MyClass() {std::cout << "MyClass constructed" << std::endl;}~MyClass() {std::cout << "MyClass destructed" << std::endl;}void DoSomething() {std::cout << "Doing something..." << std::endl;}int n{};     // 自主初始化空
};

我们将以普通指针作函数参数的功能命名为:use_uniquePtr_1(MyClass* mc),实际实现如下:

void use_uniquePtr_1(MyClass* mc)
{cout << "use_uniquePtr_1(MyClass* mc)" << endl;mc->DoSomething();
}

 来看如下代码示例调用上方函数:

// ************* 普通指针 <--> unique_ptr.release() **************
std::unique_ptr<MyClass> up = std::make_unique<MyClass>();use_uniquePtr_1(up.get());
if (!up) { cout << "(1) up is nullptr" << endl; }use_uniquePtr_1(up.release());
if (!up) { cout << "(2) up is nullptr" << endl; }

运行结果:

我们看到运行结果最后并没有打印执行析构函数,正是因为 release() 将 up 的所有权转交给use_uniquePtr_1(up.release()); 函数,然而在该函数内并未在结束时对 up 存储的对象进行释放处理,导致此函数后面执行部分也不能释放这块内存,也就自然造成了内存泄漏,为程序后续更大的灾难性错误奠定了基础。

如果我再给代码后面加一句访问 up 的操作,会报错吗?

std::unique_ptr<MyClass> up = std::make_unique<MyClass>();
use_uniquePtr_1(up.get());
if (!up) { cout << "(1) up is nullptr" << endl; }use_uniquePtr_1(up.release());
if (!up) { cout << "(2) up is nullptr" << endl; }up->DoSomething();     // ### 注意此行 ###
if (!up) { cout << "(3) up is nullptr" << endl; }

运行结果:

我们看到程序依旧没有报错,成功结束整个调试过程,但是我们明明在 release() 后 up 变为了空指针,为什么在调用类内成员函数 up->DoSomething(); 时没有报错呢?结束了,但是不代表没有进行非法操作,将同样的代码在部分编译器和平台就会运行报错!为什么这里程序正常结束没有报错访问空指针?

在 C++ 中,调用空指针的成员函数并不一定会导致运行时错误。在这种情况下,DoSomething()  函数可能是一个非虚函数,而非虚函数在调用时并不会引发空指针解引用导致的崩溃。当然,也有前提就是不通过this指针访问成员变量,比如我下面给出论证:

std::unique_ptr<MyClass> up = std::make_unique<MyClass>();
use_uniquePtr_1(up.get());
if (!up) { cout << "(1) up is nullptr" << endl; }
cout << up->n << endl;			// 注意此行use_uniquePtr_1(up.release());
if (!up) { cout << "(2) up is nullptr" << endl; }
cout << up->n << endl;			// 注意此行up->DoSomething();
if (!up) { cout << "(3) up is nullptr" << endl; }

运行上面代码:

嘿嘿,报错了吧,不急不急,访问this指针才报错是吧,如果将 release() 后的cout代码行换为访问类内静态成员的值,还会报错吗?

代码如下:

std::unique_ptr<MyClass> up = std::make_unique<MyClass>();
use_uniquePtr_1(up.get());
if (!up) { cout << "(1) up is nullptr" << endl; }
//cout << up->n << endl;
cout << up->s_n << endl;			// 注意此行use_uniquePtr_1(up.release());
if (!up) { cout << "(2) up is nullptr" << endl; }
//cout << up->n << endl;
cout << up->s_n << endl;			// 注意此行

运行结果:

具体该部分访问空指针的问题可以参考本人另外一篇文章:悬空指针 ---- 未定义行为

2)移动语义和 move()

        我们了解利用move()实现功能的本质还是实行对象所有权的转移,那么我们不妨以此方法来解决和上面 unique_ptr :: release() 情境下的问题,但是我们注意到 move() 返回值类型与参数类型一致,所以仅需改变函数功能要求,将参数从C语言普通指针转换为智能指针类型,即可实现 move() 测试将外部智能指针通过参数传递方式实现所有权转移。

为了便于理解 move() 的功能,下面给出简单示例:

void uniquePointer_move()
{std::unique_ptr<MyClass> up1 = std::make_unique<MyClass>();std::unique_ptr<MyClass> up2 = move(up1);     // 通过move()使得up2接管了up1拥有的对象// move()后只会释放 up2 管理的对象,而不会释放 up1 的对象// ### up1 在构造时调用构造函数, up2 在析构时调用析构函数 ###
}

运行结果: 

可以看到正如代码注释部分预测的情况一致,并且没有发生内存泄漏的情况。

为了与上面 release() 情形对应,将函数功能名称定义为use_uniquePtr_2(unique_ptr<MyClass>& mc),这里需要对函数参数为智能指针引用类型并非值类型的原因作出解释:unique_ptr 是独占所有权的智能指针,它的移动构造函数将转移所有权的同时使原指针为空。因此,在 use_uniquePtr_2 调用后,实参将不再拥有对象的所有权,实参的值将变成 nullptr。这就导致在 use_uniquePtr_2 函数内部,mc(形参) 是一个空的 nullptr,而在尝试使用 mc->DoSomething(); 时,会导致空指针解引用,从而编译器报错。

具体测试实现如下:

void uniquePointer_move()
{std::unique_ptr<MyClass> up1 = std::make_unique<MyClass>();std::unique_ptr<MyClass> up2 = move(up1);if (up1)      // 试图对 move() 转移后丢失对象所有权的智能指针访问{cout << "up1 is not nullptr" << endl;use_uniquePtr_2(up1);}use_uniquePtr_2(up2);
}

运行结果:

通过结果发现 use_uniquePtr_2(up1); 并未被调用,说明经 move() 函数转移对象所有权后,智能指针up1被置空,还原为初始默认状态,为空指针。如果后面代码在未对 up1 判空的情况下使用,就会造成悬空指针的风险,发生程序未定义行为或程序崩溃

三、 对比 shared_ptr 和 unique_ptr

unique_ptr:

  1. 独占所有权:

    unique_ptr 独占其所指向的对象的所有权。一个特定的 unique_ptr 是唯一能够拥有和管理其指向对象的智能指针。
  2. 轻量级:

    由于独占所有权,unique_ptr 通常比 shared_ptr 更轻量级,因为它不需要维护引用计数。
  3. 移动语义:

    支持移动语义,可以通过移动转移所有权,避免复制开销
  4. 适用场景:

    当你有一个明确的所有权关系,且对象不需要被多个智能指针共享时,使用unique_ptr

shared_ptr:

  1. 共享所有权:

    shared_ptr允许多个智能指针共享对同一对象的所有权,通过引用计数来追踪对象的引用次数。
  2. 相对重量级:

    由于需要维护引用计数,shared_ptr相对于unique_ptr来说可能更重量级。
  3. 循环引用问题:

    当存在循环引用时,shared_ptr需要谨慎使用,因为它可能导致内存泄漏
  4. 适用场景:

    当需要多个智能指针共享同一个对象,并且对象的生命周期不容易预测时,使用shared_ptr

自定义删除器

        众所周知C++中类内的析构函数可以自定义实现释放资源时要进行的额外特定操作,虽然智能指针也是类,但是我们无法修改其析构函数以帮助我们实现释放资源时进行的相关操作,于是我们可以通过构建自定义删除器来指定创建的智能指针对象在析构时要额外执行的指令,通俗讲自定义删除器本质上也是一种仿函数。现在利用仿函数自定义 free() 对C语音风格函数返回的指针处理,并将该规则传给智能指针,删除器简单实现如下:

class FreeDeleter       // 自定义删除器
{
public:void operator()(void* p){free(p);}
};

假定返回一个堆区指针,其解引用所得值为100,函数定义如下:

int* some_c_function()
{int* p = (int*)malloc(sizeof(int));*p = 100;return p;
}

在该函数外利用智能指针接受返回值,实现自动管理返回指针指向的内存空间,请注意如下两种写法的异同:

void do_work_1()        // unique_ptr 接收
{auto p = unique_ptr<int, FreeDeleter>(some_c_function());printf("%d\n", *p.get());
}
void do_work_2()        // shared_ptr 接收
{auto p = shared_ptr<int>(some_c_function(), FreeDeleter());printf("%d\n", *p.get());
}

注意:在 shared_ptr 的构造函数中提供的删除器是用于释放资源的,而在 unique_ptr 模板参数中提供的删除器是用于释放资源的,因此使用时需要小心确保删除器的正确性。

那 shared_ptr 和 unique_ptr 两者对象绑定自定义删除器的方式可以互换吗?

这是运行前的编译器报错显示,所以答案显然是不可以的。

默认删除器

其实如果想要利用智能指针来接管已有指针的指向的内存或包含的对象,更容易想到的是利用默认删除器来实现:

void do_work_3()
{auto p = unique_ptr<int>(some_c_function());printf("%d\n", *p.get());
}

评价:

  1. 简洁性:更为简洁,省略了提供自定义删除器的步骤,适用于一般情况。

  2. 默认删除器:使用默认的删除器,这是 unique_ptr 的默认行为,会调用 delete 进行内存释放。(这就意味着无论处理的是 malloc 还是 new 产生的堆区指针,都用 delete 释放)

        总体来说,选择取决于你对内存释放的需求和是否有额外的清理操作。在一般情况下,使用默认删除器的 do_work_3 更为简洁,而提供自定义删除器的 do_work_1/2 更适用于需要特定清理操作的情况


总结 

易错点和注意事项:

  • std::shared_ptr 需要小心循环引用。
  • 避免裸指针操作,尽可能使用智能指针的接口。
  • 避免混合使用 new/deletestd::make_shared/std::make_unique
  • 不要将 std::shared_ptr 转为 std::unique_ptr,除非确保没有其他 shared_ptr 指向同一资源
  • 注意对智能指针部分操作可能带来的悬空指针问题

        在选择智能指针时,根据对象的所有权需求和额外的清理操作来决定使用 shared_ptr 还是 unique_ptr。shared_ptr 适用于多个指针共享对象的情况,而 unique_ptr 更适合独占所有权的场景。通过灵活运用它们的特性,可以提高代码的安全性和效率。希望本文对正处于学习智能指针阶段的探索者能有一定的指导作用,为日常实际项目的运用中提供有用的建议。

相关文章:

智能指针及强相关知识经验总结 --- 移动语义、引用计数、循环引用、move()、自定义删除器等

目录 前言 一、shared_ptr 1. 基本用法和构造方法 2. 引用计数机制 3. weak_ptr 解决循环引用 二、unique_ptr 1. 基本用法和构造方法 2. 独占性 3. 所有权转移 1&#xff09;unique_ptr :: release() 2&#xff09;移动语义 和 move() 三、 对比 shared_ptr 和 un…...

Gson 自动生成适配器插件

在json解析方面 我们常见有下面几方面困扰 1. moshi code-gen能自动生成适配器,序列化效率比gson快,但是自定义程度不如gson,能java kotlin共存 且解决了默认值的问题 2.gson api 强大自由,但是 第一次gson的反射缓存比较慢,而且生成对象都是反射,除非主动注册com.google.gson…...

React创建项目

React创建项目 提前安装好nodejs再进行下面的操作&#xff0c;通过node -v验证是否安装 1.设置源地址 npm config set registry https://registry.npmmirror.com/2.确认源地址 npm config get registry返回如下 https://registry.npmmirror.com/3.输入命令 npx create-re…...

Redis5新特性-stream

Stream队列 Redis5.0 最大的新特性就是多出了一个数据结构 Stream&#xff0c;它是一个新的强大的 支持多播的可持久化的消息队列&#xff0c;作者声明 Redis Stream 地借鉴了 Kafka 的设计。 生产者 xadd 追加消息 xdel 删除消息&#xff0c;这里的删除仅仅是设置了标志位&am…...

删除PPT文件的备注内容

解决方案的工作经常汇报以及经常做ppt的回报工作&#xff0c;但是删除备注很痛苦。 在网上或者拿历史的ppt文件修改后&#xff0c;需要删除ppt备注内容以及删除ppt个人文件信息的办法&#xff1a; 现象&#xff1a;很多备注信息&#xff0c;需要删除 解决办法一、 文件--信息-…...

2023年亚太杯APMCM数学建模大赛B题玻璃温室小气候调控

2023年亚太杯APMCM数学建模大赛 B题 玻璃温室小气候调控 原题再现 温室作物的产量受各种气候因素的影响&#xff0c;包括温度、湿度和风速[1]。其中&#xff0c;适宜的温度和风速对植物生长至关重要[2]。为了调节玻璃温室内的温度、风速等气候因素&#xff0c;在温室设计中常…...

Oracle 查询语句限制只选择最前面几行,和最后面几行的实现方式。

查询最前面几行 在Oracle中&#xff0c;可以使用 ROWNUM 关键字来限制查询结果的行数。要选择前10条记录&#xff0c;可以使用以下查询语句&#xff1a; SELECT * FROM your_table WHERE ROWNUM < 10;实际查询时将your_table替换为要查询的表名。以上查询将返回表中的前10…...

.NET Core6.0 MVC+layui+SqlSugar 简单增删改查

HTML部分: {ViewData["Title"] "用户列表"; } <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>用户列表</title><meta name"renderer" content"webkit"><meta …...

在 Mac 上使用浅色或深色外观

在 Mac 上&#xff0c;选取苹果菜单 >“系统设置”&#xff0c;然后点按边栏中的“外观” 。&#xff08;你可能需要向下滚动。&#xff09;选择右侧的“浅色”、“深色”或“自动”。 “浅色”表示不会发生变化的浅色外观。 “深色”表示不会发生变化的深色外观。“深色模式…...

华为手环关闭智能适时测量

问题 使用华为手环并使用华为创新研究APP后&#xff0c;会自动打开智能适时测量开关&#xff0c;此开关开启后&#xff0c;手环会在睡眠时间自动测量血氧&#xff0c;增加手环功耗从而影响续航&#xff0c;用户可根据自身需求决定是否开启&#xff0c;下文介绍如何找到此开关。…...

1-Hadoop原理与技术

单选题 题目1&#xff1a;安装Hadoop集群时&#xff0c;是在哪个文件指定哪些机器作为集群的从机&#xff1f; 选项: A datanode B slaves C yarn-site.xml D core-site.xml 答案&#xff1a;B ------------------------------ 题目2&#xff1a;Hadoop配置文件所在目录是哪…...

YoloV5改进策略:Swift Parameter-free Attention,无参注意力机制,超分模型的完美迁移

摘要 https://arxiv.org/pdf/2311.12770.pdf https://github.com/hongyuanyu/SPAN SPAN是一种超分网络模型。SPAN模型通过使用参数自由的注意力机制来提高SISR的性能。这种注意力机制能够增强重要信息并减少冗余,从而在图像超分辨率过程中提高图像质量。 具体来说,SPAN模…...

DAPP开发【04】测试驱动开发

测试驱动开发(Test Driven Development)&#xff0c;是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写测试代码&#xff0c;然后只编写使测试通过的功能代码通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码&#xff0c…...

Raspberry Pi 2, 2 of n - Pi 作为 IoT 消息代理

目录 介绍 环境 先决条件 - 设置静态 IP 地址 安装 Mosquitto 启动/停止 Mosquitto 配置先决条件 - 安装 mqtt_spy 配置 Mosquitto 配置 Mosquitto - 无安全性 测试 Mosquitto 配置 - 无安全性 配置 Mosquitto - 使用密码身份验证 Mosquitto 测试 - 带密码验证 概括 介绍 在本文…...

linux服务器环境搭建(使用yum 安装mysql、jdk、redis)

一:yum的安装 1:下载yum安装包并解压 wget http://yum.baseurl.org/download/3.2/yum-3.2.28.tar.gz tar xvf yum-3.2.28.tar.gz 2.进入yum-3.2.28文件夹中进行安装,执行安装指令 cd yum-3.2.28 sudo apt install yum 3.更新版本 yum check-update yum update yum cle…...

互联网Java工程师面试题·Spring Boot篇·第二弹

目录 8、什么是 YAML&#xff1f; 9、如何实现 Spring Boot 应用程序的安全性&#xff1f; 10、如何集成 Spring Boot 和 ActiveMQ&#xff1f; 11、如何使用 Spring Boot 实现分页和排序&#xff1f; 12、什么是 Swagger&#xff1f;你用 Spring Boot 实现了它吗&#xff1f; …...

【西南交大swjtu微机与接口技术实验】D/A变换实验实验三:波形发生器

做一个存档。实验要求与电路连接见参考指导书。 1、主程序产生锯齿波 2、按下KK1输出五个周期的三角波&#xff0c;继续输出被中断的锯齿波 3、按下KK2输出五个周期的方波&#xff0c;继续输出被中断的锯齿波 程序代码 IOY0 EQU 0600H DA EQU IOT000H*2SSTACK SEGMENT STA…...

【每日一题】从二叉搜索树到更大和树

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;中序遍历的反序方法二&#xff1a;后缀数组 写在最后 Tag 【中序遍历】【二叉树】【2023-12-04】 题目来源 1038. 从二叉搜索树到更大和树 题目解读 在二叉搜索树中&#xff0c;将每一个节点的值替换成树中大于等于该…...

@Scheduled,Quartz,XXL-JOB三种定时任务总结

Scheduled&#xff0c;Quartz&#xff0c;XXL-JOB三种定时任务总结 一、Scheduled 简介 Scheduled 是 Spring 框架中用于声明定时任务的注解。通过使用 Scheduled 注解&#xff0c;你可以指定一个方法应该在何时执行&#xff0c;无需依赖外部的调度器。 这个注解通常与Enab…...

开会做笔记的时候用什么软件比较好?

在工作生涯中&#xff0c;会经历很多大大小小的会议&#xff0c;而如何快速准确记录下会议上重要的内容&#xff0c;成了很多上班族的必修课。在会上做笔记&#xff0c;选择什么样的工具才能事半功倍&#xff0c;成了一个值得深思的问题。而经过一段时间的测评后&#xff0c;我…...

HTML CSS JavaScript的网页设计

一、网页界面效果&#xff1a; 二、HTML代码&#xff1a; <!DOCTYPE html> <!-- 声明文档类型--> <html lang"en"> …...

37.从0到上线三天搭建个人网站(第一天)

点赞收藏加关注&#xff0c;你也能住大别墅&#xff01; 挑战三天搭建个人网站 从0到上线 一、项目的主要功能 1.作为自己在网上的一个工作室。 2.发帖 3.展示个人项目连接 4.介绍自己&#xff08;没准儿还能接点活儿&#xff09; 二、UI风格参考 三、技术选型 1.前端&a…...

室内外融合便携式定位终端5G+UWB+RTK

一、介绍 便携式定位终端主要用于提供高精度的位置数据&#xff0c;支持室内UWB定位和室外北斗系统定位功能&#xff0c;支持5G公网和5G专网通信功能&#xff0c;便携式定位终端中超宽带(UWB)和实时动态(RTK)技术的集成代表了精确位置跟踪方面的重大进步。这款UWBRTK便携式定位…...

使用Java语言判断一个数据类型是奇数还是偶数

判断一个数字类型是奇数&#xff0c;还是偶数&#xff0c;只需要引入Scanner类&#xff0c;然后按照数据类型的定义方式进行定义&#xff0c;比较是按照与2进行整除后的结果&#xff1b;如果余数为零&#xff0c;则代表为偶数&#xff0c;否则为奇数。 import java.util.Scann…...

Java三种代理模式:静态代理、动态代理和CGLIB代理

Java三种代理模式&#xff1a;静态代理、动态代理和CGLIB代理 代理模式 代理模式是23种设计模式种的一种。代理模式是一种结构型设计模式&#xff0c;它允许为其他对象提供一个替代品或占位符&#xff0c;以控制对这个对象的访问。代理模式可以在不修改被代理对象的基础上&am…...

vivado实现分析与收敛技巧9-分析使用率统计数据

实现问题的常见原因之一是未考量显式和隐式物理约束。例如 &#xff0c; 管脚分配 (pinout) 在逻辑布局上变为显式物理约束。 slice&#xff08; 分片 &#xff09; 逻辑在大部分器件中都是一致的。但如下专用资源表示的是隐式物理约束 &#xff0c; 因为这些资源仅在某些位置…...

7nm项目之顶层规划——01数据导入

1.创建workspace 创建workspace后&#xff0c;在其目录下产生。 CORTEXA53.json文件是将有默认配置的文件master.json、有library的.config.json文件、tunes下CORTEXA53.tunes.json文件合并 注&#xff1a;tunes下的CORTEXA53.tunes.json文件可以覆盖一些master.json的设置…...

一键式紧急报警柱系统

随着科技的不断发展&#xff0c;一键式紧急报警柱在我们的生活和工作中扮演着越来越重要的角色。在这篇文章中&#xff0c;我们将一起探究与一键式紧急报警柱有关的知识。 一键式紧急报警柱是一种常见的安全防护设备&#xff0c;能够在紧急情况下快速发出警报&#xff0c;保护…...

4-Docker命令之docker run

1.docker run介绍 docker run命令是用来创建新的容器并运行相关命令 2.docker run用法 docker run [参数] [root@centos79 ~]# docker run --helpUsage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]Create and run a new container from an imageAliases:docker conta…...

【模电】直流通路与交流通路

直流通路与交流通路 通常&#xff0c;在放大电路中&#xff0c;直流电源的作用和交流信号的作用总是共存的&#xff0c;即静态电流、电压和动态电流、电压总是共存的。但是由于电容、电感等电抗元件的存在&#xff0c;直流量所流经的通路与交流信号所流经的通路不完全相同。因此…...

笔记本做网站外网访问/360竞价推广客服电话

苏生不惑第220 篇原创文章&#xff0c;将本公众号设为星标&#xff0c;第一时间看最新文章。之前我发过好几篇关于Python的文章&#xff1a;七夕又来了&#xff0c;给女朋友做个动态二维码一键下载公众号所有文章&#xff0c;导出文件支持PDF&#xff0c;HTML&#xff0c;Markd…...

石岩网站建设/百度seo优化排名客服电话

国内四大单机数据库&#xff1a;武汉达梦DM人大金仓 Kingbase南大通用Gbase神通OSCAR国产分布式数据库&#xff1a;蚂蚁金服 OceanBase腾讯 TDSQL中兴 GoldenDB华为 GaussDB200巨杉 SequoiaDB易鲸捷 EsgynDB万里开源 GreatDB星环科技 KunDB国产云数据库&#xff1a;阿里 Analyt…...

张家港网站建设培训/aso100官网

在人类遗传里面&#xff0c;近亲结婚生出的后代会伴随着各种疾病的并发&#xff0c;主要原因是近亲结婚提高了疾病的发病了&#xff0c;故而法律明文规定不可近亲结婚。但是对于鸽子来说&#xff0c;种鸽的近亲作育却能生出好的鸽子&#xff0c;虽然说种鸽的近亲作育也会生出一…...

长沙网站建设价格/网站设计与制作教程

2019独角兽企业重金招聘Python工程师标准>>> http://smallcultfollowing.com/babysteps/blog/categories/rust/ 转载于:https://my.oschina.net/innovation/blog/150099...

可以做动画的网站都有哪些/百度账号管理

精彩网址大全——生活资讯&文体娱乐卷 中国铁道出版社 随着网络的普及&#xff0c;网络上的信息以空前的速度膨胀&#xff0c;网络也从信息集散地变成的信息垃圾场。从前在网络上寻找信息时&#xff0c;可能找不到所需要的信息&#xff0c;要想找到所需要的有用信息就变得…...

南城网站优化公司/石家庄seo排名外包

描述 查找和排序题目&#xff1a;输入任意&#xff08;用户&#xff0c;成绩&#xff09;序列&#xff0c;可以获得成绩从高到低或从低到高的排列,相同成绩 都按先录入排列在前的规则处理。例示&#xff1a; jack 70 peter 96 Tom 70 smith 67从高到低 成绩…...