()智能指针一) shared_ptr

news2025/2/25 20:36:53

智能指针(一)

文章目录

  • 智能指针(一)
    • shared_ptr
      • 1初始化方式
      • make_ptr<>() 和 shared_ptr<>() 的区别
        • shared_ptr维护引用计数需要的信息
        • 使用原始的new函数创建shared_ptr
        • 使用make_shared创建shared_ptr
      • make_shared实现异常安全
      • 使用make_shared的缺点
      • make_shared 源码解析
    • 2 引用计数的变化
      • 引用计数的增加
        • *将一个shared_ptr 作为构造函数参数 初始化 另一个 shared_ptr 指针*
        • *将一个 shared ptr 作为函数参数 传递进去*
        • 接收函数返回的 指向自身的 shared_ptr
      • 引用计数不变
        • *将一个 shared ptr 作为函数参数(引用传参的方式) 传递进去 引用计数不会增加*
      • 引用计数减少
        • *当 shared ptr 指向新的对象的时候,引用计数减一*
        • *当 局部的shareptr 离开其生命域的时候,引用计数减为0;*
      • 引用计数为0
        • *当一个shared_ptri引用计数从1变成0,则它会自动释放自己所管理(指向)的对象;*
    • 3 shared_ptr()的基本操作
        • use_count()
        • unique()
        • reset()
          • 不带参数
          • 带参数
          • 空指针使用reset()
        • 使用解引用
        • get()
          • 注意:
        • swap()
        • nullptr()
        • 智能指针名字作为判断条件
    • 4 指定删除器
          • 例子一
          • *删除器可以用lambda 表达式*
          • 自定义删除器
          • *default_delete*
          • shared<T [ ] >
          • 仿函数自定义删除器
          • 额外的例子
        • 删除器补充
        • 删除器补充

shared_ptr

1初始化方式

shared_ptr<int> makes(int values)
    {
        //    return new int(values); // 无法将new 得到的int *转化为shared_ptr

        return shared_ptr<int>(new int(values));
        // 或者显式地使用int* 创建shared_ptr<int>
        // return std::make_shared<int>(values);
    };
    void testFunction()
    {

        // 1采用构造函数初始化
        shared_ptr<int> pi(new int(100));
        // shared_ptr<int> pi2 = new int(100);// 智能指针是explicit的,阻止隐式类型转换
        // 2使用make_shared()函数 推荐
        // make_shared函数:标准库里的函数模板。安全,高效的分配和使用shared_ptr;
        // 它能够在动态内存(堆)中分配并初始化一个对象,然后返回指向此对象的shared ptr;
        shared_ptr<int> pi3 = make_shared<int>(100);
        // 也可以使用类型推导简写代码
        auto pi4 = make_shared<int>(100);
        // 3使用函数返回值初始化
        auto pi5 = makes(100);
        // 4可以使用裸指针初始化,不推荐
        int *pi6 = new int;
        shared_ptr<int> pi7(pi6);
    };
    

make_ptr<>() 和 shared_ptr<>() 的区别

shared_ptr维护引用计数需要的信息

  • 强引用, 用来记录当前有多少个存活的 shared_ptrs 正持有该对象. 共享的对象会在最后一个强引用离开的时候销毁( 也可能释放).
  • 弱引用, 用来记录当前有多少个正在观察该对象的 weak_ptrs. 当最后一个弱引用离开的时候, 共享的内部信息控制块会被销毁和释放 (共享的对象也会被释放, 如果还没有释放的话).

使用原始的new函数创建shared_ptr

  • 首先是原始的new分配了原始对象, 然后将这个对象传递给 shared_ptr (即使用 shared_ptr 的构造函数) , shared_ptr 对象只能单独的分配控制块。
  • 控制块包含被指向对象的引用计数以及其他,也就是说,控制块的内存是在std::shared_ptr的构造函数中分配的。

img


auto p = new widget();
shared_ptr sp1{ p }, sp2{ sp1 };

使用make_shared创建shared_ptr

  • 如果选择使用 make_shared 的话, 内存分配的动作, 可以一次性完成,因为std::make_shared申请一个单独的内存块来同时存放指向的对象和控制块,这减少了内存分配的次数, 而内存分配是代价很高的操作。
  • 同时,使用std::make_shared消除了一些控制块需要记录的信息,减少了程序的总内存占用。

img

auto sp1 = make_shared(), sp2{ sp1 };

