C++语法|智能指针的实现及智能指针的浅拷贝问题、auto_ptr、scoped_ptr、unique_ptr、shared_ptr和weak_ptr详细解读

news2024/11/16 19:26:19

文章目录

  • 1.自己实现智能指针
    • 智能指针引起的浅拷贝问题
    • 尝试定义自己的拷贝构造函数解决浅拷贝
  • 2.不带引用计数的智能指针
    • auto_ptr
    • scoped_ptr
    • unique_ptr(推荐)
  • 3.带引用计数的智能指针
    • 模拟实现引用计数
    • shared_ptr和weak_ptr
      • 循环引用(交叉引用)
      • 循环引用导致了什么结果
    • 如何解决
      • 定义对象的时候用强智能指针,引用对象的地方使用弱智能指针
      • 弱智能指针提升为强智能指针,让其拥有裸指针类似的行为
  • 5.多线程访问共享对象问题
    • 给对象添加引用计数使用强弱智能指针监控共享对象
  • 6.自定义删除器
    • 自定义数组删除器
    • 自定义文件资源删除器
    • 使用lambda+function

1.自己实现智能指针

我们在C++变成中使用指针最麻烦的问题就是关于资源的及时释放,尽管我们已经很小心得检查了内存泄漏问题,但还是难免有一些突发情况导致我们定义的指针成为野指针。所以我们对智能指针最简单的要求:

  • 保证做到资源的自动释放
  • 利用栈上的对象出作用域自动析构的特征,来做到资源的自动释放(所以智能指针一定不能放到堆上)
template<typename T>
class SmartPointer {
private:
	T *mptr;
public:
	SmartPointer(T *p = nullptr): mptr(p) { }
	~SmartPointer(): { delete mptr; }
	T& operator*() { return *mptr;}
	T* operator->() { return mptr;}
}

由此我们实现了构造、析构函数和重载解引用和重载成员访问运算符,接下来我们来测试其功能。

int main() {
    SmartPointer<int> ptr1(new int(10));
    *ptr1 = 20;
    class Test {
    public:
        void test() { cout << "test()" << endl; }
    };
    SmartPointer<Test> ptr2(new Test());
    //(ptr2.operator->())->test();
    ptr2->test(); //(*ptr).test();

    return 0;
}

智能指针引起的浅拷贝问题

如果我们沿用上面实现的智能指针,

template<typename T>
class SmartPointer {
private:
	T *mptr;
public:
	SmartPointer(T *p = nullptr): mptr(p) { }
	~SmartPointer(): { delete mptr; }
	T& operator*() { return *mptr;}
	T* operator->() { return mptr;}
}
int main() {
	SmartPointer<int> p1(new int);
	SmartPointer<int> p2(p1);
	return 0;
}

我们通过智能指针p1管理一块整型资源,然后拷贝构造p2,发现程序运行崩溃,这是为什么呢?

因为我们这里在做拷贝构造的时候是做的浅拷贝,在程序结束后,把同一块资源释放了两次,造成了内存泄漏。

尝试定义自己的拷贝构造函数解决浅拷贝

template<typename T>
class SmartPointer {
private:
    T *mptr;
public:
    SmartPointer(T *p = nullptr): mptr(p) { }
    SmartPointer(const SmartPointer<T> &src) {
        mptr = new T(*src.mptr);
    }
    ~SmartPointer() { delete mptr; }
    T& operator*() { return *mptr; } //注意这里返回值是一个引用
    T* operator->() { return mptr; }
};

现在我们在进行拷贝构造的时候,又new了一个空间,所以在析构的时候,各自析构自己的空间。此时代码就不会崩溃了,但是这样又引发了一个新问题:那就是不符合用户区域。

当我们使用p2拷贝构造p1的时候,希望p1和p2管理同一块资源。
也就是说,当用户操作p1和p2的时候,希望操作的是同一个指针,但其实并不是这样的,因为我们进行了深拷贝,所以p1和p2所管理的资源完全就是两块不同的资源。

