• c++ 智能指针


    1. 起源

    c++ 把内存的控制权对开发人员开放,让程序显式的控制内存,这样能够快速的定位到占用的内存,完成释放的工作。但是这样也会引发一些问题,也就是普通指针的隐患:

    1.1 野指针

    出现野指针的有几个地方 :

    1. 指针声明而未初始化,此时指针的将会随机指向
    2. 内存已经被释放、但是指针仍然指向它。这时内存有可能被系统重新分配给程序使用,从而会导致无法估计的错误
    	// 野指针
        int * p ;
        cout << "p : " << p <<   endl;
    
        int * p2 = new int(20);
        delete p2;
        cout << "p2 : " << p2 <<   endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.2 重复释放

    释放已经释放过的内存,或者释放已经被重新分配过的内存,就会导致重复释放错误.

    //2. 重复释放
        int * p3 = new int(30);
        delete p3;
        delete p3;  //重复delete 会报错。
        cout << "p3 : " << p3 <<   endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.3 内存泄漏

    不再使用的内存,并没有释放,或者忘记释放,导致内存没有得到回收利用。 忘记调用delete

    //3. 忘记释放了
        int * p4 = new int(40);
    
    • 1
    • 2

    2. 简介

    C++智能指针是一种特殊类型的指针,其作用是管理动态分配的内存资源,以提供更安全和方便的内存管理。智能指针通过封装原始指针,并在适当的时候自动释放内存,从而避免内存泄漏和悬挂指针等问题。

    c++11标准用 unique_ptr | shared_ptr | weak_ptr 等指针来自动回收堆中分配的内存。智能指针的用法和原始指针用法一样,只是它多了些释放回收的机制罢了。

    2.1 原理

    智能指针位于memory 头文件中,所以要想使用智能指针,还需要导入这个头文件 #include

    int * p = new int(10);
    unique_ptr<int> up(p);
    
    • 1
    • 2

    智能指针其实是一个类 , 它里面能够包装原始指针。

    template <typename _Tp, typename _Dp = default_delete<_Tp> >
    class unique_ptr{}
    
    • 1
    • 2

    完整代码:

    #include 
    #include
    
    using namespace std;
    
    class Abstract{
    public:
        Abstract(){
            cout <<"Abstract构造" <<endl;
        }
    
        ~Abstract(){
            cout <<"Abstract析构" <<endl;
        }
    };
    
    void Construct_pointer(){
        Abstract * A = new Abstract;
        unique_ptr<Abstract> up0 (A);
    
    }
    
    int main() {
    
        cout <<"----------------------" <<endl;
        Construct_pointer();
        cout <<"----------------------" <<endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    unique_ptr 的析构:

         ~unique_ptr() noexcept
          {
    	auto& __ptr = std::get<0>(_M_t);
    	if (__ptr != nullptr)
    	  get_deleter()(__ptr);
    	__ptr = pointer();
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 总结:
    1. 使用智能指针的类在栈内存创建的对象,来包装原始指针。
    2. 当智能指针的对象销毁了之后, 顺便也会销毁原始指针。
    3. 智能指针并不是指针,只是包装了指针的类而已。
    unique_ptr<Abstract> *up  = new unique_ptr<Abstract>(A);
    delete up; //报错
    
    • 1
    • 2

    使用智能指针的类在堆内存创建的对象,来包装原始指针,会报错。

    3. 智能指针

    智能指针把裸指针包装起来,在构造函数里初始化,在析构函数里释放。这样当对象失效销毁时,C++ 就会自动调用析构函数,完成内存释放、资源回收等清理工作。它实践了 RAII(Resource Acquisition Is Initialization),包装了裸指针,而且因为重载了 *-> 操作符,用起来和原始指针一模一样。

    3.1 unique_ptr

    unique_ptr 是一个独享所有权的智能指针,它提供了严格意义上的所有权。也就是只有这个指针能够访问这片空间,不允许拷贝,但是允许移动(转让所有权)。
    unique_ptr 在声明的时候必须用模板参数指定类型。

    3.1.1 基础使用:

    1. 创建智能指针
        int * p  = new int(10);
        unique_ptr<int> ptr0(p);
        //unique_ptr up1(p); //禁止多个智能智能包装同一个原始指针
    	unique_ptr<int> ptr1(new int(20));
    	
    	unique_ptr<std::string> ptr2(new string("hello")); // string智能指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 可以移动,禁止拷贝
    //unique_ptr up2 = up; // 禁止拷贝 Call to deleted constructor of 'unique_ptr'
    unique_ptr<int> up3 = move(ptr0); // 可以移动
    //or
     // 工厂函数创建智能指针  
    //std::unique_ptr ptr5 = make_unique(36);
    auto ptr5 = make_unique<int>(36);   
    assert(ptr5 && *ptr5 == 36);         // 此时智能指针有效
    
    auto ptr6 = std::move(ptr5);         // 使用move()转移所有权
    assert(!ptr5 && ptr6);               // ptr1变成了空指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 智能指针中得到原始指针
    int * p2 = ptr0.get();
    cout << "p2解引用:" << *p2 <<endl;
    
    • 1
    • 2

    重载 *

    typename add_lvalue_reference<element_type>::type
    operator*() const
    {
    _GLIBCXX_DEBUG_ASSERT(get() != pointer());
    return *get();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 直接通过智能指针获取裸指针指向的数据
    #include 
    cout << "智能指针来获取原始数据 " << *ptr0 <<endl;
    assert(*ptr0 == 10);
    
    assert(*ptr2 == "hello"); // 可以使用*取内容
    assert(ptr2->size() == 5);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 手动释放指针
    
    ptr0.reset(); // delete 原始指针,delete p
    //or
    int * p3 = new int(30);
    ptr0.reset(p3) ; //delete掉 p 然后重新包装p3.
    assert(*ptr0 == 30);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.1.2 注意点

    1. 智能指针没有定义加减运算,不能随意移动指针地址,这就完全避免指针越界等危险操作,可以让代码更安全:
    ptr0++;                        // 导致编译错误
    ptr1 += 2;                     // 导致编译错误
    
    • 1
    • 2
    1. 未初始化的 unique_ptr 表示空指针,这样就相当于直接操作了空指针,运行时就会产生致命的错误(比如 core dump)。
    unique_ptr<float> ptr3;
    cout << "使用智能指针来获取原始数据 " << *ptr3 <<endl;
    
    • 1
    • 2

    为了避免错误,可以调用工厂函数 make_unique(),强制创建智能指针的时候必须初始化。

    #include 
    
        auto ptr3 = make_unique<int>(36);               // 工厂函数创建智能指针
        assert(ptr3 && *ptr3 == 36);
    
        auto ptr4 = make_unique<string>("world");  // 工厂函数创建智能指针
        assert(!ptr4->empty());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果出现错误,参考: 关于c ++:error :: make_unique不是“ std”的成员
    或者在代码前面添加:

    template<typename T, typename... Args>                                          // 可变参数模板
    std::unique_ptr< T >                                                            // 返回智能指针
    make_unique(Args&&... args) {
        return std::unique_ptr< T >(new T(std::forward<Args>(args)...));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.1.3 完整代码展示:

    #include 
    #include 
    
    /*
       练习: 使用unique_ptr来管理friar的指针
    
     */
    using namespace std;
    
    template<typename T, typename... Args>                                          // 可变参数模板
    std::unique_ptr< T >                                                            // 返回智能指针
    make_unique(Args&&... args) {
        return std::unique_ptr< T >(new T(std::forward<Args>(args)...));
    }
    
    
    class friar{
    public:
        string name;
        friar(string name) : name(name){
            cout << "friar的构造..." <<endl;
        }
        ~friar (){
            cout << "friar的析构..." <<endl;
        }
    
        void travel(){
            cout << name << " 在游历..." <<endl;
        }
    };
    
    int main() {
        //方法一
        friar * f = new friar("张三丰");
        unique_ptr<friar> ptr(f);
        //方法二
        // unique_ptr ptr;
        // ptr.reset(f);
        //方法三
        //friar f("张三丰");
        //unique_ptr ptr = make_unique(f);
        
    
        //4. 通过智能指针来调用管理的那个指针指向的位置里面存放的对象的成员函数travel.
        friar * ff = ptr.get();
        ff->travel();
    
        //---------先解引用--------------
        (*ptr).travel();
    
        //---------直接调用---------
        ptr->travel();
    
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    这里重载 ->

     pointer
     operator->() const noexcept
     {
     _GLIBCXX_DEBUG_ASSERT(get() != pointer());
     return get();
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.2 shared_ptr

    shared_ptr : 允许多个智能指针共享同一块内存,由于并不是唯一指针,所以为了保证最后的释放回收,采用了计数处理,每一次的指向计数 + 1 , 每一次的 reset会导致计数 -1 ,直到最终为0 ,内存才会最终被释放掉。 可以使用use_cout 来查看目前的指针个数。

    3.2.1 基础使用:

    1. 创建 shared_ptr 智能指针
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main() {
    
        shared_ptr<int> ptr1(new int(10));     // int智能指针
        assert(*ptr1 == 10);                    // 可以使用*取内容
    
        shared_ptr<string> ptr2(new string("hello"));  // string智能指针
        assert(*ptr2 == "hello");                      // 可以使用*取内容
    
        auto ptr3 = make_shared<int>(42);  // 工厂函数创建智能指针
        assert(ptr3 && *ptr3 == 42);       // 可以判断是否为空指针
    
        auto ptr4 = make_shared<string>("world");  // 工厂函数创建智能指针
        assert(!ptr4->empty());                   // 可以使用->调用成员函数
    
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    1. shared_ptr 指向计数
      shared_ptrunique_ptr 的不同点:同一块内存的所有权是可以被安全共享的,也就是说支持拷贝赋值,允许被多个共享指针同时持有。
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main() {
    
        int * p = new int(12);
        shared_ptr <int> sp1(p); //共享指针
        cout <<"查看计数sp1 :" << sp1.use_count() << endl;
        cout << "-----------------------" << endl;
        shared_ptr <int> sp2 (sp1);
        cout <<"查看计数sp1 :" << sp1.use_count() << endl;
        cout <<"查看计数sp2 :" << sp2.use_count() << endl;
    
    
        //shared_ptr  sp03  = sp01; //可以拷贝,但是看起来拷贝之后计数没有增加。
        shared_ptr <int> sp3 {sp2};
        cout << "-----------------------" << endl;
        cout <<"查看计数sp1 :" << sp1.use_count() << endl;
        cout <<"查看计数sp2 :" << sp2.use_count() << endl;
        cout <<"查看计数sp3 :" << sp3.use_count() << endl;
    
    
        cout << "-----------------------" << endl;
        sp1.reset();
        cout <<"查看计数sp1 :" << sp1.use_count() << endl;
        cout <<"查看计数sp2 :" << sp2.use_count() << endl;
        cout <<"查看计数sp3 :" << sp3.use_count() << endl;
    
        cout << "***********************" << endl;
        sp2.reset();
        cout <<"查看计数sp1 :" << sp1.use_count() << endl;
        cout <<"查看计数sp2 :" << sp2.use_count() << endl;
        cout <<"查看计数sp3 :" << sp3.use_count() << endl;
    
        cout << ">>>>>>>>>>>>>>>>>>>>>>" << endl;
        sp3.reset();
        cout <<"查看计数sp1 :" << sp1.use_count() << endl;
        cout <<"查看计数sp2 :" << sp2.use_count() << endl;
        cout <<"查看计数sp3 :" << sp3.use_count() << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    运行结果:

    查看计数sp1 :1
    -----------------------
    查看计数sp1 :2
    查看计数sp2 :2
    -----------------------
    查看计数sp1 :3
    查看计数sp2 :3
    查看计数sp3 :3
    -----------------------
    查看计数sp1 :0
    查看计数sp2 :2
    查看计数sp3 :2
    ***********************
    查看计数sp1 :0
    查看计数sp2 :0
    查看计数sp3 :1
    >>>>>>>>>>>>>>>>>>>>>>
    查看计数sp1 :0
    查看计数sp2 :0
    查看计数sp3 :0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    引用计数最开始的时候是 1,表示只有一个持有者。如果发生拷贝赋值——也就是共享的时候,引用计数就增加,而发生析构销毁的时候,引用计数就减少。只有当引用计数减少到 0,也就是说,没有任何人使用这个指针的时候,它才会真正调用 delete 释放内存。

    因为 shared_ptr 具有完整的“值语义”(即可以拷贝赋值),所以,它可以在任何场合替代原始指针,而不用再担心资源回收的问题,比如用于容器存储指针、用于函数安全返回动态创建的对象,等等。

    3.2.2 出现的问题

    对于引用计数法实现的计数,总是避免不了循环引用(或环形引用)的问题,即我中有你,你中有我,shared_ptr也不例外。如果两个对象内部的智能指针互相指向了对方,导致自己的引用计数一直为1,所以没有进行析构,这就造成了内存泄漏。

    #include 
    #include 
    #include 
    
    
    using namespace std;
    
    class A;
    
    class B{
    public:
        shared_ptr<A> spa;
        void linkA( shared_ptr<A> sp){
            spa = sp;
        }
        B(){
            cout << "B 构造..."  << endl;
        };
        ~B(){
          cout << "B 析构~~~"  << endl;
        };
    };
    
    class A{
    public:
        shared_ptr<B> spb;
        void linkB( shared_ptr<B> sp){
            spb = sp;
        }
        A(){
            cout << "A 构造..."  << endl;
        };
        ~A(){
            cout << "A 析构 ~~~"  << endl;
        };
    };
    
    
    int main() {
    
        B * b = new B;
        A * a = new A;
    
        shared_ptr<B> sp_b(b);
        shared_ptr<A> sp_a(a);
    
    //    b->linkA(sp_a);
    //    a->linkB(sp_b);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    运行结果:

    B 构造...
    A 构造...
    A 析构 ~~~
    B 析构~~~
    
    • 1
    • 2
    • 3
    • 4

    如果不注释最后两行:

    int main() {
    
        B * b = new B;
        A * a = new A;
    
        shared_ptr<B> spb(b);
        shared_ptr<A> spa(a);
    
        b->linkA(spa);
        a->linkB(spb);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    则运行结果是:

    B 构造...
    A 构造...
    
    • 1
    • 2

    没有执行析构,指针没有销毁,计数不为0,原因解析如下图:
    在这里插入图片描述
    此时指向A 的智能指针 2 个,指向B的智能指针 2 个

    在这里插入图片描述
    当代码运行完,栈内存销毁,此时:
    此时指向A 的智能指针 1 个,指向B的智能指针 1 个

    3.3 weak_ptr

    为了避免shared_ptr的环形引用问题,需要引入一个弱指针 weak_ptr,它指向一个由 shared_ptr管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。不论是否有weak_ptr指向,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。从这个角度看,weak_ptr更像是shared_ptr的一个助手而不是智能指针。

    3.3.1 基础使用

    weak_ptr 不包装原始指针,它包装的是智能指针 (weak_ptr | shared_ptr)

    #include 
    #include 
    #include 
    
    using namespace std;
    
    class stu;
    
    int main() {
    
        int * p = new int (36);
    
        //共享指针
        shared_ptr<int> sp(p);
        cout << "sp count : " << sp.use_count() << endl;
    
        //弱指针
        weak_ptr<int> wp(sp);
        cout <<"wp count : "<< wp.use_count() << endl;
    
        //只能得到包装的共享指针。
        shared_ptr<int>  sp1 = wp.lock();
        cout << "p value : " << *sp1 << endl;
    
        cout << "p value : " << *(wp.lock()) << endl;
    
        cout << "wp cout : " << wp.lock().use_count() << endl;
    
        cout << "sp1 cout : " << sp1.use_count() << endl;
        cout << "sp cout : " << sp.use_count() << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行结果:

    sp count : 1
    wp count : 1
    p value : 36
    p value : 36
    wp cout : 3
    sp1 cout : 2
    sp cout : 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.3.2 解决环形引用问题

    #include 
    #include 
    #include 
    
    using namespace std;
    
    class A;
    
    class B{
    public:
        weak_ptr<A> spa;
        void linkA( shared_ptr<A> sp){
            spa = sp;
        }
        B(){
            cout << "B 构造..."  << endl;
        };
        ~B(){
            cout << "B 析构~~~"  << endl;
        };
    };
    
    class A{
    public:
        weak_ptr<B> spb;
        void linkB( shared_ptr<B> sp){
            spb = sp;
        }
        A(){
            cout << "A 构造..."  << endl;
        };
        ~A(){
            cout << "A 析构~~~"  << endl;
        };
    };
    
    
    int main() {
    
        B * b = new B;
        A * a = new A;
    
        shared_ptr<B> sp_b(b);
        shared_ptr<A> sp_a(a);
    
        b->linkA(sp_a);
        a->linkB(sp_b);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    运行结果:

    B 构造...
    A 构造...
    A 析构~~~
    B 析构~~~
    
    • 1
    • 2
    • 3
    • 4

    4. 使用场景

    1. 动态数组管理: 智能指针可以用于管理动态分配的数组,确保在不需要时自动释放内存。
    #include 
    #include 
    
    int main() {
        std::unique_ptr<int[]> arr(new int[5]); // 创建一个包含5个int元素的动态数组
    
        for (int i = 0; i < 5; ++i) {
            arr[i] = i; // 对数组进行赋值
        }
    
        // 使用数组
        for (int i = 0; i < 5; ++i) {
            std::cout << arr[i] << " ";
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 对象所有权转移: 智能指针可以在对象所有权转移时使用,确保资源在合适的时候被释放。
    #include 
    #include 
    
    class MyClass {
    public:
        void DoSomething() { /* ... */ }
    
        ~MyClass(){
            std::cout << "MyClass 析构~~~"  << std::endl;
    
        }
    };
    
    int main() {
        std::unique_ptr<MyClass> ptr(new MyClass()); // 创建一个唯一拥有权的智能指针
    
        ptr->DoSomething(); // 使用ptr调用成员函数
    
        return 0; // 当ptr超出作用域时,MyClass对象将自动销毁
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    1. 异常安全: 智能指针可以确保在发生异常时正确释放资源,避免资源泄漏。
    #include 
    #include 
    
    class MyResource {
    public:
        MyResource() { std::cout << "Acquiring resource." << std::endl; }
        ~MyResource() { std::cout << "Releasing resource." << std::endl; }
    
        void DoSomething() {
            // 执行一些操作
            throw std::runtime_error("Some error occurred.");
        }
    };
    
    int main() {
        try {
            std::unique_ptr<MyResource> ptr(new MyResource()); // 创建一个拥有权的智能指针
    
            ptr->DoSomething(); // 这里会抛出一个异常,但由于智能指针的析构函数会被调用,资源会得到正确释放
        } catch (const std::exception& e) {
            std::cout << "Exception occurred: " << e.what() << std::endl;
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 相关阅读:
    MySQL(高级、面试) —— (MySQL优化 二)Explain 优化MySQL索引
    Config配置文件读写
    c# 设计一个图书管理系统
    SpringBoot整合Websocket,实现作为客户端接收消息的同时作为服务端向下游客户发送消息
    Android 蓝牙使用
    嵌入式分享合集90
    Debezium系列之:Debezium技术专栏第300篇系列文章之打通Debezium实时采集Oracle数据库数据到Kafka集群的技术
    神经网络之万能定理python-pytorch实现,可以拟合任意曲线
    【深度学习】(4) Transformer 中的 Decoder 机制,附Pytorch完整代码
    把Eclipse创建的Web项目(非Maven)导入Idea
  • 原文地址:https://blog.csdn.net/weixin_40378209/article/details/133925519