CCCCCCC指针CCCCC

本文介绍[ shared_ptr / unique_ptr / weak_ptr ]的使用,以及shared_ptr 引用计数和weak_ptr 弱引用计数。

1. 智能指针概述

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

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

  • shared_ptr共享对象的所有权,但性能略差。
  • unique_ptr独占对象的所有权,由于没有引用计数,因此性能较好。
  • weak_ptr配合shared_ptr,解决循环引用的问题。

2. shared_ptr

2.1 shared_ptr 介绍

shared_ptr 内部包含两个指针,一个指向对象,另一个指向控制块(control block),控制块中包含一个引用计数(reference count), 一个弱计数(weak count)和其它一些数据。

简单来说,shared_ptr实现包含了两部分: - 一个指向堆上创建的对象的裸指针,raw_ptr - 一个指向内部隐藏的、共享的管理对象。share_count_object

在这里插入图片描述

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析构的时候,内存才会被释放。shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象销毁时,被管理对象自动销毁。

在这里插入图片描述

2.2 shared_ptr 的基本用法和常用函数

2.2.1 构造智能指针shared_ptr

我们应该优先使用make_shared来构造智能指针,因为它更高效。并且不能将一个原始指针直接赋值给一个智能指针。

//1
auto sp1 = make_shared<int>(100);
//2
shared_ptr<int> sp1 = make_shared<int>(100);
//3
shared_ptr<int> sp1(new int(100));
//err
shared_ptr<int> p = new int(1);

2.2.2 初始化智能指针shared_ptr

shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数和辅助方法来初始化。

  • reset( )不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针P不是唯一指向该对象的指针,则引用计数减少1,同时将智能指针P置空。
  • reset( )带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若P不是唯一的指针,则只减少引用计数,并指向新的对象。
// 智能指针初始化
std::shared_ptr<int> p1(A);//A use_count=1
std::shared_ptr<int> p2 = p1;//A use_count=2
std::shared_ptr<int> p3;//use_count=0
p3.reset(A);//A use_count=1
// 智能指针有值时reset
std::shared_ptr<int> p1(A);//A use_count=1
std::shared_ptr<int> p2 = p1;//A use_count=2
p2.reset(B);//A use_count=1 , B use_count=1

2.2.3 一些辅助函数

//返回shared_ptr中保存的裸指针
s.get()
//重置shared_ptr
s.reset(…)
//返回shared_ptr的强引用计数
s.use_count()
//若use_count()为1,返回true,否则返回false
s.unique()

智能指针可以通过重载的bool类型操作符来判断该指针是否指向不为空的对象

#include<iostream>
#include<memory>
using namespace std;
int main() {
    std::shared_ptr<int> p1;
    p1.reset(new int(1));
    std::shared_ptr<int> p2 = p1;
    // 引用计数此时应该是2
    cout << "p2.use_count() = " << p2.use_count()<< endl;
    p1.reset();
    cout << "p1.reset()\n";
    // 引用计数此时应该是1
    cout << "p2.use_count()= " << p2.use_count() << endl;
    if(!p1) {
        cout << "p1 is empty\n";
    }
    if(!p2) {
        cout << "p2 is empty\n";
    }
    p2.reset();
    cout << "p2.reset()\n";
    cout << "p2.use_count()= " << p2.use_count() << endl;
    if(!p2) {
        cout << "p2 is empty\n";
    }
    return 0;
}

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

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

谨慎使用p.get()的返回值,如果你不知道其危险性则永远不要调用get()函数。p.get()的返回值就相当于一个裸指针的值,不合适的使用这个值,上述陷阱的错误可能发生。

使用get()遵守以下几个约定:

  • 不要保存p.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的
  • 保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针
  • 不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误

2.2.4 指定删除器

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

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

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

#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);
    // lambda表达式
    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;
}

2.3 使用shared_ptr要注意的问题

2.3.1 不要用一个原始指针初始化多个shared_ptr

int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); // 逻辑错误,重复释放同一块内存

2.3.2 不要在函数实参中创建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());

2.3.3 通过shared_from_this()返回this指针

如果想让另一个智能指针共享自己,那么要使用shared_from_this()函数,而不应该把this指针作为shared_ptr返回出来。这其实就违法了上面写的“不要用一个原始指针初始化多个shared_ptr”。

不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构,看下面的例子。运行后调用了两次析构函数。

在这个例子中,由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系的,在离开作用域之后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 << "Construction A" << endl;
    }

    ~A() {
        cout << "Destruction A: " << this << 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;
}

正确返回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(); //ok
    }

    A() {
        cout << "Construction A" << endl;
    }

    ~A() {
        cout << "Destruction A" << endl;
    }
};