所以不满足要求!

如何解决呢?

1. 不带引用计数的智能指针

2. 带引用计数的智能指针

2.不带引用计数的智能指针

auto_ptr:在C++17之后不再支持
scoped_ptr unique_ptr:C++11新标准

auto_ptr

auto_ptr<int> ptr1(new int); //C++17标准中删除
auto_ptr<int> ptr2(ptr1);
*ptr2 = 20;
cout << *ptr1 << endl;

现在我们想看看ptr1的值是否被ptr2覆盖,但是我们发现程序崩溃。

这是因为auto_ptr的拷贝构造函数,它先调用了一个release方法,然后返回release的调用结果,其中release的源码如下:

_LIBCPP_INLINE_VISIBILITY _Tp* release() _NOEXCEPT
{
    _Tp* __t = __ptr_;
    __ptr_ = nullptr;
    return __t;
}

他先把原指针拷贝给一个临时变量,然后把原指针置为nullptr,最后返回临时变量。这就表示我们的ptr1被置为空指针了,后来的cout << *ptr1 << endl;只在操作一个空指针了!这是不被允许的。

总结:auto_ptr的解决浅拷贝逻辑就是让后来的指针来管理资源,放弃之前的指针!所以我们不推荐auto_ptr,特别是在容器中不推荐使用,比如说vector<auto_ptr<int>> vec1; vec2(vec1)。我们容器的使用过程中往往会使用容器的拷贝和赋值操作,会导致容器中每个元素的拷贝和赋值,造成容器所有元素失效。

scoped_ptr

scoped_ptr解决浅拷贝问题更加直接,他直接删除了拷贝和赋值操作:

scoped_ptr(const scoped_ptr<T>&) = delete;
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;

unique_ptr(推荐)

这种智能指针也是只让一个指针来管理资源。首先unique_ptr也是做了一个这样的操作:

unique_ptr(const scoped_ptr<T>&) = delete;
unique_ptr<T>& operator=(const scoped_ptr<T>&) = delete;

但是,我们可以使用:

unique_ptr<int> p1(new int);
unique_ptr<int> p2(std::move(p1));

其中std::move可以得到当前变量的右值类型,也就是右值强转操作,那是因为unique_ptr提供了移动构造和移动赋值构造:

unique_ptr(const scoped_ptr<T> &&src)
unique_ptr<T>& operator=(const scoped_ptr<T> &&src)

所以我们的p1的资源全部移动给了p2,所以我们还是不能去访问p1。但是unique_ptr的好吃就是,我们在拷贝构造的过程当中,用户的用意是非常明显的,用户既然都调用move了,所以就是明确要把p1的资源移动给p2。而不像我们在使用auto_ptr在用户没有感知的情况下去操作一个空指针,也不像scoped_ptr那么死板。

3.带引用计数的智能指针

带引用计数的好处就是多个智能指针可以管理同一个资源;

什么叫引用计数呢?给每一个对象资源匹配一个引用计数,当一个智能指针管理这个资源的时候,引用计数+1;当一个智能指针不再使用资源的时候,引用计数-1;并且只要引用计数不为0,就不允许析构;当引用计数为0时,说明该指针已经是管理资源的最后一个指针了,所以它在析构的时候必须释放资源。

这样就完美解决了浅拷贝导致的多次析构同一资源的问题。接下来我们会首先根据之前写的简单的智能指针代码为它添加引用计数的功能。

template<typename T>
class SmartPointer {
private:
	T *mptr;
public:
	SmartPointer(T *p = nullptr): mptr(p) { }
	~SmartPointer(): { delete mptr; }
	T& operator*() { return *mptr;}
	T* operator->() { return mptr;}
}
int main() {
	SmartPointer<int> p1(new int);
	SmartPointer<int> p2(p1);
	return 0;
}

模拟实现引用计数

  • 首先我们完成一个对资源进行引用计数的类,这个类非常简单,主要就是能够记录有多少个智能指针指向了这个资源。
    其中成员方法addRef表示引用计数+1操作
    delRef表示引用计数-1操作并且返回当前指向资源的指针个数。