make_shared实现异常安全

  • 在shared_ptr的使用过程中,不能在函数实参中创建shared_ptr,如下:
//Define
void F(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs) 
{}
//Call
F(std::shared_ptr<Lhs>(new Lhs("foo")),std::shared_ptr<Rhs>(new Rhs("bar")));

C++ 是不保证参数求值顺序, 以及内部表达式的求值顺序的, 所以可能的执行顺序如下:

new Lhs(“foo”))
new Rhs(“bar”))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>

如果在第2步的时候,发生了异常,第一步申请的 Lhs 对象内存就泄露了,
产生这个问题的核心在于, shared_ptr 没有立即获得裸指针,所以就有可能产生内存泄漏。当然,这个问题是可以这样解决:

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

但,最推荐的做法是

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

因为,申请原始对象和将原始对象裸指针赋值给shared_ptr是在同一个执行序列里,失败的话一起失败,成功就一起成功,这样就能保住创建的原始对象裸指针能安全的存放到std::shared_ptr中

使用make_shared的缺点

  • 创建的对象如果没有公有的构造函数时,make_shared无法使用。

make_shared在传递参数格式是可变的,参数传递为生成类型的构造函数参数,因此在创建shared_ptr对象的过程中调用了类型T的某一个构造函数。

当然我们可以使用一些小技巧来解决这个问题, 比如这里 How do I call ::std::make_shared on a class with only protected or private constructors?

  • 使用make_shared内存可能无法及时回收,对内存要求要的场景需要注意。

make_shared 只分配一次内存, 这看起来很好. 减少了内存分配的开销. 问题来了, weak_ptr 会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个 weak_ptr 离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 若引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题. 关于这个问题可以看这里 make_shared, almost a silver bullet

make_shared 源码解析

如下为make_shared的库函数实现版本:

template<typename _Tp, typename... _Args>

inline shared_ptr<_Tp>

make_shared(_Args&&... __args)

{

typedef typename std::remove_const<_Tp>::type _Tp_nc;

return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),

std::forward<_Args>(__args)...);

}

 
template<typename _Tp, typename _Alloc, typename... _Args>

inline shared_ptr<_Tp>

allocate_shared(const _Alloc& __a, _Args&&... __args)

{

return shared_ptr<_Tp>(_Sp_make_shared_tag(), __a,

std::forward<_Args>(__args)...);

}

我们依次分析上述的关键代码

//关键行1

template<typename _Tp, typename... _Args>

inline shared_ptr<_Tp> make_shared(_Args&&... __args)

//关键行2

std::forward<_Args>(__args)...

//关键行3

return shared_ptr<_Tp>(_Sp_make_shared_tag(), __a,

std::forward<_Args>(__args)...);

  • 从上述关键代码可以看出:make_shared是组合使用可变参数模板与forward(转发)机制实现将实参保持不变地传递给其他函数。如最开始的string例子**。**
    1)使用可变参数:是因为string有多个构造函数,且参数各不相同;
  • 2)Args参数为右值引用(Args&&)和std::forward:是为了保持实参中类型信息的传递。这样当传递一个右值string&& 对象给make_shared时,就可以使用string的移动构造函数进行初始化。注意,两者必须结合使 用,缺一不可;
  • 此外std::forward<_Args>(__args)…是采用包扩展形式调用的,原理如下:

shared_ptr<string> p1 = make_shared<string>(10, '9');

//扩展如下,对两个参数分别调用

std::forward

return shared_ptr<string>(_Sp_make_shared_tag(), _a ,

std::forward<int>(10),

std::forward<char>(c));

补充说明:
①模板参数为右值引用,采用引用折叠原理:

  1. 1)参数为左值时,实参类型为普通的左值引用; T& &, T&& &,T& && =>T&
  2. 2)参数为右值时,实参类型为右值: T&& && => T&&

std::forward:是一个模板,通过显示模板实参来调用,调用后forward返回显示实参类型的右值引用。即,forward的返回类型为T&&,在根据上述引用折叠原理即可保存参数是左值还是右值类型;比如:

  1. int i = 0;
  2. std::forward<int>(i), i将以int&传递
  3. std::forward<int>(42), 42将以int&&传递

