体验一下智能指针的强大

news2024/9/27 17:32:46

课程总目录


文章目录

  • 一、智能指针基础知识
  • 二、不带引用计数的智能指针
    • auto_ptr
    • scoped_ptr
    • unique_ptr
  • 三、带引用计数的智能指针
    • 模拟实现一个带引用计数的智能指针
    • shared_ptr交叉引用问题
  • 四、多线程访问共享对象的线程安全问题
  • 五、智能指针删除器
  • 六、建议用make_shared代替shared_ptr


一、智能指针基础知识

裸指针的缺陷:

  1. 忘记delete释放资源,导致资源泄露
  2. delete之前程序就正常退出(比如满足if中的return)或者异常退出,还没来得及delete,导致资源泄露
  3. 同一资源释放多次,导致释放野指针,程序崩溃

这时,就需要智能指针了。智能指针的智能二字,主要体现在用户可以不关注资源的释放,因为智能指针会帮你完全管理资源的释放,它会保证无论程序逻辑怎么跑,正常执行或者产生异常,资源在到期的情况下(函数作用域或程序结束),一定会进行释放

我们来自己实现一个简单的智能指针

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
	~CSmartPtr() { delete mptr; }
private:
	T* mptr;
};

int main()
{
	CSmartPtr<int> ptr(new int());
	/*其它的代码...*/

	/*
	由于ptr是栈上的智能指针对象,不管是函数正常执行完,
	还是运行过程中出现异常,栈上的对象都会自动调用析构函数,
	在析构函数中进行了delete操作,保证释放资源
	*/

	return 0;
}
  1. 智能指针体现在把裸指针进行了一次面向对象的封装,在构造函数中初始化资源地址,在析构函数中负责释放资源
  2. 利用栈上的对象出作用域自动析构这个特点,在智能指针的析构函数中保证释放资源

所以,由于以上的特点,智能指针一般都是定义在栈上的。同时,智能指针是一个类对象,这个类的构造函数中传入了一个指针,析构函数中释放传入的指针。由于该类对象是在栈上开辟和释放的,所以,当我们的函数(或程序)结束时就会被自动释放

那么,能不能在堆上定义智能指针呢?比如CSmartPtr* p = new CSmartPtr(new int);,编译是可以通过的,但是这里定义的p虽然是智能指针类型,但它实质上还是一个裸指针,因此p还是需要进行手动delete,又回到了最开始裸指针我们面临的问题,所以不要这样用

当然,智能指针要做到和裸指针相似,还要提供裸指针常见的*->两种运算符的重载函数,使用起来才真正的和裸指针一样,代码如下:

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
	~CSmartPtr() { delete mptr; }

	T& operator*() { return *mptr; }
	T* operator->() { return mptr; }

private:
	T* mptr;
};


int main()
{
	CSmartPtr<int> ptr(new int());

	*ptr = 20;	// operator*()一定要返回引用,这样才可以赋值

	cout << *ptr << endl;	// 20

	class Test
	{
	public:
		void test() { cout << "call Test::test()" << endl; }
	};

	CSmartPtr<Test> ptr2 = new Test();

	(*ptr2).test();	// (*ptr2)取出Test对象,用对象调用方法

	// operator->()返回的是一个指针,实现了用指针调用函数
	// 即(ptr2.operator->()) -> test()
	ptr2->test();

	return 0;
}

二、不带引用计数的智能指针

上一节实现的智能指针,使用起来和普通的裸指针非常相似,但是它还存在很大的问题,看下面的代码:

CSmartPtr<int> p1(new int());
CSmartPtr<int> p2(p1);	// 拷贝构造

运行代码直接崩溃,因为默认的拷贝构造函数做的是浅拷贝,p1p2持有的是同一个new int资源,p2先析构释放了资源,到p1析构的时候,就成了delete野指针了,程序崩溃

那么,怎么解决浅拷贝带来的问题呢?重写一下CSmartPtr的拷贝构造看看

CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }

现在代码正常运行,但是,此时p1p2管理的是2个不同的资源,用户如果不了解,会误以为p1p2管理的是同一块资源

所以,我们这样写拷贝构造是不对的

那么,怎么解决智能指针的浅拷贝问题?两种方法:不带引用计数的智能指针、带引用计数的智能指针

这一节我们先来看不带引用计数的智能指针

  1. auto_ptr(C++98,现已废弃)
  2. scoped_ptr(Boost库)
  3. unique_ptr(C++11,推荐使用)