template <typename T>
class RefCnt {
public:
    RefCnt(T *ptr = nullptr): mptr(ptr), mcount(1) {
        if (mptr != nullptr)
            mcount = 1;
    }
    void addRef() { mcount++; } //添加资源的引用计数
    int delRef() { return --mcount;}  
private:
    T *mptr;
    int mcount;
};
  • 然后是重写我们的构造函数和析构函数
template<typename T>
class SmartPointer { //shared_ptr
private:
    T *mptr;    // 指向资源的指针
    RefCnt<T> *mpRefCnt; //指向该资源引用计数对象的指针
public:
    SmartPointer(T *p = nullptr): mptr(p) {
        mpRefCnt = new RefCnt<T>(mptr);
    }

    ~SmartPointer() { 
        if (0 == mpRefCnt->delRef())
        {
            delete mptr; 
            mptr = nullptr;
        }
    }

构造函数需要初始化一个RefCnt,析构函数判断只有当引用计数为0的时候,才对资源进行释放。

  • 重构拷贝构造函数
    SmartPointer(const SmartPointer<T> &src)
        :mptr(src.mptr), mpRefCnt(src.mpRefCnt) {
        if (mptr != nullptr)
            mpRefCnt->addRef();       
    }

拷贝构造需要首先把当前资源给过去,然后把当前的引用计数对象也给拷贝变量,并且我们需要把计数+1,这里调用的是引用计数类的addRef方法

