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

文章目录

  • 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);
	});

}

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

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

相关文章

LeetCode---396周赛

题目列表 3136. 有效单词 3137. K 周期字符串需要的最少操作次数 3138. 同位字符串连接的最小长度 3139. 使数组中所有元素相等的最小开销 一、有效单词 按照题目要求&#xff0c;统计个数&#xff0c;看是否符合条件即可&#xff0c;代码如下 class Solution { public:b…

Java - Json字符串转List<LinkedHashMap<String,String>>

需求&#xff1a;在处理数据时&#xff0c;需要将一个Object类型对象集合转为有序的Map类型集合。 一、问题 1.原代码&#xff1a; 但在使用时出现报错&#xff1a; Incompatible equality constraint: LinkedHashMap<String, String> and LinkedHashMap 不兼容的相等…

初识sql注入--手工注入

目录 可能使用的sql函数 入侵网站方式 1、文件上传漏洞 2、rce 3、sql注入 SQL注入 什么是sql注入 进行SQL注入 实验环境 开始实验&#xff08;使用information_shema数据库&#xff09; 1、进入靶场 2、报列数 下面来解释一下为什么要照上面SQL语句写 url编码 单…

Linux-vi、vim

使用Xshell远程登录到Linux主机进行操作 命令行不用全部掌握&#xff0c; 一般编辑大文件&#xff0c;比较复杂的情况下&#xff0c; 我们还是使用Xftp工具&#xff0c; down下来再恢复回去。

有边数限制的最短路

文章目录 题目 有边数限制的最短路算法分析1、问题&#xff1a;为什么Dijkstra不能使用在含负权的图中&#xff1f;dijkstra详细步骤2、什么是bellman - ford算法&#xff1f;3、bellman - ford算法的具体步骤4、在下面代码中&#xff0c;是否能到达n号点的判断中需要进行if(di…

Seaborn : 超好用的Python可视化工具

1. 引言 说到数据可视化&#xff0c;Seaborn就像一颗隐藏的宝石&#xff01;在进行探索性数据分析时&#xff0c;我们通常从Matplotlib 开始&#xff0c;而对 Seaborn 的探索相对较少&#xff01;但是&#xff0c;只要你了解 Seaborn 的全部潜力&#xff0c;你就会惊奇地发现&…

安全工程师面试题

安全工程师面试题安全工程师是一个非常重要的职位&#xff0c;他们负责保护公司的网络和系统免受黑客和恶意软件的攻击。如果你想成为一名安全工程师&#xff0c;那么你需要准备好面试。下面是一… 1安全工程师面试题 安全工程师是一个非常重要的职位&#xff0c;他们负责保护…

C++Linux系统编程——makefile

Makefile Makefile简介 一个工程中的源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件需要后编译&#xff0c;哪些文件需要重新编译&#xff0c;甚至于…

基于Django实现的校园疫情监控平台

基于Django实现的校园疫情监控平台 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat、Maven 系统功能实现 登录注册功能 用户在没有登录自己的用户名之前只能浏览本网站的首页&#xff0c;想要使用其他功能都会…

sqli-labs靶场第十四关

目录 1&#xff1a;分析 找闭合符&#xff1a; 2&#xff1a;开始注入 报错注入&#xff1a; 注入数据库名&#xff1a; 注入表名&#xff1a; 注入列名&#xff1a; 注入具体值&#xff1a; 1&#xff1a;分析 经过我们的实验发现当我们输入的密码后面存在双引号时会报…

构建内网yum仓库

1、环境介绍 系统&#xff1a;龙蜥os 7.9 2、安装epel源 yum install epel-release -y3、安装nginx服务器并启动 yum install nginx httpd -y配置 server {listen 80;server_name repo.wtown.com;root /usr/share/nginx/html/repo;index index.html index.htm;location / {…

阿里云ECS服务器实例挂载数据盘步骤(磁盘自动挂载.、访问挂载点)

阿里云ECS服务器实例挂载数据盘步骤 相关指令 df -h 查看磁盘空间 du -sh * 查看使用内存大小1.磁盘自动挂载 首先登录阿里云ECS服务器&#xff0c;通过 df -h 命令查看当前磁盘挂载情况 通过 fdisk -l 命令查看磁盘情况&#xff0c;可以发现有两个盘&#xff1a; 系统盘 …

Ubuntu 和 Windows之间无法复制粘贴问题解决方法

需要安装open-vm-tools&#xff0c;官方安装open-vm-tools的网址&#xff1a;安装 Open VM Tools (vmware.com)

安全测试工具Nessus安装和使用

安装 下载地址&#xff1a;https://pan.baidu.com/s/1OaYMDdQqYI4BbZ_uUErrTw 提取码: yg2g 安装Nessus-8.14.0-x64.msi&#xff0c;按照提示next至安装完成&#xff0c;显示如下页面&#xff0c;点击Connect via SSL 点击“高级”、“继续访问” 选择 【Managed Scanner】选…

Visual Studio,第1个hello world,入门C++,分别编译一个可以在Windows和Linux下运行的程序

本人的VxTerm&#xff0c;是在Visual Studio 2022下编写的。 其它的语言工具是不是也可以那么方便的使用&#xff0c;本人并不得而知&#xff0c;至少本人能知道&#xff1a;对于我来说&#xff0c;Visual Studio可以让我觉得C/C语言非常简单&#xff01; 一、安装Visual Stu…

stm32——OLED篇

技术笔记&#xff01; 一、OLED显示屏介绍&#xff08;了解&#xff09; 1. OLED显示屏简介 二、OLED驱动原理&#xff08;熟悉&#xff09; 1. 驱动OLED驱动芯片的步骤 2. SSD1306工作时序 三、OLED驱动芯片简介&#xff08;掌握&#xff09; 1. 常用SSD1306指令 2. …

apache atlas 如何自定义hook

atals 是开源的数据元数据和数据资产管理平台&#xff0c;平台设计支持强大的图数数据库&#xff0c;nosql&#xff0c;和搜索引擎3个组件构建。都是基于开源构建。 目前市场上开源的元数据管理工具有Atlas&#xff0c; Datahub&#xff0c; Openmetadata等&#xff0c;你要说二…

进程间通信:连接不同程序世界的桥梁

目录 一、进程间通信的重要性 二、常见的进程间通信方式 三、进程间通信的目的 四、进程间通信的本质 在计算机编程的领域中&#xff0c;进程间通信&#xff08;Inter-Process Communication&#xff0c;IPC&#xff09;是一个至关重要的概念。当我们在操作系统中运行多个程…

Vue中进行粘贴板粘贴数据(图片、文字等)

在页面中如果需要进行粘贴数据&#xff0c;那么就要读取系统粘贴板clipboard&#xff0c;通过此Api来进行粘贴板数据的操作。 目录: 一.封装相关函数1.示例代码&#xff1a;2.代码解释&#xff1a; 二.页面中进行粘贴1.代码示例&#xff1a;2.代码解释&#xff1a; 三.运行结果…

linux day 3

touch 创建文件命令 cat命令&#xff0c;查看文件内容 more命令&#xff0c;查看文件内容。 cat是直接全部显示出来&#xff0c;more是支持翻页&#xff0c;即文件内容过多可以一页一页显示&#xff08;按空格翻页&#xff0c;按Q进行退出&#xff09; cp命令&#xff0c;复制…