使用的时候包含头文件:#include <memory>

auto_ptr

先来考虑这段代码

auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);

*ptr2 = 20;
cout << *ptr1 << endl;

运行崩溃,为什么呢,来看一下auto_ptr的源码

在这里插入图片描述

可以看到拷贝构造会调用传入对象的release方法,这个方法也就是把_Right指向的资源返回给新auto_ptr对象,同时把_Right(旧auto_ptr)的_Myptr置为nullptr

简而言之,就是把旧auto_ptr指向的资源给新auto_ptr持有,旧的置为nullptr。因此,上面那段代码使用*ptr1就是错误的。auto_ptr永远让最后一个智能指针管理资源)

那么,auto_ptr能不能使用在容器当中,看下面这段代码

int main()
{
	vector<auto_ptr<int>> vec;
	vec.push_back(auto_ptr<int>(new int(10)));
	vec.push_back(auto_ptr<int>(new int(20)));
	vec.push_back(auto_ptr<int>(new int(30)));
	
	cout << *vec[0] << endl;	// 10
	
	vector<auto_ptr<int>> vec2 = vec;
	/* 这里由于上面做了vector容器的拷贝,
	相当于容器中的每一个元素都进行了拷贝构造,
	原来vec中的智能指针全部为nullptr了,
	再次访问就成访问空指针了,程序崩溃
	*/
	
	cout << *vec[0] << endl;	// 程序崩溃
	
	return 0;
}

因此,在C++中不推荐使用auto_ptr,除非应用场景非常简单。且auto_ptr在C++11中被废弃,并在C++17中被完全移除

总结:auto_ptr智能指针不带引用计数,那么它处理浅拷贝的问题,是直接把前面的auto_ptr都置为nullptr,只让最后一个auto_ptr持有资源

scoped_ptr

需要安装Boost库,安装好了包含头文件#include <boost/scoped_ptr.hpp>即可使用

看一下scoped_ptr的源码:

template<class T> class scoped_ptr
{
private:
	scoped_ptr(scoped_ptr const&);
	scoped_ptr& operator=(scoped_ptr const&);
...
};

可以看到scoped_ptr的拷贝构造和赋值函数都私有化了,这样对象就不支持这两种操作,无法调用,从根本上杜绝了浅拷贝的发生

所以scoped_ptr也是不能用在容器当中的,如果容器互相进行拷贝或者赋值,就会引起scoped_ptr对象的拷贝构造和赋值函数,编译错误

scoped_ptrauto_ptr的区别:可以用所有权来解释,auto_ptr可以任意转移资源的所有权,而scoped_ptr不会转移所有权(因为拷贝构造和赋值函数被禁止了)

scoped_ptr一般也不使用

unique_ptr

先看看unique_ptr的部分源码:

template<class _Ty, class _Dx>
class unique_ptr
{
public:
	/*提供了右值引用的拷贝构造函数*/
	unique_ptr(unique_ptr&& _Right) { ... }
	
	/*提供了右值引用的operator=赋值重载函数*/
	unique_ptr& operator=(unique_ptr&& _Right) { ... }

	/*
	删除了unique_ptr的拷贝构造和赋值函数,
	因此不能做unique_ptr智能指针对象的拷贝构造和赋值,
	防止浅拷贝的发生
	*/
	unique_ptr(const unique_ptr&) = delete;
	unique_ptr& operator=(const unique_ptr&) = delete;
};

从上面看到,unique_ptr有一点和scoped_ptr做的一样,就是禁用了拷贝构造和赋值重载函数,禁止用户对unique_ptr进行显式的拷贝构造和赋值,防止智能指针浅拷贝问题的发生。

由于没有拷贝构造函数了,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);自然就是错误的

但是unique_ptr提供了带右值引用参数的拷贝构造和赋值函数,也就是说,unique_ptr智能指针可以通过右值引用进行拷贝构造和赋值操作,或者在产生unique_ptr临时对象的地方,例如把unique_ptr作为函数的返回值,示例代码如下:

// 示例1
unique_ptr<int> p1(new int());
unique_ptr<int> p2(move(p1));	// 使用了右值引用的拷贝构造
p2 = move(p1);	// 使用了右值引用的operator=赋值重载函数
// 示例2
unique_ptr<int> test_uniqueptr()
{
	unique_ptr<int> ptr(new int());
	return ptr;
}
int main()
{
	/*
	此处调用test_uniqueptr函数,在return ptr代码处,
	调用右值引用的拷贝构造和赋值函数
	*/
	unique_ptr<int> p = test_uniqueptr();	// 调用带右值引用的拷贝构造函数
	p = test_uniqueptr();	// 调用带右值引用的operator=赋值重载函数
	return 0;
}