  • 重构重载赋值运算符
    SmartPointer<T>& operator=(const SmartPointer<T> &src) {
        if (this == &src)
            return *this;
        if(0==mpRefCnt->delRef()){ //给原来使用的资源减少一个引用计数
            delete mptr;
        }
        mptr = src.mptr;
        mpRefCnt = src.mpRefCnt;
        mpRefCnt->addRef();
        return *this;
    }

在这里我们基本实现了一个shared_ptr的核心代码,不过我们这里还存在一个问题,再多线程操作中,该类涉及到了对共享资源count的频繁操作,所以标准库中的智能指针模板都是原子操作的,也就是说他们都是线程安全的。

shared_ptr和weak_ptr

shared_ptr为强智能指针,可以改变资源的引用计数。weak_ptr为弱智能指针,不会改变资源的引用计数。弱智能指针用来观察强智能指针,强智能指针来观察资源(内存)

为什么我们需要强弱智能指针呢?

主要就是因为我们的强智能指针有循环引用的问题。

循环引用(交叉引用)

class B;
class A {
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    weak_ptr<B> _ptrb;
};

class B {
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
	_ptra->testA();
    weak_ptr<A> _ptra;
};
int main () {
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    cout <<  pa.use_count() << endl;
    cout <<  pb.use_count() << endl;
    return 0;
}

输出时:

A()
B()
1
1
~A()
~B()

符合资源分配的预期。那如果我们在main函数中做这样一个事情。

int main () {
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    pa->_ptrb = pb;
    pb->_ptra = pa;

    cout <<  pa.use_count() << endl;
    cout <<  pb.use_count() << endl;

    return 0;
}

循环引用导致了什么结果

如果执行该程序,我们会发现我们的A、B类竟然没有析构,并且引用计数都是2.

引用计数造成了new出来的资源无法释放!! 这是严重的资源泄漏问题。

我们首先一段一段讲解代码。

首先我们new了两个堆内存 A类 和 B类,A类中有一个指向B类的智能指针_ptrb;B类中有一个指向A的智能指针_ptra。

栈上初始化了两个智能指针 shared_ptr<A> pa(new A()); shared_ptr<B> pa(new B());分别指向堆内存上的A类和B类:

此时我们的智能指针计数分别是 1 1

现在,我们堆上的_ptrb也指向了B类,所以堆上放B类的内存资源计数为2;
同理,_ptra指向了A类,A类的资源计数也为2;

所以我们打印资源计数的时候也是两个2,所以出作用域的时候,pb先析构,然后析构pa,但是他们并不能释放资源,因为2-1 = 1,所以堆上的内存还不能析构。

这就是典型的循环引用问题。

如何解决

定义对象的时候用强智能指针,引用对象的地方使用弱智能指针

class B;
class A {
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    void testA() { cout << "非常好的方法!" << endl; }
    weak_ptr<B> _ptrb;
};

class B {
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    weak_ptr<A> _ptra;
};

此时就能完成正常的打印。A、B对象都析构了,这是为什么呢?
看下图:

堆上的资源都是弱智能指针,由于弱智能指针不会改变资源的引用计数,那也就是说我们_ptrb和_ptra都分别指向了A类和B类,但是由于他们是弱智能指针,他只起一个观察的作用,也就是说观察这个资源还活着没。所以说我们两个资源的引用计数都是 1 和 1,那么等智能指针作用域结束,资源也就能正常释放了。

弱智能指针提升为强智能指针,让其拥有裸指针类似的行为

假如说我们的A类有一个“非常好用的方法”,我们希望能够在B类中利用那个指向A类的成员属性来调用那个“非常好用的方法”。
如下:

class B;
class A {
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    void testA() { cout << "非常好的方法!" << endl; }
    weak_ptr<B> _ptrb;
};
class B {
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    void func () {
        _ptra->testA();
    }
    weak_ptr<A> _ptra;
};

但是很可惜,_ptra->testA()错误,不能这样调用,因为我们的weak_ptr只能观察资源,他不能使用资源,也就是说弱智能指针根本就没有提供operator*和operator->。我们不能把它当一个裸指针来操作。

那应该如何使用呢?

class B {
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    void func () {
        shared_ptr<A> ps = _ptra.lock(); //弱智能指针的提升方法,提升为强智能指针
        if (ps != nullptr) {
            ps->testA();
        }
    }
    weak_ptr<A> _ptra;
};

调用弱智能指针的lock()方法,把它提升为一个强智能指针。再一个,我们需要注意,在多线程编程中,由于weak_ptr只作为一个观察着,所以我们在使用提升方法的过程中,又可能提升失败,因为资源有可能已经释放了,所以我们必须检查调用lock()方法后生成的指针不为空,才说明提升强智能指针成功。这样我们才能在B类中调用A类的那个非常好用的方法。
并且,我们在主函数中使用它。

int main () {
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    pa->_ptrb = pb;
    pb->_ptra = pa;

    cout <<  pa.use_count() << endl;
    cout <<  pa.use_count() << endl;

    pb->func();

    return 0;
}

5.多线程访问共享对象问题

加入有一个类A

class A {
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    void testA() { cout << "非常好的方法!" << endl; }
};

我们在main函数里new了一个对象A,然后启动一个线程,传入线程函数,随后是释放资源A。最后等待线程结束。在子线程中我们调用A类那个非常好用的方法。

void handler01(A *q) {
	q->testA();
}
int main () {
	A *p = new A();
	thread t1(handler01, p);
	std::this_thread::sleep_for(std::chrono::seconds(2));
	delete p;
	t1.join();
	return 0;
}

这个过程是没有任何问题的。


我们在handler01中模拟这样一个问题:我们让子线程睡两秒,不让主线程睡了,也就是说,我们想先delete掉A类这块资源,然后再让子线程来访问这块资源,按道理来说这是不被允许的,然而,子线程还是完成了调用,不符合我们的预期。

void handler01 (weak_ptr<A> pw) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    sp->testA();
}
int main () {
	A *p = new A();
	thread t1(handler01, p);
	delete p;
	t1.join();
	return 0;
}

这非常不合理!因为析构也就意味着我们已经把外部资源释放了,原来资源已经啥都没有了,但是子线程仍然在进行访问。