int main() {
    shared_ptr<A> sp1(new A);
    shared_ptr<A> sp2 = sp1->GetSelf();  // ok
    cout << "sp1.use_count() = " << sp1.use_count() << endl;
    cout << "sp2.use_count() = " << sp2.use_count() << endl;
    cout << "leave {}" << endl;

    return 0;
}

2.3.4 避免循环引用

循环引用会导致内存泄漏,循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不会减为0,导致两个指针都不会被析构,产生内存泄漏。解决的办法是把A和B任何一个成员变量改为weak_ptr,具体方法见weak_ptr章节。

// 1-3-2-cycle-shared-ptr
#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> ap(new A);
        std::shared_ptr<B> bp(new B);
        ap->bptr = bp;
        bp->aptr = ap;
    }
    cout << "main leave" << endl;  // 循环引用导致ap bp退出了作用域都没有析构
    return 0;
}

3. 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

除了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。

4. weak_ptr

4.1 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指针和解决循环引用的问题。

4.2 weak_ptr 的基本用法和常用函数

4.2.1 构造智能指针weak_ptr

它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造。

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);

4.2.2 一些辅助函数

  • 通过use_count()方法获取当前观察资源的引用计数
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; //结果输出1
  • 通过expired()方法判断所观察资源是否已经释放
shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);
    if (wp.expired())
        cout << "weak_ptr无效,资源已释放";
    else
        cout << "weak_ptr有效";
  • 通过lock方法返回shared_ptr,这个shared_ptr共享weak监视的对象,也就是说相等于shared_ptr new=old。第一次调用f()时,sp还没有释放,use_count=1,所以在lock之后,创建了一个新的shared_ptr共享,那么use_count=2。在第二次调用f()时,sp生命周期已经结束,所以use_count=0,那么lock之后新的shared_ptr也只是指向空,那么必然是无效的,已经释放了。
#include <iostream>
#include <memory>

using namespace std;
std::weak_ptr<int> gw;

void f() {
    auto spt = gw.lock();
    if (gw.expired()) {
        cout << "gw无效,资源已释放";
    }
    else {
        cout << "gw有效, *spt = " << *spt << endl;
    }
}

int main() {
    {
        auto sp = std::make_shared<int>(42);
        gw = sp;
        f();
    }
    f();
    return 0;
}
gw有效, *spt = 42
gw无效,资源已释放

4.3 通过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(); //ok
    }

    A() {
        cout << "Construction A" << endl;
    }

    ~A() {
        cout << "Destruction A" << endl;
    }
};

int main() {
    shared_ptr<A> sp1(new A);
    shared_ptr<A> sp2 = sp1->GetSelf();  // ok
    cout << "sp1.use_count() = " << sp1.use_count() << endl;
    cout << "sp2.use_count() = " << sp2.use_count() << endl;
    cout << "leave {}" << endl;

    return 0;
}
Destructor A

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

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

4.4 weak_ptr解决循环引用问题

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

// 1-3-2-cycle-shared-ptr
#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::weak_ptr<A> aptr;

    ~B() {
        cout << "B is deleted" << endl;
    }
};

int main() {
    {
        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
B is deleted
main leave

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

4.5 使用weak_ptr要注意的问题

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

weak_ptr<int> wp;
{
    shared_ptr<int> sp(new int(1)); //sp.use_count()==1
    wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
    shared_ptr<int> sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
    //sp.use_count()==2
}
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对象被析构,这块“堆”存储块才能被回收。否则weak_ptr无法知道自己所容纳的那个指针资源的当前状态。

我们在使用wp之前需要先lock,再判断。一般情况如下

shared_ptr<int> sp(new int(1)); //sp.use_count()==1
weak_ptr<int> wp=sp;
wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
shared_ptr<int> sp_ok = wp.lock(); //sp.use_count()==2
if(!wp.expired()){
    cout<<"执行流程"<<endl;
}else{
    cout<<"已经销毁"<<endl;
}

如果不是先lock,考虑下面这种多线程特殊情况。可以看到如果先判断再lock,就会出现对空指针的操作,这与我们的逻辑不符。而先lock时,会对引用计数+1,那么就算别的线程释放了sp,该资源也不会被销毁。

shared_ptr<int> sp(new int(1)); //sp.use_count()==1
weak_ptr<int> wp=sp;
wp = sp; //wp不会改变引用计数,所以sp.use_count()==1

if(!wp.expired()){//1 != 0 true
    // 此时其他线程将 sp 释放,则 sp.use_count()==0
    shared_ptr<int> sp_ok = wp.lock(); //sp.use_count()==0,无效
    //再对指针操作都是无效操作了 
}else{
    cout<<"已经销毁"<<endl;
}

5. 智能指针安全性问题

引用计数本身是安全的,至于智能指针是否安全需要结合实际使用分情况讨论:

  1. 多线程代码操作的是同一个shared_ptr的对象,此时是不安全的。

比如std::thread的回调函数,是一个lambda表达式,其中引用捕获了一个shared_ptr。

std::thread td([&sp1]()){....});

又或者通过回调函数的参数传入的shared_ptr对象,参数类型引用

void fn(shared_ptr<A>&sp) {
...
}
..
std::thread td(fn, sp1);
  1. 多线程代码操作的不是同一个shared_ptr的对象

这里指的是管理的数据是同一份,而shared_ptr不是同一个对象。比如多线程回调的lambda的是按值捕获的对象。

std::thread td([sp1]()){....});