那么使用unique_ptr的好处也就是用户通过类似unique_ptr<int> p2(move(p1));的语句,可以明显的看出p1把资源转移给了p2p1不持有资源了,如果用auto_ptr的话不会显式的把move写出来,用意不明显,不了解底层的话会用错

同时,从unique_ptr的名字就可以看出来,其最终也是只能有一个该智能指针引用资源,因此建议在使用不带引用计数的智能指针时,优先选择unique_ptr

三、带引用计数的智能指针

带引用计数的智能指针主要包括shared_ptrweak_ptr,带引用计数的好处就是多个智能指针可以管理同一个资源,那么什么是带引用计数的智能指针呢?

带引用计数:给每一个对象的资源,匹配一个引用计数

当允许多个智能指针指向同一个资源的时候,每一个智能指针都会给资源的引用计数加1,当一个智能指针析构时,同样会使资源的引用计数减1,这样最后一个智能指针把资源的引用计数从1减到0时,就说明该资源可以释放了,由最后一个智能指针的析构函数来处理资源的释放问题,这就是引用计数的概念。

  • 当引用计数减1不为0时,当前智能指针不使用这个资源了,但是还有其他智能指针在使用这个资源,当前智能指针不能析构这个资源,只能直接走
  • 当引用计数减1为0时,说明当前智能指针是最后一个使用这个资源的智能指针,所以它要负责这个资源的释放

模拟实现一个带引用计数的智能指针

拿前面实现的CSmartPtr进行修改,直接上代码:

// 对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:
	RefCnt(T* ptr = nullptr) : mptr(ptr)
	{
		if (mptr != nullptr)
			mcount = 1;
	}
	void addRef() { mcount++; }	// 增加资源的引用计数
	int delRef() { return --mcount; }

private:
	T* mptr;
	int mcount;
};

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr)
	{
		// 智能指针构造的时候给资源建立引用计数对象
		mpRefCnt = new RefCnt<T>(mptr);
	}
	~CSmartPtr()
	{
		if (0 == mpRefCnt->delRef())
		{
			delete mptr;
			mptr = nullptr;
		}
	}

	// 实现拷贝构造
	CSmartPtr(const CSmartPtr<T>& src)
		:mptr(src.mptr), mpRefCnt(src.mpRefCnt)
	{
		if (mptr != nullptr)
			mpRefCnt->addRef();
	}
	CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
	{
		// 防止自赋值
		if (this == &src)
			return *this;

		// 本身指向的资源减1
		// 如果减1为0释放资源;如果减1不为0直接走
		if (0 == mpRefCnt->delRef()) { delete mptr; }

		mptr = src.mptr;
		mpRefCnt = src.mpRefCnt;
		mpRefCnt->addRef();
		return *this;
	}

	T& operator*() { return *mptr; }
	T* operator->() { return mptr; }

private:
	T* mptr;	// 指向资源的指针
	RefCnt<T>* mpRefCnt;	// 指向该资源引用计数对象的指针
};
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;

*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl;	// 20 20

这就实现了多个智能指针管理同一个资源

但是我们现在实现的智能指针不是线程安全的,不能使用在多线程场景下。库里实现的shared_ptrweak_ptr是线程安全的!

shared_ptr交叉引用问题

  • shared_ptr智能指针(可以改变资源的引用计数)
  • weak_ptr智能指针(不会改变资源的引用计数)

我们上一节实现的CSmartPtr也是强智能指针(可以改变资源的引用计数)

可以这样理解:弱智能指针 观察 强智能指针,强智能指针 观察 资源(内存)

那么,强智能指针的交叉引用(循环引用)问题是什么呢?来看一下

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	shared_ptr<B> _ptrb;	// 指向B对象的智能指针
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	shared_ptr<A> _ptra;	// 指向A对象的智能指针
};

int main()
{
	shared_ptr<A> pa(new A());	// pa指向A对象,A的引用计数为1
	shared_ptr<B> pb(new B());	// pb指向B对象,B的引用计数为1
	pa->_ptrb = pb;	// A对象的成员变量_ptrb也指向B对象,B的引用计数为2
	pb->_ptra = pa;	// B对象的成员变量_ptra也指向A对象,A的引用计数为2

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

	return 0;
}