std::forward实现代码:

  1. /**
  2. * @brief Forward an lvalue.
  3. * @return The parameter cast to the specified type.
  4. *
  5. * This function is used to implement "perfect forwarding".
  6. */
  7. template<typename _Tp>
  8. constexpr _Tp&&
  9. forward(typename std::remove_reference<_Tp>::type& __t) noexcept
  10. { return static_cast<_Tp&&>(__t); }
  11. /**
  12. * @brief Forward an rvalue.
  13. * @return The parameter cast to the specified type.
  14. *
  15. * This function is used to implement "perfect forwarding".
  16. */
  17. template<typename _Tp>
  18. constexpr _Tp&&
  19. forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
  20. {
  21. static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
  22. " substituting _Tp is an lvalue reference type");
  23. return static_cast<_Tp&&>(__t);
  24. }

2 引用计数的变化

引用计数的增加

将一个shared_ptr 作为构造函数参数 初始化 另一个 shared_ptr 指针

 // 1 将一个shared_ptr 作为构造函数参数 初始化 另一个 shared_ptr 指针
    void func1()
    {
        auto pi = make_shared<int>(100);
        cout << "pi.useCount()=" << pi.use_count() << endl;
        auto pi2(pi);
        // pi和pi2指向了相同的对象, pi的引用计数+1; pi和 pi2 共享对象,所以引用计数都是2
        cout << "pi.useCount()=" << pi.use_count() << "      "
             << "pi2.useCout()=" << pi2.use_count() << endl;

        // 每个shared ptr都会记录有多少个其他的shared pt指向相同的对象;
    }

将一个 shared ptr 作为函数参数 传递进去

 void F(shared_ptr<int> ptr) // ptr 和传入的pi 指向 同一个对象,引用计数加一
    {
        cout << "ptr.use_count()=" << ptr.use_count() << endl;
        // ptr 在结束生命区域后,会被回收,所以ptr的引用计数会减一
        return;
    }
    void func2()
    {
        auto pi = make_shared<int>(100);
        cout << "pi.useCount()=" << pi.use_count() << endl;

        F(pi);

        cout << "pi.useCount()=" << pi.use_count() << endl;
    }

接收函数返回的 指向自身的 shared_ptr

 shared_ptr<int> FF(shared_ptr<int> &ptr) // 传引用作为参数,引用计数不会增加
    {
        cout << "ptr.use_count()=" << ptr.use_count() << endl;

        return ptr; // 左常引用和右值引用都可以把临时对象的生命周期延长到与引用自身的生命周期相同
                    // ptr的生命周期将被延长,将不会在FF 函数结束之后被回收
                    // 这里的ptr 对象的 就相当于是 pi 对象(因为传引用)
    }
    void func3()
    {

        auto pi = make_shared<int>(100);
        cout << "pi.useCount()=" << pi.use_count() << endl;

        auto ret = FF(pi); // 引用计数变成2
                           //  FF(pi);// 如果不接收返回值,也就是ret 没有指向 ptr对象(也就是pi 对象)则引用数 会变成 1

        cout << "pi.useCount()=" << pi.use_count() << endl;
    }

引用计数不变

将一个 shared ptr 作为函数参数(引用传参的方式) 传递进去 引用计数不会增加

 // 将一个 shared ptr 作为函数参数 传递进去

    void F(shared_ptr<int> &ptr) // ptr 传引用参数,引用计数将不会增加
    {
        cout << "ptr.use_count()=" << ptr.use_count() << endl;
        //
        return;
    }
    void func2()
    {
        auto pi = make_shared<int>(100);
        cout << "pi.useCount()=" << pi.use_count() << endl;

        F(pi);

        cout << "pi.useCount()=" << pi.use_count() << endl;
    }

引用计数减少

当 shared ptr 指向新的对象的时候,引用计数减一

 shared_ptr<int> pi(new int);
    shared_ptr<int> pi2(pi);
    shared_ptr<int> pi3(pi);
    void func()
    {

        cout << "pi.useCount()=" << pi.use_count() << endl;
        cout << "pi2.useCount()=" << pi2.use_count() << endl;
        cout << "pi3.useCount()=" << pi3.use_count() << endl;

        pi = make_shared<int>(100); // pi指向新的对象,引用计数为1, p2 p3 的引用计数减一
        cout << "pi.useCount()=" << pi.use_count() << endl;
        cout << "pi2.useCount()=" << pi2.use_count() << endl;
        cout << "pi3.useCount()=" << pi3.use_count() << endl;
    }

