【C++】指针与智慧的邂逅:C++内存管理的诗意

news2024/12/13 4:12:32

文章目录

  • RAII
  • 智能指针
    • `auto_ptr`
    • `unique_ptr`
  • shared_ptr
    • 模拟实现
    • 定制删除器
    • 循环引用 和 `weak_ptr`

RAII

RAII(Resource Acquisition Is Initialization)是一种广泛应用于 C++ 等编程语言中的编程范式,它的核心思想是:资源的获取和释放与对象的生命周期绑定。在 RAII 中,资源(如内存、文件句柄、网络连接等)的获取通常发生在对象的构造函数中,而资源的释放则发生在对象的析构函数中。

这种设计模式确保了资源在不再需要时自动释放,从而避免了手动管理资源的复杂性和潜在的错误(如内存泄漏和资源泄露)。

核心思想

  • 资源获取: 当一个对象被创建时,它会立即获取某个资源。例如,分配内存、打开文件或创建数据库连接等。
  • 资源释放: 当该对象超出作用域或被销毁时,它的析构函数会自动释放相应的资源。这意味着开发者不需要显式地释放资源,降低了出错的概率。

实现方式

  • 构造函数:在对象创建时,负责分配所需的资源。例如,在构造函数中打开一个文件或分配一块内存。
  • 析构函数:在对象销毁时,负责释放该对象占用的资源。当对象的生命周期结束时,析构函数会自动执行,释放资源。

RAII 的优势

  • 自动资源管理: RAII 自动处理资源的释放,不需要显式调用资源释放代码,减少了出错的可能性(如忘记释放资源)。
  • 异常安全: RAII 能够保证即使程序中发生异常,资源也会被正确释放。例如,在 try 块中的对象被销毁时,析构函数会自动释放资源,从而避免资源泄漏。
  • 简洁性和易维护性: 使用 RAII 模式可以使资源管理代码更加简洁和模块化,减少了繁琐的手动管理。
  • 防止内存泄漏: 通过将资源与对象的生命周期绑定,可以有效防止内存泄漏、悬挂指针等问题。

RAII 的缺点

  • 不能自由控制资源释放的时机: 在 RAII 模式中,资源的释放依赖于对象的生命周期,无法显式控制资源的释放时机。如果需要在对象销毁之前释放资源,RAII 可能不适用。
  • 资源生命周期绑定问题: RAII 通过对象生命周期管理资源,这对于某些类型的资源可能不适用。例如,某些外部资源(如数据库连接)可能需要在特定时刻关闭,而不仅仅是在对象销毁时。

RAII 的应用场景

  • 内存管理:例如,unique_ptrshared_ptr 是 C++ 中的智能指针,它们的实现就是基于 RAII 模式,自动管理内存资源。
  • 文件操作:如上文所示,RAII 可以用于文件的打开和关闭,确保即使发生异常,文件资源也会被自动释放。
  • 数据库连接:RAII 可用于数据库连接的管理,确保连接在对象生命周期结束时被自动关闭。
  • 线程锁管理:通过 RAII 模式,锁的获取和释放可以自动管理,避免忘记释放锁导致死锁。

智能指针