所以我们希望q在访问A对象的时候,需要侦测一下A对象是否存活,如果存活,我们可以访问,如果已经被析构了,我们就不应该调用该方法。
这就是我们的多线程访问共享对象的安全问题。

给对象添加引用计数使用强弱智能指针监控共享对象

在主函数的初始化中,我们定义一个强智能指针,并且给线程仍一个弱智能指针,然后去掉delete,因为有智能指针帮我们做资源释放,并且我们加一个作用域来模拟资源被析构时,观察子线程还能够访问A对象:

int main () {
    {
        shared_ptr<A> p(new A());

        thread t1(handler01, weak_ptr<A>(p));
        t1.detach();
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std:this_thread::sleep_for(std::chrono::seconds(5));
    return 0;
}

引用的时候用一个弱智能指针,并且在访问A对象的时候,侦测A是否存活

//子线程
void handler01 (weak_ptr<A> pw) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    //q访问A对象的时候,需要侦测一下A对象是否存活
    shared_ptr<A> sp = pw.lock();
    if (sp != nullptr)
        sp->testA();
    else 
        cout << "A对象已经析构,不能再访问!" << endl;
}

最后我们能观察到理想中的结果:

A()
~A()
A对象已经析构,不能再访问!

然后将handler01睡觉的时间改成1,main函数子作用域的睡觉时间改成2,也就是说子线程在调用A对象的时候A还活着。
打印结果如下:

A()
非常好的方法!
~A()

符合预期!

6.自定义删除器

我们都知道智能指针能够保证资源的绝对释放,在之前,释放资源都是用的delete ptr.
那比如说,如果我们用智能指针来管理数组的资源,那么得在中间加一个中括号,又比如说用智能指针来管理一块文件资源或者是其他资源,那么释放这些资源也不是用的delete。

那么就需要思考一个问题了,如何给智能指针来自定义一个删除器来指导智能指针正确得删除资源呢?

库中的unique_ptr和shared_ptr都提供了自定义的删除器,如果看一下他们的源码的话会发现他们的析构函数调用了一个函数对象,通过对函数对象的调用来deletor(ptr)

~unique_ptr() { 函数对象的调用 	deletor(ptr) }

template<typename T>
class default_delete {
public:
	void operator() (T *ptr) {
		delete ptr;
	}
}

自定义数组删除器

如果我们想自定义删除对象的方式,我们直接给他提供一个这样的模板即可,比如说

int main () {
	unique_ptr<int> ptr1(new int[100]); //delete []ptr
	return 0;
}

我们自己写一个删除资源的方法

template<typename T>
class MyDeletor {
public:
	void operator() (T *ptr)const{
		cout << "call MyDeletor.operator()" << endl;
		delete []ptr;
	}
};

//调用
int main () {
	unique_ptr<int, MyDeletor<int>> ptr1(new int[100]); //delete []ptr
	return 0;
}

我们可以很顺利的看到终端打印出 call MyDeletor.operator()。说明用到了我们自己定义的删除器。

自定义文件资源删除器

template<typename T>
class MyFileDeletor {
public:
	void operator() (T *ptr)const{
		cout << "call MyDeletor.operator()" << endl;
		fclose(ptr);
	}
};

//调用
int main () {
	unique_ptr<FILE, MyFileDeletor<FILE>> ptr2(fopen("data.txt", "w"));
    return 0;
}

我们可以很顺利的看到终端打印出 call MyFlieDeletor.operator()。说明用到了我们自己定义的删除器。


但是这样自定义删除器不是特别好,因为我们往往需要定义一个模板类型,然后只使用在智能指针定义的语句当中,其他地方都再也用不到了,这个东西就像我们的临时量一样,它的使用只出现在某一个语句当中,那么有没有什么方法可以让我们直接在语句当中去指定我们自定义的删除器,而不用啰哩啰嗦的去自定义上面的两个模板类出来呢?

没错!!答案就是使用lambda表达式!
然而,定义智能指针的时候需要指定删除器的类型,但是我们只有lambda表达式的对象,那么lambda表达式对象的类型如何确定呢?
没错!!function函数对象!他可以留下我们lambda表达式的类型