另个线程传递的shared_ptr是值传递,而非引用:

void fn(shared_ptr<A>sp) {
...
}
..
std::thread td(fn, sp1);

这时候每个线程内看到的sp,他们所管理的是同一份数据,用的是同一个引用计数。但是各自是不同的对象,当发生多线程中修改sp指向的操作的时候,是不会出现非预期的异常行为的。也就是说,如下操作是安全的。

void fn(shared_ptr<A>sp) {
    ...
    if(..){
        sp = other_sp;
    } else {
        sp = other_sp2;
    }
}

需要注意:所管理数据的线程安全性问题:显而易见,所管理的对象必然不是线程安全的,必然 sp1、sp2、sp3智能指针实际都是指向对象A, 三个线程同时操作对象A,那对象的数据安全必然是需要对象A自己去保证

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

查企业联系电话的方法

对于销售来说&#xff0c;获取准确、全面的企业联系方式&#xff0c;无疑是开发客户的基础与保障&#xff0c;因为任凭能力再高&#xff0c;说服能力多强&#xff0c;没有与客户接触的机会&#xff0c;这些都是无稽之谈。但是大家都知道&#xff0c;道理都懂&#xff0c;但是要…

C++算法 —— 贪心(3)

文章目录 1、买卖股票的最佳时机2、买卖股票的最佳时机Ⅱ3、K次取反后最大化的数组和4、按身高排序5、优势洗牌6、最长回文串7、增减字符串匹配 1、买卖股票的最佳时机 121. 买卖股票的最佳时机 这里最容易想到的就是暴力枚举&#xff0c;两层for循环&#xff0c;i 0&#xf…

分布式链路追踪实战篇-日志库集成opentelemetry的思路

由上文分布式链路追踪入门篇-基础原理与快速应用可以知道分布式链路追踪的作用&#xff0c;但是距离应用到项目中&#xff0c;我们还需要对项目中一些关键组件进行opentelemetry的集成&#xff0c;例如日志库&#xff0c;ORM、http框架、rpc框架等。 一、日志库如何集成opentel…

设计模式-创建型模式-工厂方法模式

一、什么是工厂方法模式 工厂模式又称工厂方法模式&#xff0c;是一种创建型设计模式&#xff0c;其在父类中提供一个创建对象的方法&#xff0c; 允许子类决定实例化对象的类型。工厂方法模式是目标是定义一个创建产品对象的工厂接口&#xff0c;将实际创建工作推迟到子类中。…

美创联合浙江省农业农村厅斩获“IDC中国20大杰出安全项目”!

11月23日&#xff0c;由IDC主办&#xff0c;以“安全风险管控&#xff1a;新形势下的数据安全保护”为主题的2023全球CSO网络安全峰会&#xff08;中国站&#xff09;隆重召开。 会上&#xff0c;IDC “中国20大杰出安全项目&#xff08;CSO20&#xff09;” 重磅揭晓&#xff…

Linux中df命令使用

在Linux中&#xff0c;df命令用于显示磁盘空间的使用情况。它的基本语法如下&#xff1a; df [选项] [文件或目录]这个命令可以用来查看当前系统上各个磁盘分区的使用情况。如果没有指定文件或目录&#xff0c;则所有当前被挂载的文件系统的可用空间将被显示。 df命令的一些常…

手把手用GPT开发小程序全流程!就是这么easy~

大家好&#xff0c;我是五竹。 前段时间用GPT开发了一款小程序:GPT真牛批&#xff01;三天开发一个小程序&#xff0c;三天积累了2000的用户&#xff0c;上周末抽空又接入了流量主&#xff0c;感兴趣的同学可以围观一下。 今天就来带大家走一遍用GPT开发一款小程序的全过程&a…

手把手webpack搭建前端架子

这里以react为例> (一)初始化package.json package name: 你的项目名字叫啥 version: 版本号 description: 对项目的描述 entry point: 项目的入口文件&#xff08;一般你要用那…

