c++ std::shared_ptr学习

背景

        c++中智能指针shared_ptr用于自动管理资源,通过引用计数来记录资源被多少出地方使用。在不使用资源时,减少引用计数,如果引用计数为0,表示资源不会再被使用,此时会释放资源。本文记录对c++中std::shared_ptr的源码学习。

为什么需要“智能”指针

       当我们需要在堆上分配对象时,可以通过new来创建,在不需要使用对象时,需要使用delete来释放对象占用的资源,如下所示:

void foo() {...int *pi = new int(0);...delete pi;...
}

        如果在delete之前,函数提前return,则new出来的内存不会被释放,导致内存泄露。另外,如果一个对象在函数中被new出来,并作为返回值返回,但是使用者如果不清楚函数内部实现,也可能忘记delete调new出来的对象,导致内存泄露,如下所示:

int* foo() {return new int(0);
}void bar() {int *pi = foo();...return; // 因为不知道foo返回的int*是new出来的,所以没有delete pi
}

        对上面这种场景,还可能会出现一个指针被多处使用。为了确保指针在使用过程中没有被delete,还需要关注指针使用的先后顺序。但在迭代的过程中,这会导致指针的使用变得很难维护。为此,我们需要有一个“智能”的指针,维护我们new出来的对象,并在不需要的时候自动delete,释放资源。

智能指针如何“智能”

        c++智能指针利用class的构造函数和析构函数来对指针管理,构造函数中将new出来的指针传入,在析构函数中判断如果没人再使用,则会释放指针。

        如何判断指针还被使用呢?答案是使用引用计数。只要指针被引用了,引用计数加1,当某一个引用不再使用(析构之后),则引用计数减1,如果指针不再被使用时,引用计数为0,此时即可delete指针,释放资源。

        那如何知道一个指针被引用了呢?答案就是复制构造和赋值构造。如果不知道什么是复制构造和赋值构造,可以先去学习下,以下是个简单的例子:

class A {
public:// 构造函数A() {x = 0;}// 析构函数~A() {}// 复制构造A(const A &a) {x = a.x;}// 赋值构造A& operate=(cosnt A &a) {x = a.x;return *this;}
private:int x;
}int main() {A a1; // 构造函数A a2(a1); // 复制构造A a3 = a1; // 赋值构造
}
// 离开作用域,a1, a2, a3析构

shared_ptr概览

        shared_ptr定义如下:

  /***  @brief  A smart pointer with reference-counted copy semantics.**  The object pointed to is deleted when the last shared_ptr pointing to*  it is destroyed or reset.*/template<typename _Tp>class shared_ptr : public __shared_ptr<_Tp>{...}