当 局部的shareptr 离开其生命域的时候,引用计数减为0;

 shared_ptr<string> func2()
    {
        return make_shared<string>("null");
        // 离开作用域之后,引用计数将减少为0
    };

引用计数为0

当一个shared_ptri引用计数从1变成0,则它会自动释放自己所管理(指向)的对象;

 auto pi = make_shared<int>(100);
    auto pi2 = make_shared<int>(100);
    pi = pi2; // 把 pi2 赋值给 pi , pi将和pi2共同指向一个对象; pi原先指向的对象 被释放;   pi 的引用计数和 pi2的 引用计数为 2
    cout << "pi.cout=" << pi.use_count() << endl;
    cout << "pi2.cout=" << pi2.use_count() << endl;

3 shared_ptr()的基本操作

use_count()

// 1) user_count() 显示share_ptr 的引用计数

unique()

// 2) unique()是否该智能指针独占某个指向的对像,也就是若只有一个智能指针指向某个对象,unique()返回true

 auto pi = make_shared<int>(100);
    auto pi2 = make_shared<int>(100);
    pi = pi2; // 把 pi2 赋值给 pi , pi将和pi2共同指向一个对象; pi原先指向的对象 被释放;   pi 的引用计数和 pi2的 引用计数为 2
     if (pi.unique())
    {
        cout << "pi独占对象" << endl;
    }
    else
    {
        cout << "pi共享对象" << endl;
    }
  

reset()

不带参数
  • 若p是唯一指向该对象的指针,那么释放p所指向的对象,并将pi置空
  • 若p不是唯一指向该对象的指针,那么不释放p所指向的对象,但指向该对象的引用计数会减少1,同时将置空 *
 auto pi = make_shared<int>(100);
  auto pi3 = make_shared<int>(100);
    cout << "pi3: usecout=" << pi3.use_count() << endl;
    pi3.reset();
    pi.reset();

    cout << "pi3: usecout=" << pi3.use_count() << endl;
    cout << "pi: usecout=" << pi.use_count() << endl;
    cout << "================================================================" << endl;

带参数
  • reset()带参数(一般是一个new出来的指针)时
  • 若p是唯一指向该对象的指针,则释放p指向的对象,让p指向新对像。
  • 若p不是唯一指向该对象的指针,则不释放p指向的对象,但指向该对象的引用计数会减少1,同时让p指向新对象:

    auto pi4 = make_shared<int>(100);
    auto pi5(pi4);
    cout << "pi4: usecout=" << pi4.use_count() << endl;
    cout << "pi5: usecout=" << pi5.use_count() << endl;
    pi4.reset(new int(200)); // 不释放原内存; 原内存的引用计数减一, pi4 指向新内存;
    cout << "pi4: usecout=" << pi4.use_count() << endl;
    cout << "pi5: usecout=" << pi5.use_count() << endl;
    if (pi4.unique() && pi5.unique())
    {
        cout << "pi4 pi5独占对象" << endl;
    }
    else
    {
        cout << "pi4 pi5共享对象" << endl;
    }