智能指针(Smart Pointer 是现代 C++ 中用于自动管理动态内存的一种工具,它通过封装原始指针,提供对内存资源的自动管理,帮助避免常见的内存管理错误,如内存泄漏和悬挂指针。

智能指针实际上是一个类,它重载了指针操作符(如 *->),使得使用智能指针的代码和普通指针一样简便,但它能自动处理资源的释放。

C++标准库中的智能指针都在 <memory> 这个头文件下,智能指针主要有 auto_ptrunique_ptrshared_ptrweak_ptr 等。

auto_ptr

auto_ptr 是C++98标准中引入的一个智能指针类型,通过自动释放资源来避免内存泄漏和悬挂指针的问题。

1. auto_ptr 的缺陷

auto_ptr 的设计存在巨大缺陷,在涉及资源所有权转移时(拷贝或者赋值时)原auto_ptr 不再拥有资源,资源的所有权转移给 目标auto_ptr ,这导致了 原auto_ptr 变成一个悬挂指针(类似于空指针)。

代码示例:

//模拟一个日期类
struct Date
{
	int _year;
	int _month;
	int _day;

	Date(int year = 2000, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	~Date()
	{
		cout << "~Date" << endl;
	}

};

int main()
{
	auto_ptr<Date> ap1(new Date);
	//拷贝时,Date的管理权限从ap1转移ap2,ap1被置空
	auto_ptr<Date> ap2(ap1);

	//ap1相当于空指针了,再去访问会造成程序崩溃
	//ap1->_day;

	//赋值也是同样的道理,Date的管理权限从ap2转移ap3,ap2被置空
	auto_ptr<Date> ap3;
	ap3 = ap2;
	
	//ap2被置空,访问会造成程序崩溃
	ap2->_day;

	return 0;
}

auto_ptr 的设计存在缺陷,在在涉及资源所有权转移时,其行为会造成意外的错误,auto_ptr在C++11中被废弃,不推荐使用

2. auto_ptr 的模拟实现
auto_ptr 的模拟实现比较简单,在涉及资源的转移时,将原指针置空即可。

template<class T>
class auto_ptr
{
public:
	auto_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	auto_ptr(auto_ptr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = nullptr;
	}

	auto_ptr<T>& operator=(auto_ptr<T>& ap)
	{
		if (_ptr != ap._ptr)
		{
			if (_ptr)
				delete _ptr;

			_ptr = ap._ptr;
			ap._ptr = nullptr
		}
		return *this;
	}

	~auto_ptr()
	{
		if (_ptr)
			delete _ptr;
		_ptr = nullptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

private:
	T* _ptr;
};

unique_ptr

unique_ptr 是独占式的智能指针,表示指向一个动态分配的对象的唯一所有者。该指针不支持拷贝和赋值,但支持移动构造或者赋值

当一个资源只能有一个拥有者时,使用 unique_ptr 是最合适的选择。

代码示例:

unique_ptr<Date> up1(new Date);
//不支持拷贝或者赋值
//unique_ptr<Date> up2(up1);
//unique_ptr<Date> up3; up3 = up1;

//支持移动构造或者赋值,但是ap1置空了,谨慎使用
unique_ptr<Date> up2(move(up1));
unique_ptr<Date> up3;
up3 = move(up2);

1. make_unique
make_unique 是 C++11/14 标准库中引入的一个函数模板,用于创建动态分配的对象并返回一个 unique_ptr,从而安全高效地管理对象的生命周期。

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args);

与直接使用 new 操作符相比的优势

  1. 避免手动调用 newdelete :使用 make_unique 能够简化动态内存分配,避免使用裸指针容易产生的内存泄漏或未定义行为。
  2. 性能优化 :它能够一次性分配对象和控制块所需的内存,减少额外开销。
  3. 强异常安全性:使用 make_unique 时,不会因为对象构造和分配的中间异常而泄漏内存。
//使用 make_unique 创建一个 int 类型的 unique_ptr(推荐)
auto up1 = make_unique<int>(20);

//直接使用 unique_ptr(容易出错),如果构造函数抛异常就会出现内存泄漏
unique_ptr<int> up2(new int(10));

2. 定制删除器

unique_ptr 在释放资源时,默认是 delete _ptr ,如果指向的资源是 new type[num] 而来的,默认释放资源的方式就不适合了,需要 delete[] 的方式是释放资源,这时我们需要定制删除器。

new [] 的方式经常使用,库里已经有了特化版本,而对于定制删除器,仿函数、函数指针、lamba表达式都可作为删除器。

不过要注意的是传定制删除器给 unique_ptr ,是传给模板参数,其构造参数也要传。
在这里插入图片描述
代码示例:

//new []特化
unique_ptr<Date[]> up1(new Date[5]);

// 仿函数作删除器,将其传到模板参数,仿函数构造的对象可以直接调用,不需要传给构造参数
unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);

//函数指针作删除器,既要传模板参数也要传构造参数
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);