使用lambda+function

我们在第一个传入模版类型的时候传入function,初始化列表中的第二个参数中写上lambda表达式

int main () {
	unique_ptr<int, funciton<void (int*)>> ptr1(new int[100], [](int *p)->void{ 
		cout << "call lambda release new int[100]" << endl;
		delete[]p;
	});
	unique_ptr<FILE, funciton<void (FILE*)>> ptr2(fopen("data.txt", "w"), [](FILE *p)->void{ 
		cout << "call lambda release new fopen" << endl;
		fclose(p);
	});

}

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

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

相关文章

Maven 的仓库、周期和插件

优质博文&#xff1a;IT-BLOG-CN 一、Maven 仓库 在Maven的世界中&#xff0c;任何一个依赖、插件或者项目构建的输出&#xff0c;都可以称为构建。Maven在某个统一的位置存储所有项目的共享的构建&#xff0c;这个统一的位置&#xff0c;我们就称之为仓库。任何的构建都有唯一…

信息系统架构模型_2.面向服务架构(SOA)模式

前面讲的客户机/服务器模式&#xff0c;无论多少层的C/S软件结构&#xff0c;对外来讲&#xff0c;都只是一个单结点应用&#xff08;无论它由多个不同层的“服务”相互配合来完成其功能&#xff09;&#xff0c;具体表现为一个门户网站、一个应用系统等。而多个单点应用相互通…

蓝桥杯单片机之模块代码《多样点灯方式》

过往历程 历程1&#xff1a;秒表 历程2&#xff1a;按键显示时钟 历程3&#xff1a;列矩阵按键显示时钟 历程4&#xff1a;行矩阵按键显示时钟 历程5&#xff1a;新DS1302 历程6&#xff1a;小数点精确后两位ds18b20 历程7&#xff1a;35定时器测量频率 历程8&#xff…

docker部署minio和业务服务因变更minio密码导致访问不到图片的问题

问题起因 业务application和minio都是docker部署。按部署规则minio的环境变量中设置了MINIO_ROOT_USER和MINIO_ROOT_PASSWORD。这样就可以用这套用户名密码登录minio了。而我的application中是通过api访问minio获取资源URL&#xff0c;提供给前端的。所以在application的环境变…

SVN 合并到 Git 时有文件大于 100 M 被限制 Push

如果有文件大小大于 100M&#xff0c;GitHub 是会被限制推送到仓库中的&#xff0c;大概率情况会显示下面的错误&#xff1a; remote: Resolving deltas: 100% (3601/3601), done. remote: error: Trace: aea1f450da6f2ef7bfce457c715d0fbb9b0f6d428fdca80233aff34b601ff59b re…

【知识碎片】2024_05_11

本篇记录了两个代码&#xff0c;【图片整理】是一个数组排序题&#xff0c;【寻找数组的中心下标】看起来很适合用双指针&#xff0c;但是细节多&#xff0c;最后还是没通过全部用例&#xff0c;看了题解写出来的。 C语言部分是两个知道错了之后恍然大悟的选择题。 每日代码 图…

iOS Xcode Debug View Hierarchy 查看视图层级结构

前言 我们难免会遇到接手别人项目的情况&#xff0c;让你去改他遗留的问题&#xff0c;想想都头大&#xff0c;&#x1f602;可是也不得不面对。作为开发者只要让我们找到出问题的代码文件&#xff0c;我们就总有办法去解决它&#xff0c;那么如何快速定位问题对应的代码文件呢…

原创未发表!24年新算法SBOA优化TVFEMD实现分解+四种熵值+频谱图+参数变化图+相关系数图!

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 数据介绍 优化流程 创新点 使用TVFEMD的创…

哪里有高清视频素材库?哪里有视频素材免费提供?