运行结果:

A()
B()
2
2

可以看到,A和B没有析构,也就是这样交叉引用会造成new出来的资源无法释放,导致资源泄露!

分析:
在这里插入图片描述在这里插入图片描述
main函数作用域,papb两个局部对象析构,分别给A对象和B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是A和B的引用计数减为0),因此造成两个new出来的A对象和B对象无法释放,导致内存泄露,这个问题就是强智能指针的交叉引用(循环引用)问题

解决办法:定义对象的时候用强智能指针,引用对象的时候用弱智能指针

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	weak_ptr<B> _ptrb;	// 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	weak_ptr<A> _ptra;	// 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};


int main()
{
	// 定义对象时,用强智能指针
	shared_ptr<A> pa(new A());	// pa指向A对象,A的引用计数为1
	shared_ptr<B> pb(new B());	// pb指向B对象,B的引用计数为1

	// A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变
	pa->_ptrb = pb;
	// B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变
	pb->_ptra = pa;

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

	return 0;
}

运行结果:

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

可以看到,出main函数作用域,papb两个局部对象析构,分别给A对象和B对象的引用计数从1减到0,达到释放A和B的条件,因此new出来的A对象和B对象被析构掉,解决了强智能指针的交叉引用(循环引用)问题

在这里插入图片描述
可以看到,weak_ptr弱智能指针不会改变资源的引用计数,也就是说弱智能指针只是观察对象是否活着(引用计数是否为0),也无法使用资源

那么此时如果在A类中增加void testA() { cout << "非常好用的方法!!" << endl; }这样一个方法,在B中增加void func() { _ptra->testA(); }这样的方法去调用,可以吗?

  • 不可以,因为弱智能指针只是一个观察者,不能去使用资源,也即没有提供*->运算符的重载函数,不能使用类似裸指针的功能

解决办法:

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	void testA() { cout << "非常好用的方法!!" << endl; }
	weak_ptr<B> _ptrb;	// 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	void func()
	{
		shared_ptr<A> sp = _ptra.lock(); // 提升方法,出函数作用域就自动析构了
		if (sp != nullptr)
			sp->testA();
	}
	weak_ptr<A> _ptra;	// 指向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; // 1
	cout << pb.use_count() << endl; // 1

	pb->func();

	return 0;
}

虽然 weak_ptr不拥有对象,但它可以通过lock()方法尝试获取一个指向对象的shared_ptr,如果对象仍然存活(即还有其他shared_ptr指向它),lock()将返回一个指向该对象的shared_ptr;否则,将返回一个空的shared_ptr

使用lock()方法的一个典型场景是在需要临时访问weak_ptr所指向的对象时,同时又不希望增加该对象的生命周期计数

运行结果:

A()
B()
1
1
非常好用的方法!!
~B()
~A()

此时可以看到,正确调用了!

四、多线程访问共享对象的线程安全问题

我们来看一个多线程访问共享对象的线程安全问题:线程A和线程B访问一个共享的对象,如果线程A正在析构这个对象的时候,线程B又要调用该共享对象的成员方法,此时可能线程A已经把对象析构完了,线程B再去访问该对象,就会发生不可预期的错误

先看如下代码:

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

// 子线程
void handler01(A* q)
{
	// 睡眠两秒,此时main主线程已经把A对象给delete析构掉了
	std::this_thread::sleep_for(std::chrono::seconds(2));
	q->testA();
}

// main线程
int main()
{
	A* p = new A();
	thread t1(handler01, p);
	delete p;
	//阻塞当前线程,直到调用join()的线程结束
	t1.join();
	return 0;
}

在执行q->testA();这条语句的时候,访问了main线程已经析构的共享对象,这明显是不合理的

如果想通过q指针想访问A对象,需要判断A对象是否存活,如果A对象存活,调用testA方法没有问题;如果A对象已经析构,调用testA有问题!也就是说q访问A对象的时候,需要侦测一下A对象是否存活,怎么解决呢,需要用强弱智能指针

来看这段代码:

// 子线程
void handler01(weak_ptr<A> wp)
{
	// 睡眠两秒
	std::this_thread::sleep_for(std::chrono::seconds(2));
	shared_ptr<A> sp = wp.lock();
	if (sp != nullptr)
		sp->testA();
	else
		cout << "A对象已经析构,不能再访问!" << endl;
}