// lambda表达式作删除器,decltype获取delArr的类型
auto delArr = [](Date* ptr) {delete[] ptr; };
unique_ptr<Date, decltype(delArr)> up4(new Date[5], delArr);

简单说一下定制删除器的底层,将定制删除器的类型传过去利用其类型创建删除器对象并用传给构造参数的具体定制删除器对象来初始化,这样底层就有了外层传进来的定制删除器,然后利用删除器释放资源

3. unique_ptr 的模拟实现

unique_ptr 的模拟实现也比较简单,将其构造函数赋值重载函数 delete 即可。

template<class T>
class unique_ptr
{
public:
	//防止隐式类型转换
	explicit unique_ptr(T* ptr)
		:_ptr(ptr)
	{}

	~unique_ptr()
	{
		if (_ptr)
			delete _ptr;
		_ptr = nullptr;
	}

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

	unique_ptr(unique_ptr<T>&& up)
		:_ptr(up._ptr)
	{
		up._ptr = nullptr;
	}

	unique_ptr<T>& operator=(unique_ptr<T>&& up)
	{
		delete _ptr;
		_ptr = up._ptr;
		up._ptr = nullptr;
	}

private:
	T* _ptr;
};

有关 explicit

关键字 explicit 的作用是修饰构造函数,防止隐式类型转换

Date* ptr = new Date;
//hz::unique_ptr<Date> up1 = ptr; 这种写法编译会出错,explicit不允许隐式类型转换,本质是构造+拷贝
unique_ptr<Date> up1(ptr);

使用 explicit 修饰单参数构造函数可以提高代码的可读性,减少维护负担。

shared_ptr

shared_ptr 是 C++11 标准引入的一种智能指针,用于管理动态分配的对象,并允许多个 shared_ptr 实例共享同一对象的所有权。shared_ptr 使用 引用计数 来追踪有多少个 shared_ptr 对象共享资源,并在最后一个 shared_ptr 被销毁时自动释放资源。这种机制确保了内存管理的安全性,避免了内存泄漏,同时允许多个对象共享相同的资源。

shared_ptr 是一种 共享所有权 的智能指针,而非独占所有权(像 unique_ptr )。多个 shared_ptr 对象可以共同管理一个动态分配的对象,而不必担心资源的重复释放或遗漏释放。

代码示例:

//创建一个 shared_ptr 管理 Date 对象
shared_ptr<Date> sp1(new Date);

//复制sp1,增加引用计数
shared_ptr<Date> sp2(sp1);
cout << "Reference count: " << sp1.use_count() << endl;

//当 sp1 和 sp2 超出作用域时,Date 对象会被自动销毁

代码解析:
1.shared_ptr<Date> sp1(new Date);

  • sp1 是一个 shared_ptr,它管理一个动态分配的 Date 对象。此时引用计数为 1。

2.shared_ptr<Date> sp2(sp1);

  • sp2sp1 的副本,意味着它也指向同一个 Date 对象,引用计数增加到 。

3.sp1.use_count() 返回当前有多少个 shared_ptr 管理相同的对象。此时返回 2。

4.当 sp1sp2 超出作用域时,它们的引用计数都会减少。当引用计数降到 0 时,Date 对象会自动销毁。

有关 make_shared

make_shared 也是一个函数模板,用于创建共享指针,可以接受任何类型的参数,并返回一个指向该类型对象的共享指针。

template <class T, class... Args>
  shared_ptr<T> make_shared (Args&&... args);
  • make_shared 与直接使用 shared_ptr 的对比
特性make_shared直接用shared_ptr
语法简洁性更简洁需要手动调用 new
内存分配次数1 次2 次(对象和引用计数分别分配)
异常安全性更安全容易出现内存泄漏

模拟实现

对于 shared_ptr 的模拟实现,我们首先要考虑的就是引用计数的设计。

引用计数用静态成员变量是无法实现的

因为静态成员变量是整个类共有的,每当指向一个资源,无论是不同的资源还是相同的资源,静态成员变量都会增加,不能做到对于不同的资源都有独立的一份引用计数