【回眸】Tessy单元测试软件使用指南(一)安装篇

安装 在官网上下载安装包&#xff0c;安装完成后打开进入这个界面 注册申请license&#xff1a;在作为服务端的电脑上安装Tessy。安装完成后&#xff0c;启动Tessy会自动生成license服务器的注册码。&#xff08;注册码用于申请试用或永久的license文件&#xff09;这个对于我…

树莓派上使用Nginx通过内网穿透实现无公网IP访问内网本地站点

前言 安装 Nginx&#xff08;发音为“engine-x”&#xff09;可以将您的树莓派变成一个强大的 Web 服务器&#xff0c;可以用于托管网站或 Web 应用程序。相比其他 Web 服务器&#xff0c;Nginx 的内存占用率非常低&#xff0c;可以在树莓派等资源受限的设备上运行。同时结合c…

NX二次开发UF_CSYS_set_wcs 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CSYS_set_wcs Defined in: uf_csys.h int UF_CSYS_set_wcs(tag_t csys_id ) overview 概述 Sets the work coordinate system to the prototype coordinate system whose tag y…

竞赛选题 题目:基于FP-Growth的新闻挖掘算法系统的设计与实现

文章目录 0 前言1 项目背景2 算法架构3 FP-Growth算法原理3.1 FP树3.2 算法过程3.3 算法实现3.3.1 构建FP树 3.4 从FP树中挖掘频繁项集 4 系统设计展示5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于FP-Growth的新闻挖掘算法系统的设计与实现…

【PDF.js】2023 最新 PDF.js 在 Vue3 中的使用

因为自己写业务要定制各种 pdf 预览情况&#xff08;可能&#xff09;&#xff0c;所以采用了 pdf.js 而不是各种第三方封装库&#xff0c;主要还是为了更好的自由度。 一、PDF.js 介绍 官方地址 中文文档 PDF.js 是一个使用 HTML5 构建的便携式文档格式查看器。 pdf.js 是社区…

【HuggingFace Transformer库学习笔记】基础组件学习:pipeline

一、Transformer基础知识 pip install transformers datasets evaluate peft accelerate gradio optimum sentencepiece pip install jupyterlab scikit-learn pandas matplotlib tensorboard nltk rouge在host文件里添加途中信息&#xff0c;可以避免运行代码下载模型时候报错…

vue中下载文件后无法打开的坑

今天在项目开发的时候临时要添加个导出功能我就写了一份请求加导出得代码&#xff0c; 代码&#xff1a; //导出按钮放开exportDutySummarizing (dataRangeInfo) {const params {departmentName: dataRangeInfo.name,departmentQode: dataRangeInfo.qode}//拼接所需得urlcons…

Tomcat注册为服务后,如何配置Tomcat内存大小

前提条件&#xff1a;tomcat已经注册为服务。 1.winR,输入regedit打开注册表 2.找到Tomcat注册表路径&#xff1a; HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Apache Software Foundation\Procrun 2.0\Tomcat80603.找到jvm内存配置路径&#xff1a; HKEY_LOCAL_MACHINE\SOFTW…

Redis高可用之主从复制及哨兵模式

一、Redis的主从复制 1.1 Redis主从复制定义 主从复制是redis实现高可用的基础&#xff0c;哨兵模式和集群都是在主从复制的基础之上实现高可用&#xff1b; 主从复制实现数据的多级备份&#xff0c;以及读写分离(主服务器负责写&#xff0c;从服务器只能读) 1.2 主从复制流…

Windows从源码构建tensorflow

由一开始的在线编译&#xff0c;到后面的离线编译&#xff0c;一路踩坑无数。在此记录一下参考过的文章&#xff0c;有时间整理一下踩坑记录。 一、环境配置 在tensorflow官网上有版本对应关系 win10 bazel 3.1.0 msys2 tensorflow2.3.0 python3.5-3.8 MSVC2019 protobuf3.9.…

羊大师:强健身体是成功的关键

健康是一项无价的财富&#xff0c;拥有强健的身体是实现人生目标的关键。而如何保持健康并拥有一个强健的身体呢&#xff1f;下面就为大家分享一些有效的健身方法和建议&#xff0c;帮助您达到健美身材的目标。 良好的饮食习惯是形成强健身体的基石。我们要摄入足够的营养物质…

Java二级医院区域HIS信息管理系统源码(SaaS服务)

一个好的HIS系统&#xff0c;要具有开放性&#xff0c;便于扩展升级&#xff0c;增加新的功能模块&#xff0c;支撑好医院的业务的拓展&#xff0c;而且可以反过来给医院赋能&#xff0c;最终向更多的患者提供更好的服务。 系统采用前后端分离架构&#xff0c;前端由Angular、J…