空指针使用reset()
 shared_ptr<int>p;
 p.reset(neit(1);  //释放p所指向的对象,让p指向新对象,因为原来p为空的,所以就等于啥也没释放直接指向新对象;*

使用解引用

 auto pi6 = make_shared<int>(500);
    cout << "pi6 = " << *pi6 << endl;

get()

  • 考虑到有些函数的修数需要的是一个内置裸指针而不是智能指针。
  • p.get():返回中保存的指针(裸指针),小心使用,如果智能指针释放了所指向的对象,那么这个返回的裸指针也就失效.
  int *point = pi6.get();
    *point = 600;
    cout << "pi6 = " << *pi6 << endl;
注意:
  • 不能使用delete 来回收get() 返回的裸指针
  delete point;

swap()

  • 交换两个智能指针所指向的对象
   shared_ptr<double> Rp(new double(100.00));

    shared_ptr<double> Lp(new double(520.00));
    cout << "Lp = " << *Lp << endl;
    cout << "Rp = " << *Rp << endl;
    Rp.swap(Lp);
    // swap(Lp,Rp); 或者使用这个函数
    cout << "Lp = " << *Lp << endl;
    cout << "Rp = " << *Rp << endl;

nullptr()

  • 将所指向的对象引用计数减1,若引用计数变为0,则释放智能指针所指向的对象。
  • 将智能指针置空
shared_ptr<string> Np(new string("null"));
    auto pp(Np);

    cout << "Np.useCout = " << Np.use_count() << endl;
    cout << "pp.useCout = " << pp.use_count() << endl;
    Np = nullptr; // Np 置空; pp指向的对象 的引用计数减一
    cout << "Np.useCout = " << Np.use_count() << endl;
    cout << "pp.useCout = " << pp.use_count() << endl;

智能指针名字作为判断条件

 if (Np)
    {
        cout << "Np 指针指向一个对象" << endl;
    }
    else
    {

        cout << "Np 指针指向空" << endl;
    }

4 指定删除器

  • 一定时机帮我们删除所指向的对象;delete:将delete运算符号作为默认的资源析构方式。
  • 我们可以指定自己的删除器取代系统提供的默认删除器,当智能指针需要删除所指向的对象时,编译器就会调用我们自己的删除器
  • shared ptr指定删除器方法比较简单,一般只需要在参数中添加具体的删除器函数名即可;
例子一
 void myDelete(int *p) // 删除整型对象用
    {
        delete p;
        cout << "detail5::myDelete is Used" << endl;
    }

    void func()
    {
        // auto pi = make_shared<int>(new int(100),myDelete); // make_shared 函数不能 指定删除器
        shared_ptr<int> pi2(new int(100), myDelete);
    }
删除器可以用lambda 表达式
  void func2()
    {
        shared_ptr<int> pi3(new int(100), [](int *p)
                            { cout<<"detail5::func2()"<<endl;
                                delete p; });
    };
自定义删除器
  • 默认删除器处理不了(用shared ptri管理动态数组),需要我们提供自己指定的删刚除器:
void func3()
    {
        shared_ptr<int> pi4(new int[10](), [](int *p)
                            {cout<<"detail5:: func3()"<<endl;
                                delete[] p; });
    }
default_delete
  • 可用default_delete来做删除器,default_.delete是标准库里的模板类。
void fun4()
    {
        cout << "detail5:: func4()" << endl;
        shared_ptr<int> pi5(new int[100](), std::default_delete<int[]>());
    }
shared<T [ ] >

也可以使用 ,定义数组的时候我们在尖括号中加[],定义模板参数是数组

void func5()
    {
        cout << "detail5:: func5()" << endl;
        shared_ptr<int[]> pi5(new int[100]());
    }
仿函数自定义删除器
//用来释放malloc出来的函数对象
template<class T>
class FreeFunc{
public:
	void operator()(T* ptr)
	{
		cout << "free:" << ptr << endl;
		free(ptr);
	}
};

//用来释放new[]出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};
//用来释放new 出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
	void operator()(T* ptr)
	{
		cout << "delete " << ptr << endl;
		delete ptr;
	}
};

//用来释放文件描述符的函数对象
template<class T>
class ClosefdFunc{
public:
	void operator()(T* fd)
	{
		cout << "close fd" << fd << endl;
		fclose(fd);
	}
};

void test06(){

	FreeFunc<int>        Object1;
	shared_ptr<int>      sp1((int*)malloc(sizeof(int)*4), Object1);         // 回调函数是可调用对象,可以是普通的函数名或者函数对象或者lambda表达式

	DeleteArrayFunc<int> Object2;
	shared_ptr<int>      sp2(new int[4], Object2); 

	ClosefdFunc<FILE>    Object3;
	shared_ptr<FILE>     sp3(fopen("myfile.txt","w"), Object3);

}

	int main()
{
    test06();
    return 0;
}

参见我的 另一篇 <仿函数>

额外的例子

    class A
    {
    public:
        A() = default; //
        ~A()
        {
            cout << "~A()" << endl;
        }
    };
    void func()
    {
        // 使用lambda 自定义删除器
        shared_ptr<A> p(new A[10], [](A *p) // 对象数组
                        {cout<<"detail6::func()"<<endl;
                             delete[] p; });
    }
    void func2()
    {
        cout << "detail6:: func2()" << endl;
        // 可用default_delete来做删除器,default_.delete是标准库里的模板类。
        shared_ptr<A> p2(new A[10], std::default_delete<A[]>()); // 模板参数是对象数组
    }
    // 也可以使用 ,定义数组的时候我们在尖括号中加[],定义模板参数是对象数组
    void func3()
    {
        cout << "detail6:: func3()" << endl;
        shared_ptr<A[]> p2(new A[10]);
    }

删除器补充

  • 就算是两个shared ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared ptr也属于同一个类型