比如 sp1 和 sp2 指向着资源1,引用计数是2,在创建一个 sp3 指向资源2,由于引用计数是静态成员变量,引用计数就变成3了,这显然是错误的,sp3 的引用计数应该是1.

  • 引用计数的设计应该采用动态开辟的方式,做到每一个不同的资源都有一份独立的引用计数。
    在这里插入图片描述
    以下为 shared_ptr 的实现:
template<class T>
class shared_ptr
{
public:
	//explicit防止隐式类型转换
	explicit shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}

	~shared_ptr()
	{
		if (--(*_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
		}
	}

	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
	{
		++(*_pcount);
	}

	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}

			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++(*_pcount);
		}
		return *this;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	 T* get() const
	 {
	 	 return _ptr;
	 }
	 
 	int use_count() const
     {
         return *_pcount;
     }
	
private:
	T* _ptr;
	int* _pcount;
};

shared_ptr 的成员函数的实现都比较简单,但是赋值重载函数有比较多细节要注意:

  1. 赋值操作要保证不是一个指针自己给自己赋值this != &sp 不能完全处理所有情况,因为不同的 shared_ptr 对象的 _ptr 可能是一样的,得用 _ptr != sp._ptr 才可以完全覆盖所有情况。

  2. 被赋值的指针的引用计数要先要减1判断该指针是否是最后一个指向对应资源的指针若是则要释放原来的资源

  3. 进行赋值操作,完成后引用计数要+1,最后返回 *this
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
	if (_ptr != sp._ptr)
	{
		if (--(_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
		}

		_ptr = sp._ptr;
		_pcount = sp._pcount;
		++(*_pcount);
	}
	return *this;
}

定制删除器

shared_ptr 也可以传定制删除器,不过相比 unique_ptr 的方式,shared_ptr 传递删除器的方式只需传到构造函数的参数即可

//其构造函数的声明
template <class U, class D> shared_ptr (U* p, D del);
template <class D> shared_ptr (nullptr_t p, D del);

使用示例:

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

template<class T>
void DeleteArrayFunc(T* ptr)
{
	delete[] ptr;
}

//[]特化版本
shared_ptr<int[]> sp1(new int[10]);

//仿函数作删除器
shared_ptr<Date> sp2(new Date[10], DeleteArray<Date>());

//函数指针作删除器
shared_ptr<Date> sp3(new Date[10], DeleteArrayFunc<Date>);

//lambda作删除器
auto  delArr = [](Date* ptr) {delete[] ptr; };
shared_ptr<Date> sp4(new Date[10], delArr);

增加定制删除器的模拟实现:

template<class T>
class shared_ptr
{
public:
	//explicit防止隐式类型转换
	explicit shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{
	}

	template<class D>
	shared_ptr(T* ptr, D del)
		:_ptr(ptr)
		, _pcount(new int(1))
		,_del(del)
	{}

	~shared_ptr()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete _pcount;
		}
	}

	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
		, _del(sp._del)
	{
		++(*_pcount);
	}

	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			if (--(*_pcount) == 0)
			{
				_del(_ptr);
				delete _pcount;
			}

			_ptr = sp._ptr;
			_pcount = sp._pcount;
			_del = sp._del;
			++(*_pcount);
		}
		return *this;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

private:
	T* _ptr;
	int* _pcount;
	//利用function来包装 _del,默认是不带[] 的delete
	function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

代码细节:

  • 写多一个构造函数并套一层模板,当传递删除器的时候,调用此函数。
template<class D>
	shared_ptr(T* ptr, D del)
		:_ptr(ptr)
		, _pcount(new int(1))
		,_del(del)
	{}
  • 删除器可以是仿函数、函数指针和 lambda 表达式等,我们是没有办法用具体的某个类型去创建 _del 变量,但是C++11中有一个类模板 function ,它是通用的函数包装器,可以包装仿函数、函数指针和 lambda 表达式,而删除器的函数签名都是 void(T* ptr)(返回类型和参数类型)。我们就可以用 function 来创建 _del 变量,并给上 lambda 缺省值 [](T* ptr) {delete ptr; }