即shared_ptr继承__shared_ptr,__shared_ptr定义如下:

  template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...private:element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.}

        从注释中可以看出,__shared_ptr中有两个成员变量:_M_ptr和_M_refcount。_M_ptr是智能指针管理的资源,_M_refcount是引用计数。再看下_M_refcount的类型定义,即__shared_count:

  template<_Lock_policy _Lp>class __shared_count{...private:_Sp_counted_base<_Lp>*  _M_pi;};template<_Lock_policy _Lp = __default_lock_policy>class _Sp_counted_base: public _Mutex_base<_Lp>{...private:_Atomic_word  _M_use_count;     // #shared...};typedef int _Atomic_word;

        可以看出,引用计数本质是一个int值:_M_use_count。

        shared_ptr简单的类图示意如下:

        根据智能指针实现方式,我们主要考虑构造函数、析构函数、复制构造函数、赋值构造函数。

构造函数

        用以下代码作为示例说明智能指针构造过程:

class A {
public:A() {cout << "construct A" << endl;}~A() {cout << "deconstruct A" << endl;}
};void test_init() {shared_ptr<A> pa(new A);
}

        当test函数被调用是,创建了一个A指针,该指针作为shared_ptr入参传入构造函数,shared_ptr构造函数调用过程如下:

// shared_ptr构造函数/***  @brief  Construct a %shared_ptr that owns the pointer @a __p.*  @param  __p  A pointer that is convertible to element_type*.*  @post   use_count() == 1 && get() == __p*  @throw  std::bad_alloc, in which case @c delete @a __p is called.*/template<typename _Yp, typename = _Constructible<_Yp*>>explicitshared_ptr(_Yp* __p) : __shared_ptr<_Tp>(__p) { }// __shared_ptr构造函数,本文考虑指针不是数组指针,即is_array<_Tp>::type() == false_typetemplate<typename _Yp, typename = _SafeConv<_Yp>>explicit__shared_ptr(_Yp* __p): _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type()){...}// 引用计数__shared_ptr::_M_refcount(__shared_count类型)构造函数template<typename _Ptr>__shared_count(_Ptr __p, /* is_array = */ false_type): __shared_count(__p){ }template<typename _Ptr>explicit__shared_count(_Ptr __p) : _M_pi(0){__try{_M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);}__catch(...){delete __p;__throw_exception_again;}}// __shared_count::_M_pi构造函数// Counted ptr with no deleter or allocator supporttemplate<typename _Ptr, _Lock_policy _Lp>class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>{public:explicit_Sp_counted_ptr(_Ptr __p) noexcept: _M_ptr(__p) { }...private:_Ptr             _M_ptr;};_Sp_counted_base() noexcept: _M_use_count(1), _M_weak_count(1) { }

       可以看到,new创建的指针被存在__shared_ptr::_M_ptr中,同时,__shared_ptr::_M_refcount构造函数中,new创建了_M_refcount::_M_pi,这样,引用计数才不会因为__shared_ptr析构而消失,引用计数的生命周期应该与指针的生命周期一致,才能记录指针被引用的次数。__Sp_counted_base构造函数中,初始化引用计数_M_use_count为1,表示当前只有1处在使用该指针。

        构造函数调用结束之后,shared_ptr数据可简单表示为:

        (为什么内存布局是这样?可查看附录中的测试代码。)

复制构造

        以下代码被调用时,智能指针的复制构造函数被调用:

int test_copy() {shared_ptr<A> pa(new A);shared_ptr<A> pa_copy(pa);  // 复制构造
}

        复制构造主要实现代码如下:

  template<typename _Tp>class shared_ptr : public __shared_ptr<_Tp>{...public:...shared_ptr(const shared_ptr&) noexcept = default;  // 默认复制构造...}template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...__shared_ptr(const __shared_ptr&) noexcept = default;  // 默认复制构造...private:...element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.}

        复制构造使用编译器生成的默认复制构造函数,伪代码可以表示成:

template<typename _Tp>class shared_ptr : public __shared_ptr<_Tp>{...public:...shared_ptr(const shared_ptr& ptr) noexcept {_M_ptr = __ptr._M_ptr;_M_refcount.__shared_count::__shared_count(__ptr._M_refcount);}...}

        继续看__shared_count的复制构造函数:

  template<_Lock_policy _Lp>class __shared_count{...public:__shared_count(const __shared_count& __r) noexcept: _M_pi(__r._M_pi){if (_M_pi != 0)_M_pi->_M_add_ref_copy();}...private:..._Sp_counted_base<_Lp>*  _M_pi;}template<_Lock_policy _Lp = __default_lock_policy>class _Sp_counted_base: public _Mutex_base<_Lp>{public:...void_M_add_ref_copy(){ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }...private:_Sp_counted_base(_Sp_counted_base const&) = delete;_Sp_counted_base& operator=(_Sp_counted_base const&) = delete;_Atomic_word  _M_use_count;     // #shared_Atomic_word  _M_weak_count;    // #weak + (#shared != 0)};namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION// Functions for portable atomic access.// To abstract locking primitives across all thread policies, use:// __exchange_and_add_dispatch// __atomic_add_dispatch
#ifdef _GLIBCXX_ATOMIC_BUILTINSstatic inline _Atomic_word __exchange_and_add(volatile _Atomic_word* __mem, int __val){ return __atomic_fetch_add(__mem, __val, __ATOMIC_ACQ_REL); }...static inline void__attribute__ ((__unused__))__atomic_add_dispatch(_Atomic_word* __mem, int __val){
#ifdef __GTHREADSif (__gthread_active_p())__atomic_add(__mem, __val);else__atomic_add_single(__mem, __val);
#else__atomic_add_single(__mem, __val);
#endif}_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

        可以看到,复制构造函数最终就是将引用计数加1,且是通过原子操作来进行加1的,由此可见:智能指针是线程安全的!至于如何实现的线程安全,本文暂不深究。

赋值构造

        类似地,以下函数调用时,智能指针的赋值构造函数被调用:

int test_assgin() {shared_ptr<A> pa(new A);shared_ptr<A> pa_assign(new A);pa_assign = pa;  // 赋值构造
}

        比复制构造稍微复杂一点,赋值构造的实现如下:

  template<typename _Tp>class shared_ptr : public __shared_ptr<_Tp>{...public:...shared_ptr& operator=(const shared_ptr&) noexcept = default;...}template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...__shared_ptr& operator=(const __shared_ptr&) noexcept = default;...private:...element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.}template<_Lock_policy _Lp>class __shared_count{...public:...__shared_count&operator=(const __shared_count& __r) noexcept{_Sp_counted_base<_Lp>* __tmp = __r._M_pi;if (__tmp != _M_pi){if (__tmp != 0)__tmp->_M_add_ref_copy();if (_M_pi != 0)_M_pi->_M_release();_M_pi = __tmp;}return *this;}...private:..._Sp_counted_base<_Lp>*  _M_pi;}template<typename _Ptr, _Lock_policy _Lp>class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>{public:...virtual void_M_dispose() noexcept{ delete _M_ptr; }virtual void_M_destroy() noexcept{ delete this; }...}template<_Lock_policy _Lp = __default_lock_policy>class _Sp_counted_base: public _Mutex_base<_Lp>{public:...void_M_add_ref_copy(){ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }...void_M_release() noexcept{// Be race-detector-friendly.  For more info see bits/c++config._GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1){_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);_M_dispose();// There must be a memory barrier between dispose() and destroy()// to ensure that the effects of dispose() are observed in the// thread that runs destroy().// See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.htmlif (_Mutex_base<_Lp>::_S_need_barriers){__atomic_thread_fence (__ATOMIC_ACQ_REL);}// Be race-detector-friendly.  For more info see bits/c++config._GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count,-1) == 1){_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);_M_destroy();}}}...private:_Sp_counted_base(_Sp_counted_base const&) = delete;_Sp_counted_base& operator=(_Sp_counted_base const&) = delete;_Atomic_word  _M_use_count;     // #shared_Atomic_word  _M_weak_count;    // #weak + (#shared != 0)};namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION...static inline _Atomic_word__attribute__ ((__unused__))__exchange_and_add_dispatch(_Atomic_word* __mem, int __val){
#ifdef __GTHREADSif (__gthread_active_p())return __exchange_and_add(__mem, __val);elsereturn __exchange_and_add_single(__mem, __val);
#elsereturn __exchange_and_add_single(__mem, __val);
#endif}..._GLIBCXX_END_NAMESPACE_VERSION
} // namespace

        赋值构造会将被复制的对象引用计数加1,同时,将自身的引用计数减1,如果减1之前,自身的引用计数已经为1,则将释放持有的指针:_M_dispose(),并且weak_count=1时,也会将自身delete掉:_M_destroy()。整个操作也是线程安全的。

析构函数

        当类离开其作用域时,析构函数会被调用:

int test_destroy() {{shared_ptr<A> pa(new A);}// 此时, pa的析构函数已经被调用
}

        析构函数的实现如下:

  // shared_ptr没有显式定义析构函数template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...~__shared_ptr() = default;...private:...element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.}template<_Lock_policy _Lp>class __shared_count{...public:...~__shared_count() noexcept{if (_M_pi != nullptr)_M_pi->_M_release();}...private:..._Sp_counted_base<_Lp>*  _M_pi;}

        析构函数调用引用计数的_M_release(),即引用计数减1,且如果资源不再使用,则释放资源。

像使用指针一样

        在使用智能指针时,为了能像使用指针一样,智能指针对操作符*、->进行了重载,我们就能像下面这样使用智能指针了:

struct B {int b;
};void test() {B *pb = new B;shared_ptr<B> shared_pb(pb);shared_pb->b = 1; // 类似于pb->b = 1B b = *shared_pb; // 类似于B b = *pb;
}

        重载操作符的实现如下:

  template<typename _Tp, _Lock_policy _Lp>class __shared_ptr_access<_Tp, _Lp, true, false>{public:using element_type = typename remove_extent<_Tp>::type;#if __cplusplus <= 201402L[[__deprecated__("shared_ptr<T[]>::operator* is absent from C++17")]]element_type&operator*() const noexcept{__glibcxx_assert(_M_get() != nullptr);return *_M_get();}[[__deprecated__("shared_ptr<T[]>::operator-> is absent from C++17")]]element_type*operator->() const noexcept{_GLIBCXX_DEBUG_PEDASSERT(_M_get() != nullptr);return _M_get();}
#endif...private:element_type*_M_get() const noexcept{ return static_cast<const __shared_ptr<_Tp, _Lp>*>(this)->get(); }};template<typename _Tp, _Lock_policy _Lp>class __shared_ptr: public __shared_ptr_access<_Tp, _Lp>{public:using element_type = typename remove_extent<_Tp>::type;...element_type*get() const noexcept{ return _M_ptr; }...private:element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.};

总结

        总结一下,智能指针,就是申请资源(本文中的例子是指针)之后,将资源交给智能指针,智能指针复制、赋值时原子增加、减少引用计数,并在引用计数为0,即没有人再使用资源时,释放资源,并将引用计数释放掉(引用计数也是new申请出来的),以此来实现“智能”管理资源。

附录

shared_ptr内存布局测试

        测试代码如下:

# include <memory>
# include <iostream>
# include <stdint.h>using namespace std;class A {
public:A() {cout << "construct A" << endl;}~A() {cout << "deconstruct A" << endl;}
};/*__shared_ptr:element_type*	   _M_ptr;         // Contained pointer.__shared_count<_Lp>  _M_refcount;    // Reference counter.__shared_count:_Sp_counted_base<_Lp>*  _M_pi;_Sp_counted_ptr:void *vptr;_Atomic_word  _M_use_count;     // #shared_Atomic_word  _M_weak_count;    // #weak + (#shared != 0)_Ptr             _M_ptr;*/int main() {{A *pa = new A();shared_ptr<A> shared_pa(pa);char *addr = (char*)&shared_pa;void *shared_pa_addr = *((void**)addr);void *_M_pi = *((void**)(addr+8));printf("pa: 0x%X, shared_pa_addr: 0x%X, _M_pi: 0x%X\n", pa, shared_pa_addr, _M_pi);addr = (char*)_M_pi;void *vptr = *((void**)addr);int _M_use_count = *((int*)(addr+8));int _M_weak_count = *((int*)(addr+12));void *_M_ptr = *((void**)(addr+16));printf("vptr: 0x%X, _M_use_count: %d, _M_weak_count: %d, _M_ptr: 0x%X\n", vptr, _M_use_count, _M_weak_count, _M_ptr);}cout << "move scope" << endl;
}

面试要求写一个智能指针

        面试中可能会被要求实现一个智能指针,先不考虑线程安全、各种场景的兼容,可以对上面代码进行精简,仅对核心逻辑做实现:

#ifndef M_SMART_POINTER_H
#define M_SMART_POINTER_Htemplate<typename T>
class MSmartPointer {
public:MSmartPointer(T *ptr_) {ptr = ptr_;ref_count_ptr = new int(1);}MSmartPointer(const MSmartPointer &r) {Copy(r);}MSmartPointer& operator=(const MSmartPointer &r) {if (r.ptr != ptr) {Release();Copy(r);}return *this;}~MSmartPointer() {Release();}T& operator*() {return *ptr;}T* operator->() {return ptr;}int ref_count() {return *ref_count_ptr;}
private:void Copy(const MSmartPointer &r) {ptr = r.ptr; // 持有指针ref_count_ptr = r.ref_count_ptr; // 持有指针对应的引用计数if (ref_count_ptr)(*ref_count_ptr)++; // 引用计数加1}void Release() {if (!ref_count_ptr) return;if (--(*ref_count_ptr) <= 0) { // 引用计数减1,如果引用计数小于等于0,代表没人用了,需要释放资源delete ref_count_ptr; // 释放引用计数ref_count_ptr = nullptr;delete ptr; // 释放持有的指针ptr = nullptr;}}private:T *ptr;int *ref_count_ptr;
};#endif

        附上测试代码:

void test_m_smart_pointer() {cout << "test_m_smart_pointer" << endl;{MSmartPointer<Test> p(new Test);cout << "1. ref_count: " << p.ref_count() << endl;p->t = 1;cout << "1. t: " << p->t << endl;{MSmartPointer<Test> p1(p);cout << "2. ref_count: " << p.ref_count() << endl; (*(p1)).t = 2;cout << "2. t: " << p->t << endl;MSmartPointer<Test> p2(new Test);p2 = p;cout << "3. ref_count: " << p.ref_count() << endl; }cout << "4. ref_count: " << p.ref_count() << endl; }cout << "move scope" << endl;
}

线程安全思考

        在对智能指针的引用计数进行修改时,都使用额原子操作,因此引用计数的加减都是现成安全的。但是,在释放资源时,其整个释放的操作为非原子的。如果在释放资源的过程,发生了复制、赋值,则就可能出现资源被释放,但是还有地方在引用的问题。不考虑多线程场景,以下面的代码举例:

void test_atomic_thread_safe() {A *pa = new A;shared_ptr<A> shared_pa(pa);shared_pa.__shared_ptr<A>::~__shared_ptr<A>();shared_ptr<A> shared_pb(pa);cout << "test call deconstruct: " << shared_pb.use_count() << endl;
}

         这段代码运行结果如下:

construct A
deconstruct A
test call deconstruct: 1
deconstruct A
deconstruct A
free(): double free detected in tcache 2
[1]    6372 abort (core dumped)  ./output/main

        可以看到,在shared_pa被析构之后,再去对其复制,将导致持有的指针不再有效。但是,正常情况下,谁会主动掉析构函数呢?如果我们不主动调析构函数,则析构函数被调用时,我们也取不到对象了,更别提对其引用。所以总体看来,智能指针还是线程安全的。

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

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

相关文章

攻防世界PHP2

1、打开靶机链接http://61.147.171.105:49513/&#xff0c;没有发现任何线索 2、尝试访问http://61.147.171.105:49513/index.php&#xff0c;页面没有发生跳转 3、尝试将访问 尝试访问http://61.147.171.105:49513/index.phps index.php 和 index.phps 文件之间的主要区别在于…

GNU Radio创建时间戳 C++ OOT块

文章目录 前言一、创建自定义的 C OOT 块1、创建 timestamp_sender C OOT 模块①、创建 timestamp_sender OOT 块②、修改 C 代码 2、创建 timestamp_receiver C OOT 模块①、创建 timestamp_receiver OOT 块②、修改 C 代码 3、创建 delayMicroSec C OOT 模块①、创建 delayMi…

Vue3实战笔记(20)—封装头部导航组件

文章目录 前言一、封装头部导航栏二、使用步骤总结 前言 Vue 3 封装头部导航栏有助于提高代码复用性、统一风格、降低维护成本、提高可配置性和模块化程度&#xff0c;同时还可以实现动态渲染等功能&#xff0c;有利于项目开发和维护。 一、封装头部导航栏 封装头部导航栏&am…

HFSS学习-day4-建模操作

通过昨天的学习&#xff0c;我们已经熟悉了HFSS的工作环境&#xff1b;今天我们来讲解HFSS中创建物体模型的县体步骤和相关操作。物体建模是HFSS仿真设计工作的第一步&#xff0c;HFSS中提供了诸如矩形、圆面、长方体圆柱体和球体等多种基本模型(Primitive)&#xff0c;这些基本…

新书速览|MATLAB科技绘图与数据分析

提升你的数据洞察力&#xff0c;用于精确绘图和分析的高级MATLAB技术。 本书内容 《MATLAB科技绘图与数据分析》结合作者多年的数据分析与科研绘图经验&#xff0c;详细讲解MATLAB在科技图表制作与数据分析中的使用方法与技巧。全书分为3部分&#xff0c;共12章&#xff0c;第1…

精英都是时间控!职场精英的完美一天~~~谷歌FB都在用的时间管理术!

如何超高效使用24小时 每个人的一天都只有24小时&#xff0c;使用时间的方法将决定整个人生。时间管理术并不提倡把自己忙死榨干&#xff0c;而是通过在合适的时间做合适的事情&#xff0c;把大脑机能发挥到极致&#xff0c;从而提高效率&#xff0c;节省下更多时间用于生活与…

(项目)-KDE巡检报告(模板

金山云于12月26日对建行共计【30】个KDE集群,合计【198】台服务器进行了巡检服务。共发现系统风险【135】条,服务风险【1912】条,服务配置风险【368】条。 一、系统风险 1、风险分析(图片+描述) (1)磁盘使用率高 问题描述多个集群的多台服务器磁盘使用率较高,远超过…

答辩PPT模版如何选择?aippt快速生成

这些网站我愿称之为制作答辩PPT的神&#xff01; 很多快要毕业的同学在做答辩PPT的时候总是感觉毫无思路&#xff0c;一窍不通。但这并不是你们的错&#xff0c;对于平时没接触过相关方面&#xff0c;第一次搞答辩PPT的人来说&#xff0c;这是很正常的一件事。一个好的答辩PPT…

简述RocketMQ系统架构及其相关概念

一、概述 RocketMQ是一款高性能、高吞吐量的分布式消息队列系统&#xff0c;它采用了分布式架构&#xff0c;支持多生产者和消费者并发读写&#xff0c;具有高可用性、高吞吐量、低延迟等特点。本文将对RocketMQ的系统架构进行详细解析。 二、架构设计 RocketMQ采用了分布式架…

入门物联网就是这么简单——青创智通

工业物联网解决方案-工业IOT-青创智通 MQTT&#xff0c;全称为Message Queuing Telemetry Transport&#xff0c;是一种轻量级的发布/订阅消息传输协议&#xff0c;广泛应用于物联网领域。 MQTT协议以其高效、可靠、灵活的特性&#xff0c;成为物联网设备间通信的理想选择。本…

升级版ComfyUI InstantID 换脸:FaceDetailer + InstantID + IP-Adapter

在使用ComfyUI的InstantID进行人脸替换时&#xff0c;一个常见问题是该工具倾向于保留原始参考图的构图&#xff0c;即使用户的提示词与之不符。 例如&#xff0c;即使用户提供的是大头照并请求生成全身照&#xff0c;结果仍是大头照&#xff0c;没有显示出用户所期望的构图。…

MySQL_DDL语句

1.Data类临时数据的弊端 我们之前在将ServletJSP配合处理请求的过程中 数据库起到一个存取数据的作用 但是我们之前的案例中 数据是在Data类中临时定义的 并不是从数据库中获取的 这样做是不好的 因为每一次服务器关闭之后 那么部署在其上的类也会随着卸载 紧接着和类相挂钩的静…

基于C#开发web网页管理系统模板流程-登录界面

前言&#xff0c;首先介绍一下本项目将要实现的功能 &#xff08;一&#xff09;登录界面 实现一个不算特别美观的登录窗口&#xff0c;当然这一步跟开发者本身的设计美学相关&#xff0c;像蒟蒻博主就没啥艺术细胞&#xff0c;勉强能用能看就行…… &#xff08;二&#xff09…

使用Tkinter开发Python棋盘游戏

使用 Tkinter 开发一个简单的棋盘游戏是很有趣的&#xff01;下面是一个示例&#xff0c;演示如何使用 Tkinter 创建一个简单的五子棋游戏&#xff1a;这个是我通过几个夜晚整理出来的解决方案和实际操作教程。 1、问题背景 目标是开发一个 Python 棋盘游戏&#xff0c;玩家可…

人力资源管理:员工体验平台设计

员工体验是员工的感受&#xff0c;是员工作为企业一份子观察到、感受到和与之互动的一切&#xff0c;包含企业为员工提供的物质条件、人文环境等各方面的内容。 是在工作过程中接触到的所有接触点所产生的对自己与用人单位关系的整体感知&#xff0c;员工体验从员工入职开始贯…

使用Go和JavaScript爬取股吧动态信息的完整指南

引言 在现代金融生态系统中&#xff0c;信息流动的速度和效率对于市场的健康和投资者的成功至关重要。股市信息&#xff0c;特别是来自活跃交流平台如股吧的实时数据&#xff0c;为投资者提供了一个独特的视角&#xff0c;帮助他们洞察市场趋势和投资者情绪。这些信息不仅能够…

NumPy常用操作

目录 一&#xff1a;简介 二&#xff1a;NumPy 常用操作 三&#xff1a;总结 一&#xff1a;简介 是一个开源的Python库&#xff0c;它为Python提供了强大的多维数组对象和用于处理这些数组的函数。NumPy的核心是ndarray&#xff0c;它是一个高效的多维数组容器&#xff0c;用…

力扣【旋转函数】python

如果直接用暴力的话&#xff0c;只能过4个样例好像&#xff0c;超时 因此得用递推公式 F1F0前n-1个数-(n-1)*第n个数 F0sum(nums)-n*第n个数 nlen(nums) ans[]#定义一个存最大值值的列表 ss sum(nums) dm 0 for j in range(n):dm j * nums[j] ans.append(dm) print(dm) n…

面试中的算法(查找缺失的整数)

在一个无序数组里有99个不重复的正整数&#xff0c;范围是1~100&#xff0c;唯独缺少1个1~100中的整数。如何找出这个缺失的整数? 一个很简单也很高效的方法&#xff0c;先算出1~100之和&#xff0c;然后依次减去数组里的元素&#xff0c;最后得到的差值&#xff0c;就是那个缺…

集合系列(二十五) -二叉树、平衡二叉树、红黑树性能总结

一、摘要 二叉树&#xff0c;作为一种数据结构&#xff0c;在实际开发中&#xff0c;有着非常广泛的应用&#xff0c;尤其是以平衡二叉树、红黑树为代表&#xff0c;在前几篇文章中&#xff0c;我们详细的介绍了BST、AVL、RBT的算法以及代码实践&#xff0c;下面简要概括描述一…