随着数字媒体和内容创作的不断演进&#xff0c;高效且创意的视频素材变得日益重要。以下列举的视频素材网站&#xff0c;无论是国内还是国际&#xff0c;都能为您的项目提供宝贵的资源。 1. 蛙学府 不断扩展其视频库&#xff0c;提供从经典剪辑到现代影视制作所需的素材。除了…

01、什么是ip、协议、端口号知道吗?计算机网络通信的组成是什么?

声明&#xff1a;本教程不收取任何费用&#xff0c;欢迎转载&#xff0c;尊重作者劳动成果&#xff0c;不得用于商业用途&#xff0c;侵权必究&#xff01;&#xff01;&#xff01; 目录 前言 计算机网络 网络ip地址 网络协议 网络端口号 前言 最近有个项目要用到相关文章…

【回溯 网格 状态压缩】52. N 皇后 II

本文涉及知识点 回溯 网格 状态压缩 LeetCode52. N 皇后 II n 皇后问题 研究的是如何将 n 个皇后放置在 n n 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回 n 皇后问题 不同的解决方案的数量。 示例 1&#xff1a; 输入&#x…

Mysql数据库的基础学习

为什么使用数据库&#xff1f; 1.持久化&#xff1a;将数据保存到可掉电式存储设备中以供使用。 数据库相关概念&#xff1a; DB:数据库&#xff08;Databass&#xff09;即存储数据的仓库&#xff0c;本质是一个文件系统&#xff0c;保存了一系列有组织的数据DBMS:数据库管…

RustGUI学习(iced)之小部件(十二):如何使用rule分割线部件来分割UI?

前言 本专栏是学习Rust的GUI库iced的合集,将介绍iced涉及的各个小部件分别介绍,最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个,目前处于发展中(即版本可能会改变),本专栏基于版本0.12.1. 概述 这是本专栏的第十二篇,主要讲述rule分割线部件的使用,会结合…

ShellCode详解一

首先&#xff0c;感谢imbyter的教程&#xff0c;我也是从他的教程中一步一步的了解了shellcode的原理和各种知识。 原理 shellcode仅是一段可执行代码&#xff0c;不需要入口函数。理解shellcode加载原理之前需要理解PE文件在系统中的执行原理&#xff0c;即代码在内存中的执…

数据结构与算法学习笔记三---循环队列的表示和实现(C语言)

目录 前言 1.为啥要使用循环队列 2.队列的顺序表示和实现 1.定义 2.初始化 3.销毁 4.清空 5.空队列 6.队列长度 7.获取队头 8.入队 9.出队 10.遍历队列 11.完整代码 前言 本篇博客介绍栈和队列的表示和实现。 1.为啥要使用循环队列 上篇文章中我们知道了顺序队列…

docker安装时报错:Error: Nothing to do

安装docker时报以下错误 解决方法&#xff1a; 1.下载关于docker的相关依赖环境 yum -y install yum-utils device-mapper-persistent-data lvm22.设置下载Docker的镜像源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo3…

分层存储无法拯救 Kafka

01 引言 Apache Kafka 自诞生之日起&#xff0c;就以其卓越的设计和强大的功能&#xff0c;成为了流处理领域的标杆。它不仅定义了现代流处理架构&#xff0c;更以其独特的分布式日志抽象&#xff0c;为实时数据流的处理和分析提供了前所未有的能力。Kafka 的成功&#xff0…

Docker搭建ctfd平台

安装docker和docker-compose &#xff08;1&#xff09;安装docker&#xff1a; curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun&#xff08;2&#xff09;安装 Docker Compose&#xff1a; yum install docker-compose安装失败参考下面文章 https:/…

【Linux】环境变量是什么?如何配置?详解

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Electron学习笔记(四)

文章目录 相关笔记笔记说明 六、数据1、使用本地文件持久化数据(1) 用户数据目录(2) 读写本地文件(3) 第三方库 2、读写受限访问的 Cookie3、清空浏览器缓存 相关笔记 Electron学习笔记&#xff08;一&#xff09;Electron学习笔记&#xff08;二&#xff09;Electron学习笔记…