//利用function来包装 _del,默认是不带[] 的delete
	function<void(T*)> _del = [](T* ptr) {delete ptr; };

循环引用 和 weak_ptr

智能指针是用来管理动态分配的内存,以避免内存泄漏的问题。然而,如果使用不当,智能指针也会引入一些新的问题,例如循环引用

循环引用(Cyclic Reference)是指两个或多个对象互相持有对方的引用形成一个环导致它们无法被释放,即使它们已经不再被其他部分使用。

代码示例

class Node {
public:
	//指向下一个节点的智能指针
	shared_ptr<Node> next;
	~Node() { cout << "Node destroyed" << endl; }
};

int main() {
	auto node1 = make_shared<Node>();
	auto node2 = make_shared<Node>();

	// 相互引用
	node1->next = node2;
	node2->next = node1;

	// 程序结束时,node1和node2不会被释放
	return 0;
}

在这里插入图片描述

为什么会出现循环引用?

  1. shared_ptr 的原理:

    • shared_ptr 通过引用计数来管理对象的生命周期。
    • 当引用计数变为0时,shared_ptr 会自动释放内存。
  2. 循环引用的本质:

    • 在上述例子中,node1node2 互相持有对方的 shared_ptrnode1 需要 node2的shared_ptr 析构时释放,而 node2的shared_ptrnode2 的成员变量,需要让 node2 释放才会析构,node2 需要 node1的shared_ptr 析构时释放, node1的shared_ptr 需要让 node1 释放才会析构。这样就形成一个环了,两个节点的 shared_ptr 的引用计数始终不为0;
    • 即使它们超出了作用域,也不会被销毁,从而引发内存泄漏
      在这里插入图片描述

如何解决循环引用问题?

可以通过将其中一个 shared_ptr 替换为 weak_ptr来打破循环引用。

class Node {
public:
	// 用weak_ptr打破循环引用
	 weak_ptr<Node> next;
	~Node() { cout << "Node destroyed" << endl; }
};

int main() {
	auto node1 = make_shared<Node>();
	auto node2 = make_shared<Node>();

	// weak_ptr不会增加引用计数
	node1->next = node2;
	node2->next = node1;

	// 程序结束时,node1和node2会被正确释放
	return 0;
}

程序结束时,先析构 node2 ,引用计数减到0,释放 node2 ,而不会像循环引用那般由于 node2 和 node1->next 指向同一个对象,析构 node2 时其引用计数从2减到1,导致引用计数永远不为0导致 node2 无法释放。node2 释放后 node1 也能正常释放了

weak_ptr 是一种辅助智能指针,它与 shared_ptr 配合使用,用于解决循环引用问题实现对象的非强拥有关系

  • weak_ptr 是一种不参与引用计数的智能指针。
  • 它不会改变所指向对象的生命周期,仅仅是一个“弱引用”。
  • 常用于观察由 shared_ptr 管理的对象,而不会影响其销毁时机。

基本用法:

  • weak_ptr 必须从 shared_ptr 初始化,不能直接管理动态分配的内存。
  • 通过 weak_ptr 无法直接访问对象,需要调用 lock() 方法将其转换为 shared_ptr
    lock() 方法返回一个指向相同对象的 shared_ptr,如果对象已被释放,则返回一个空指针。

shared_ptr 的区别

特性shared_ptrweak_ptr
是否参与引用计数
是否影响生命周期
访问对象方式直接使用 *->需调用 lock() 转换为 shared_ptr
应用场景强拥有关系,负责对象生命周期弱引用,避免循环引用或临时访问

总结

  • weak_ptr 是一种轻量级的智能指针,用于观察对象,不参与对象生命周期管理。
  • 在设计需要临时引用或防止循环引用的场景中,weak_ptr 是一个非常重要的工具。
  • 配合 shared_ptr 使用,能够更好地管理复杂对象间的依赖关系。

拜拜,下期再见😏

摸鱼ing😴✨🎞
请添加图片描述

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

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

相关文章