// main线程
int main()
{
	shared_ptr<A> p(new A());
	thread t1(handler01, weak_ptr<A>(p));
	t1.join();
	return 0;
}

运行结果:

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

可以看到运行了sp->testA();,因为main线程调用了t1.join()方法等待子线程结束,此时wp通过lock成功提升为sp

修改一下上面的代码:

// 子线程
void handler01(weak_ptr<A> wp)
{
	// 睡眠两秒
	std::this_thread::sleep_for(std::chrono::seconds(2));
	shared_ptr<A> sp = wp.lock();
	if (sp != nullptr)
		sp->testA();
	else
		cout << "A对象已经析构,不能再访问!" << endl;
}

// main线程
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(3));
	return 0;
}

运行结果:

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

可以看到,我们设置了一个作用域,同时设置t1为分离线程,让智能指针p出作用域把A析构,那么此时就会打印A对象已经析构,不能再访问!,也就是wp通过lock没能成功提升为sp

以上,就是在多线程访问共享对象的线程安全问题,这是对shared_ptrweak_ptr的一个典型应用。

五、智能指针删除器

六、建议用make_shared代替shared_ptr


参考文章:深入掌握C++智能指针

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

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

相关文章

如何找工作 校招 | 社招 | 秋招 | 春招 | 提前批

马上又秋招了&#xff0c;作者想起以前读书的时候&#xff0c;秋招踩了很多坑&#xff0c;但是第一份工作其实挺重要的。这里写一篇文章&#xff0c;分享一些校招社招的心得。 现在大学的情况是&#xff0c;管就业的人&#xff0c;大都是没有就业的辅导员&#xff08;笔者见过…

JavaWeb(一:基础知识和环境搭建)

一、基本概念 1.前言 JavaWeb&#xff1a;在Java中&#xff0c;动态web资源开发网页的技术。 web应用分两种&#xff1a;静态web资源和动态web资源 Ⅰ. 静态web资源&#xff08;如html 页面&#xff09;&#xff1a; 指web页面中的数据始终是不变。 所有用户看到都是同一个…

基于YOLOV8的数粒机-农业应用辣椒种子计数计重双标质量解决方案

一:辣椒种子行业背景调查 中国辣椒年产量稳居世界第一,食辣人口超5亿。中国辣椒全球闻名,小辣椒长成大产业,带动全球食品行业腾飞。 在中国,“辣”是不少地方餐桌上的一大特色。从四川的麻辣火锅到湖南的剁椒鱼头再到陕西的油泼辣子面,由南到北,总有食客对辣有着独一份偏…

力扣-回溯法

何为回溯法&#xff1f; 在搜索到某一节点的时候&#xff0c;如果我们发现目前的节点&#xff08;及其子节点&#xff09;并不是需求目标时&#xff0c;我们回退到原来的节点继续搜索&#xff0c;并且把在目前节点修改的状态还原。 记住两个小诀窍&#xff0c;一是按引用传状态…

连接与隔离:Facebook在全球化背景下的影响力

在当今全球化的背景下&#xff0c;Facebook作为全球最大的社交网络平台&#xff0c;不仅连接了世界各地的人们&#xff0c;还在全球社会、经济和文化中发挥着深远的影响。本文将深入探讨Facebook在全球化进程中的作用&#xff0c;以及其对个体和社会之间连接与隔离的双重影响。…

【区块链农场】:农场游戏+游戏

我的酒坊是一款非常受玩家欢迎的经营手游,游戏中你需要合理经营一家酒厂,将其做大做强。通过制定合理的战略,例如新建厂房,并采用传统工艺制作,针对不同的人群研制多重口味。

Ubuntu与Windows通过WIFI与以太网口共享网络,Ubuntu与Windows相互ping通,但ping百度失败

Linux开发板&#xff08;正点原子阿尔法_IMX6U&#xff09;与Ubuntu的文件传输SCP 报错 SSH: no matching host key type found. Their offer: ssh-rsa-CSDN博客 前面的文章提到了如何将Ubuntu与Windows通过WIFI共享网络给以太网&#xff0c;从而实现Linux开发板、Ubuntu、Win…

Umi.js 项目中使用 Web Worker

1.配置 Umi.js 在 Umi.js 中&#xff0c;需要通过配置来扩展 Webpack 的功能。在项目根目录下修改 config/config.ts 文件&#xff1a; export default defineConfig({chainWebpack(config) {config.module.rule(worker).test(/\.worker\.ts$/).use(worker-loader).loader(wo…