void func()
    {
        auto lambda1 = [](int *p)
        {
            cout << "detail7::func() lambda1" << endl;
            delete p;
        };

        auto lambda2 = [](int *p)
        {
            cout << "detail7::func() lambda2" << endl;
            delete p;
        };

        shared_ptr<int> p1(new int(100), lambda1);
        shared_ptr<int> p2(new int(100), lambda2);
        // /类型相同,就代表可以放到元素类型为该对象类型的容器里来;
        vector<shared_ptr<int>> vc{p1, p2};
    }
  • 也可以使用 ,定义数组的时候我们在尖括号中加[],定义模板参数是对象数组
void func3()
{
    cout << "detail6:: func3()" << endl;
    shared_ptr<A[]> p2(new A[10]);
}

删除器补充

  • 就算是两个shared ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared ptr也属于同一个类型
void func()
    {
        auto lambda1 = [](int *p)
        {
            cout << "detail7::func() lambda1" << endl;
            delete p;
        };

        auto lambda2 = [](int *p)
        {
            cout << "detail7::func() lambda2" << endl;
            delete p;
        };

        shared_ptr<int> p1(new int(100), lambda1);
        shared_ptr<int> p2(new int(100), lambda2);
        // /类型相同,就代表可以放到元素类型为该对象类型的容器里来;
        vector<shared_ptr<int>> vc{p1, p2};
    }


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

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

相关文章

从一个Demo说起Dubbo3

简介 2017年的9月份&#xff0c;阿里宣布重启Dubbo的开发维护&#xff0c;并且后续又将Dubbo捐献给了Apache&#xff0c;经过多年的发展已经发布到3.X版本了&#xff0c;Dubbo重启维护之后是否有值得我们期待的功能呢&#xff0c;下面就来看看吧。 Apache Dubbo 是一款微服务…

买英伟达RTX 30 系显卡送《穿越火线》大礼包,你心动了吗?

2022年下半年英伟达一口气推出了RTX 4090、4080、4070 Ti显卡&#xff0c;40系列中的4060也在准备中&#xff0c;而RTX 30 系列在新系列的光芒下显得有些暗淡。 面对40系列即将成为主流的这种情况下&#xff0c;英伟达势必要想一些办法清清30系列显卡的库存&#xff0c;于是英…

上半年要写的博客文章24

上半年要写的博客文章21 这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个…

网络工程师备考6章(续3)

6.20 距离矢量路协议与RIP 动态路由协议的第一个分类:距离矢量和链路状态 什么是距离矢量,例如我要从成都自驾去北京,不知道怎么走,决定路径的方式可以问别人怎么到西安,到西安后再问别人下一条路径,怎么到郑州,到郑州再问别人。一跳一跳的不停问别人,类似于距离矢量协…

P2- 复信号 - 通讯原理

前言&#xff1a; 这里主要结合一下欧拉定理,介绍一下复信号 一 复数概念定义 复数有两种定义方式&#xff1a; 指数函数&#xff1a;&#xff08;通过欧拉公式展开可以得到对应的复数形式&#xff09; 复数&#xff1a;实部a, 虚部b 幅值:A 相位: 二 复信号 2.1 定义 复信…

深度学习程序的预处理

目录 引入 1、预定义符号 1、为什么oj的编译器是clang和gcc呐&#xff1f; 1、vs测试 2、gcc测试 2、#define 1、#define定义标识符 1、#define的花样使用 2、续行符\的使用 3、预处理文件的内容展示和为什么头文件不能重复包含 4、vs下如何生成预处理后的文件&#xff1…

高分综述:人类肠道病毒组分类的进展和挑战

期刊&#xff1a;Cell Host Microbe 影响因子&#xff1a;31.316发表时间&#xff1a;2022.7 - 一、摘要 -人类肠道病毒组通常被称为肠道微生物组的“暗物质”&#xff0c;仍未得到充分研究。了解不同人群肠道病毒组的组成和变化对于探索其对人类健康的影响至关重要。人类肠道病…

聊透Spring事件机制

1、概述 事件机制是Spring为企业级开发提供的神兵利器之一&#xff0c;它提供了一种低耦合、无侵入的解决方式。 但其实Spring事件的设计其实并不复杂&#xff0c;它由三部分组成&#xff1a;事件、发布器、监听器。事件是主体&#xff0c;发布器负责发布事件&#xff0c;监听…

Lottie简介 + 结合到vue3中使用