【优选算法 位运算】位运算算法入门详解:常见位运算总结

判定字符是否唯一 题目解析 算法原理 解法一 &#xff1a;哈希数组 从前往后扫描字符串&#xff0c;把扫描到的字符先进行判断&#xff0c;如果对应的 val 0 &#xff0c;则放入哈希表中&#xff0c;否则返回 false&#xff0c;知道扫描完整个字符&#xff1b;时间…

深入理解Linux进程管理机制

个人主页&#xff1a;chian-ocean 文章专栏-Linux 前言 进程是现代操作系统中一个不可或缺的概念&#xff0c;其主要目的在于管理资源、实现并发、提高系统效率&#xff0c;并确保系统的稳定性和安全性。 进程的定义 进程&#xff08;Process&#xff09; 是计算机操作系统中…

R 语言科研绘图第 4 期 --- 折线图-置信区间

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…

springSecurity自定义登陆接口和JWT认证过滤器

下面我会根据该流程图去自定义接口&#xff1a; 我们需要做的任务有&#xff1a; 登陆&#xff1a;1、通过ProviderManager的方法进行认证&#xff0c;生成jwt&#xff1b;2、把用户信息存入redis&#xff1b;3、自定义UserDetailsService实现到数据库查询数据的方法。 校验&a…

使用 LabVIEW 与 PLC 通信的方式

要将 PLC 与 LabVIEW 或其他 NI 产品进行通信&#xff0c;首先需要明确 PLC 支持的通信协议和接口类型。NI 提供了多种方案&#xff0c;包括 OPC 服务器、Modbus、Ethernet/IP 和其他工业通信协议。下面将详细介绍这些方法&#xff0c;并进行比较分析&#xff0c;帮助你选择最适…

软考高级架构-9.4.4-双机热备技术 与 服务器集群技术

一、双机热备 1、特点&#xff1a; 软硬件结合&#xff1a;系统由两台服务器&#xff08;主机和备机&#xff09;、一个共享存储&#xff08;通常为磁盘阵列柜&#xff09;、以及双机热备软件&#xff08;提供心跳检测、故障转移和资源管理功能的核心软件&#xff09;组成。 …

专题二十五_动态规划_两个数组的 dp (含字符串数组)_算法专题详细总结

目录 动态规划_两个数组的 dp &#xff08;含字符串数组&#xff09; 1. 最⻓公共⼦序列&#xff08;medium&#xff09; 解析&#xff1a; 1. 状态表⽰&#xff1a; 2. 状态转移⽅程&#xff1a; 3. 初始化&#xff1a;​编辑 4. 填表顺序&#xff1a;​编辑 5. 返回值…

12,攻防世界simple_php

simple_php 题目来源:Cyberpeace-n3k0 题目描述: 小宁听说php是最好的语言,于是她简单学习之后写了几行php代码。 进入靶场 这段PHP代码是一个简单的web应用示例&#xff0c;让我们逐步分析这段代码&#xff1a; show_source(__FILE__);&#xff1a;这行代码会显示当前文件的…

NIO - selector简单介绍

一 前言 selector作为NIO当中三大组件之一&#xff0c;是处理NIO非阻塞模式下的核心组件&#xff0c;它允许一个单个线程管理多个通道。 NIO下的阻塞模式 因为对于阻塞模式下的NIO模式&#xff0c;存在很大的问题&#xff0c;即使在单线程下&#xff0c;对应的服务端也会一直进…

二、部署docker

二、安装与部署 2.1 安装环境概述 Docker划分为CE和EE&#xff0c;CE为社区版&#xff08;免费&#xff0c;支持周期三个月&#xff09;&#xff0c;EE为企业版&#xff08;强调安全&#xff0c;付费使用&#xff09;。 Docker CE每月发布一个Edge版本&#xff08;17.03&…

Camp4-L2:LMDeploy 量化部署进阶实践