常见网页问题解决

用edge浏览器打印功能时&#xff0c;出现瞬间或加载几秒后突然闪退情况&#xff0c;本来以为是浏览器出了问题&#xff0c;去重置设置也没有&#xff0c;后来又下载了Chrome浏览器&#xff0c;没想到还是一样&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;想着…

系统服务综合实验

实验需求&#xff1a; 现有主机 node01 和 node02&#xff0c;完成如下需求&#xff1a; 在 node01 主机上提供 DNS 和 WEB 服务dns 服务提供本实验所有主机名解析web服务提供 www.rhce.com 虚拟主机该虚拟主机的documentroot目录在 /nfs/rhce 目录该目录由 node02 主机提供的…

判断链表中是否有环(力扣141.环形链表)

这道题要用到快慢指针。 先解释一下什么是快慢指针。 快慢指针有两个指针&#xff0c;走得慢的是慢指针&#xff0c;走得快的是快指针。 在这道题&#xff0c;我们规定慢指针一次走一步&#xff0c;快指针一次走2步。 如果该链表有环&#xff0c;快慢指针最终会在环中相遇&a…

MySQL数据库基本操作-DDL和DML

1. DDL解释 DDL(Data Definition Language)&#xff0c;数据定义语言&#xff0c;该语言部分包括以下内容&#xff1a; 对数据库的常用操作对表结构的常用操作修改表结构 2. 对数据库的常用操作 功能SQL查看所有的数据库show databases&#xff1b;查看有印象的数据库show d…

02. 存储引擎

1. 前言 在校招或者社招面试中&#xff0c;无论你是 Java 后端、Cpp 后端、Python 后端&#xff0c;面试官都会详细地考察各种语法细节、框架知识&#xff0c;但是大多数候选人入职之后&#xff0c;都会体会到 "面试造火箭&#xff0c;上班拧螺丝"。面试时我们熟悉各…

Python | Leetcode Python题解之第229题多数元素II

题目&#xff1a; 题解&#xff1a; class Solution:def majorityElement(self, nums: List[int]) -> List[int]:cnt {}ans []for v in nums:if v in cnt:cnt[v] 1else:cnt[v] 1for item in cnt.keys():if cnt[item] > len(nums)//3:ans.append(item)return ans

C#绘制阻抗圆图初步

阻抗圆图&#xff0c;或者叫史密斯图&#xff0c;是无线电设计方面用的&#xff1b; 基本的阻抗圆图如下&#xff0c; 下面尝试用C#能不能画一下&#xff1b; 先在网上找一个画坐标的C#类&#xff0c;它的效果如下&#xff1b; 自己再增加一个函数&#xff0c;可以绘制中心在…

【JSP+Servlet+Maven】——优质外卖订餐系统之概论部分

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

PPTP、L2TP、IPSec、IPS 有什么区别?

随着互联网的发展&#xff0c;保护网络通信的安全越来越重要。PPTP、L2TP、IPSec、IPS是常见的网络安全协议和技术&#xff0c;在保护网络通信安全方面发挥着不同的作用和特点。下面介绍PPTP、L2TP、IPSec、IPS之间的区别。 点对点隧道协议&#xff08;PPTP&#xff09;是一种用…

使用大模型进行SQL迁移的实践总结

在现代化的项目管理和运维工作中&#xff0c;利用大模型&#xff08;如ChatGPT&#xff09;处理复杂任务已成为一种高效手段。近期我们在一个项目中尝试利用大模型将MySQL导出的SQL语句迁移为达梦信创数据库格式&#xff0c;通过几轮操作&#xff0c;我们深刻体会到提示词工程的…

C# 串口数据转网口实现空气风速风向检测

1.窗体搭建 添加time(定时器) 因为需要风速和风向自动刷新 2.进行网口空气检测 ①服务器连接按钮 // 连接按钮private void button1_Click(object sender, EventArgs e){if (button1.Text "连接"){ConnectSocke();// 连接服务器}else{CloseSocket(); // 关闭服务器…

基于html开发的在线网址导航在线工具箱源码

基于html开发的在线网址导航在线工具箱源码&#xff0c;将全部文件复制到服务器&#xff0c;入口文件是index.html 如需修改网址&#xff0c;可修改index.html 如需修改关于页面&#xff0c;可修改about里面的index页面 源码下载&#xff1a;https://download.csdn.net/down…