Lottie简介 结合封装到vue3中使用前言&#xff1a;一、Lottie是什么1. 官方介绍2. 实现流程3. 动画资源二、为什么要选择Lottie三、lottie-web的使用1. 安装导入2. 初始化动画实例3. lottie-web支持的控制动画的主要方法4. lottie-web支持的监听动画的常用的事件四、lottie-we…

C++字符编码详解及利用string遍历中文字符串

作者&#xff1a;非妃是公主 专栏&#xff1a;《笔记》《C》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录C遍历英文字符串C遍历中文字符串&#xff08;不会出问题情况&#xff09;C遍历中文字符串&#xff…

Linux基础——进程的概念和控制(操作系统级讲解)

前言 我们经常会听到一个概念——进程。但是进程并不是一个孤立的概念&#xff0c;需要对操作系统有比较深入的了解。所以这篇博客将在读者的脑中先对操作系统构建一个大概的印象&#xff0c;再对进程做了解。 冯诺依曼结构 冯诺依曼结构也称普林斯顿结构&#xff0c;是一种…

微信小程序|智能停车系统中车牌计费功能实现

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; …

数据结构基础篇》》约瑟夫环

数据结构开讲啦&#xff01;&#xff01;&#xff01;&#x1f388;&#x1f388;&#x1f388; 本专栏包括&#xff1a; 抽象数据类型线性表及其应用栈和队列及其应用串及其应用数组和广义表树、图及其应用存储管理、查找和排序将从简单的抽象数据类型出发&#xff0c;深入浅出…

python 基础入门

文章目录前言python 基础入门一、python环境如何搭建、开发工具pycharm如何破解01 python下载02 python 安装03 python开发工具安装(pycharm )03::01 安装pycharm03::02 多次试用二、python 常规基础01 python 规范02 python中的关键字03 python缩进04 python注释哈哈哈前言 如…

07-JVM 类加载机制?

1.JVM 类加载机制分为五个部分&#xff1a;加载&#xff0c;验证&#xff0c;准备&#xff0c;解析&#xff0c;初始化。 2.一个类型从被加载到虚拟机内存中开始&#xff0c;到卸载出内存为止&#xff0c;它的整个生命周期将会经历加载&#xff08;Loading、验证&#xff08;V…

分布式版本控制Git

从基本的环境配置与安装到Git的基本操作&#xff0c;轻松应对Git在使用时遇到的常见问题。 https://blog.csdn.net/a18307096730/article/details/124586216?spm1001.2014.3001.550202_版本控制器的方式03_svn(过时)_git04git工作流程简述05git环境配与安装06 获取本地仓库Git…

P3375 【模板】KMP字符串匹配

题目描述 给出两个字符串 s_1s1​ 和 s_2s2​&#xff0c;若 s_1s1​ 的区间 [l, r][l,r] 子串与 s_2s2​ 完全相同&#xff0c;则称 s_2s2​ 在 s_1s1​ 中出现了&#xff0c;其出现位置为 ll。 现在请你求出 s_2s2​ 在 s_1s1​ 中所有出现的位置。 定义一个字符串 ss 的 bor…

概率论【离散型二维变量与连续性二维变量(上)】--猴博士爱讲课

5.离散型二维变量与连续性二维变量&#xff08;上&#xff09; 1/8 已知二维离散型分布律&#xff0c;求??? 离散型直接看表 【做题方法参考如下】 2/8 已知二维离散型分布律&#xff0c;判断独立性 如果满足p(xy) p(x) * p(y)&#xff0c;那么相互独立 则我们只需要验证每…

C 程序设计教程(12)—— C 语言顺序结构程序设计

C 程序设计教程&#xff08;12&#xff09;—— C 语言顺序结构程序设计 该专栏主要介绍 C 语言的基本语法&#xff0c;作为《程序设计语言》课程的课件与参考资料&#xff0c;用于《程序设计语言》课程的教学&#xff0c;供入门级用户阅读。 目录C 程序设计教程&#xff08;1…

深入探索Flutter性能优化

前言 Flutter 作为目前最火爆的移动端跨平台框架&#xff0c;能够帮助开发者通过一套代码库高效地构建多平台的精美应用&#xff0c;并支持移动、Web、桌面和嵌入式平台。对于 Android 来说&#xff0c;Flutter 能够创作媲美原生的高性能应用&#xff0c;但是&#xff0c;在较…