书生浦语大模型实战营第四期&#xff1a;LMDeploy 量化部署进阶实践 教程链接&#xff1a;https://github.com/InternLM/Tutorial/tree/camp4/docs/L2/LMDeploy视频链接&#xff1a;https://www.bilibili.com/video/BV18aUHY3EEG/?vd_sourceb96c7e6e6d1a48e73edafa36a36f1697…

Qt之第三方库QCustomPlot使用(二)

Qt开发 系列文章 - qcustomplot&#xff08;二&#xff09; 目录 前言 一、Qt开源库 二、QCustomPlot 1.qcustomplot介绍 2.qcustomplot下载 3.qcustomplot移植 4.修改项目文件.pro 5.提升QWidget类‌ 三、技巧讲解 1.拖动缩放功能 2.等待更新 总结 前言 Qt第三方…

python数据分析之爬虫基础:selenium详细讲解

目录 1、selenium介绍 2、selenium的作用&#xff1a; 3、配置浏览器驱动环境及selenium安装 4、selenium基本语法 4.1、selenium元素的定位 4.2、selenium元素的信息 4.3、selenium元素的交互 5、Phantomjs介绍 6、chrome handless模式 1、selenium介绍 &#xff08;1…

LearnOpenGL学习(模型加载 -- Assimp,网格,模型)

完整代码见&#xff1a;zaizai77/Cherno-OpenGL: OpenGL 小白学习之路 Assimp 3D建模工具如Blender、3DS Max在导出模型文件时&#xff0c;会自动生成所有的顶点坐标、顶点法线和纹理坐标。 .obj 格式只包含了模型数据和材质信息&#xff08;颜色、贴图等&#xff09; Assi…

qtcanpool 知 08:Docking

文章目录 前言口味改造后语 前言 很久以前&#xff0c;作者用 Qt 仿照前端 UI 设计了一个 ministack&#xff08;https://gitee.com/icanpool/qtcanpool/blob/release-1.x/src/libs/qcanpool/ministack.h&#xff09; 控件&#xff0c;这个控件可以折叠。部分用户体验后&#…

【Linux】文件管理必备知识和基本指令

【Linux】文件管理必备知识和基本指令 什么是操作系统什么是文件什么是路径01. ls 指令02. pwd命令03. cd 指令04. touch指令05.mkdir指令&#xff08;重要&#xff09;&#xff1a;06.rmdir指令 && rm 指令&#xff08;重要&#xff09;&#xff1a;rmdir指令rm指令 0…

R155 VTA 认证对汽车入侵检测系统(IDS)合规要求

续接上集“浅谈汽车网络安全车辆型式认证&#xff08;VTA&#xff09;的现状和未来发展”&#xff0c;有许多读者小伙伴有联系笔者来确认相关的R155 VTA网络安全审核要求&#xff0c;基于此&#xff0c;笔者将针对 R155 VTA 每一条网络安全审核细则来具体展开。 今天就先从汽车…

【PHP项目实战】活动报名系统

目录 项目介绍 开发语言 后端 前端 项目截图&#xff08;部分&#xff09; 首页 列表 详情 个人中心 后台管理 项目演示 项目介绍 本项目是一款基于手机浏览器的活动报名系统。它提供了一个方便快捷的活动报名解决方案&#xff0c;无需下载和安装任何APP&#xff0c…

【数据分享】1901-2023年我国省市县三级逐年最低气温数据(Shp/Excel格式)

之前我们分享过1901-2023年1km分辨率逐月最低气温栅格数据和Excel和Shp格式的省市县三级逐月最低气温数据&#xff0c;原始的逐月最低气温栅格数据来源于彭守璋学者在国家青藏高原科学数据中心平台上分享的数据&#xff01;基于逐月栅格数据我们采用求年平均值的方法得到逐年最…

使用伪装IP地址和MAC地址进行Nmap扫描

使用伪装IP地址和MAC地址进行Nmap扫描 在某些网络设置中&#xff0c;攻击者可以使用伪装的IP地址甚至伪装的MAC地址进行系统扫描。这种扫描方式只有在可以保证捕获响应的情况下才有意义。如果从某个随机的网络尝试使用伪装的IP地址进行扫描&#xff0c;很可能无法接收到任何响…