【智能指针】

news2024/10/6 14:38:31

目录:

  • 前言
  • 智能指针
    • (一)智能指针初始
    • 了解内存泄漏
      • 1. 内存泄漏分类
      • 2. 如何检测内存泄漏
      • 3. 如何避免内存泄漏
        • 使用智能指针之前,异常安全的处理
    • (二)智能指针实现既原理
      • 智能指针
      • RAII
        • 使用智能指针之后,异常安全的处理
      • auto_ptr
    • (三)c++11智能指针
      • unique_ptr
      • shared_ptr
        • 1. 引用计数要存储在哪个区域
        • 2.多线程,智能指针引用计数的访问
        • 3.多线程,智能指针指向资源的访问
        • 智能指针的循环引用
      • weak_ptr
    • 删除器
  • 总结

前言

打怪升级:第92天
在这里插入图片描述

智能指针

(一)智能指针初始

  • 什么是智能指针
    智能指针简单来说就是将指针封装到类中,借助对象的局部作用域有效特性,在出了作用域后自动释放资源。

  • 为什么需要使用智能指针
    上一篇文章我们讲解了C++异常的概念,我们也了解到异常的捕捉可以跨好几个函数,这就会引发异常安全的问题,在上一篇文章中我们解决异常安全的方法是在每一个可能会引发异常安全的地方都加上try,catch捕捉异常,处理完安全问题后再抛出;
    上面的方法固然可以解决问题,但同时也会使得函数逻辑变得复杂许多,今天我们借助类的特性来大大简化这一逻辑。

  • [注]: 异常可以直接跳好几个函数,这个只是我们在调试时看到的现象,在底层堆栈中依然是层层出栈的。
  • 智能指针单指一个特殊指针吗
    不是的,智能指针如今指的是一类指针,智能指针在c++98中就已经有了,当时的智能指针只有一个: auto_ptr,auto_ptr是一个类模板,但是由于auto_ptr的使用十分不尽人意,因此很多个人或公司都禁止使用,直到c++11出来后,为我们带来了许多实用的智能指针,常用的有三种:unique_ptr, shared_ptr,weak_ptr,,它们都包含于头文件 < memory>,下面我们会一一介绍。

了解内存泄漏

1. 内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  1. 堆内存泄漏(Heap leak)
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  2. 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

2. 如何检测内存泄漏

在linux下内存泄漏检测:linux下几款内存泄漏检测工具
在windows下使用第三方工具:VLD工具说明
其他工具:内存泄漏工具比较

3. 如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配地去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

使用智能指针之前,异常安全的处理
#include<iostream>

using namespace std;

void Div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw("division of zero");
	else
		cout << "a / b = " << a / b << endl;
}

void Func()
{
	int* p1 = new int;
	int* p2 = new int;

	Div();

	cout << "delete p1" << endl;
	delete p1;
	cout << "delete p2" << endl;
	delete p2;
}

int main()
{
	while (1)
	{
		try
		{
			Func();
		}
		catch (const char* s)
		{
			cout << s << endl;
		}
	}

	return 0;
}

这里是引用
这段代码大家应该可以理解,由于Div函数抛出异常,直接跳转到main函数中匹配的catch子句,导致func函数中发生内存泄漏。

防止内存泄漏的改进:

#include<iostream>

using namespace std;

void Div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw("division of zero");
	else
		cout << "a / b = " << a / b << endl;
}

void Func()
{
	// p1 new空间时 抛异常会怎样,怎么办
	// p2 new空间时 抛异常会怎样,怎么办
	// Div 抛异常时怎么办
	int* p1 = nullptr;
	int* p2 = nullptr;

	p1 = new int(10);

	try{
		p2 = new int(1);
	}
	catch (...){
		cout << "delete p1" << endl;
		delete p1;
		throw;
	}

	try{
		Div();
	}
	catch (...){
		cout << "delete p1" << endl;
		delete p1;
		cout << "delete p2" << endl;
		delete p2;
		throw;
	}

	cout << "delete p1" << endl;
	delete p1;
	cout << "delete p2" << endl;
	delete p2;
}

int main()
{
	while (1)
	{
		try
		{
			Func();
		}
		catch (const char* s)
		{
			cout << s << endl;
		}
		catch (std::bad_alloc& e) // new错误 抛出的异常类型:bad_alloc
		{
			cout << "exception: " << e.what() << endl;
		}
	}

	return 0;
}


在这里插入图片描述

原本一个十分简单的逻辑,为了使用异常我们就要防止内存泄漏,这使得原本简单的逻辑变得复杂。


(二)智能指针实现既原理

智能指针

智能指针的两点要去:

  1. 符合RAII
  2. 重载 解引用操作符*,与箭头操作符->模拟指针的行为。

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象创建时获取资源,之后控制资源的访问在对象整个生命周期内都有效,在对象销毁时释放资源,这样将资源的申请与释放与对象绑定到一起。这样做有两个好处:

  1. 不用手动释放资源;
  2. 资源在对象的整个生命周期内都有效。
使用智能指针之后,异常安全的处理

在这里插入图片描述
此处我们实现了智能指针的两个条件中的一个:RAII,通过对象的生命周期来控制资源的申请和释放;

下面我们要添加对指针的访问,模拟指针的行为:

#include<iostream>

using namespace std;

template<class T>
class smart_ptr
{
public:
	smart_ptr(T* mp)
	:_ptr(mp)
	{}

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

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

	~smart_ptr()
	{
		cout << "delete" << endl;
		delete _ptr;
	}
private:
	T* _ptr;
};

void Div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw("division of zero");
	else
		cout << "a / b = " << a / b << endl;
}

void Func()
{
	smart_ptr<int>p1(new int(2));
	smart_ptr<int>p2(new int(5));

	Div();
}

int main()
{
	while (1)
	{
		try
		{
			Func();
		}
		catch (const char* s)
		{
			cout << s << endl;
		}
		catch (std::bad_alloc& e)
		{
			cout << "exception: " << e.what() << endl;
		}
	}

	return 0;
}

这里是引用
为什么使用智能指针可以解决这个问题, 底层很复杂吗? – 不见得
我们在一开始就说了, 触发异常时, 函数调用链仍然会一层层出栈(不然栈区空间就泄漏了, 并且, 如果不出栈, 怎么访问到该函数调用链上的其他函数), 那么在出栈时, 函数栈上的变量就会被销毁, 我们的智能指针虽然是用来管理堆区资源的, 但是智能指针本身创建在栈区, 在智能指针被销毁时, 就会调用它的析构函数同步销毁管理的资源. perfect


auto_ptr

auto_ptr整体逻辑与上方相同,看起来确实解决了异常安全问题,但是,为何auto_ptr会被使用者以及公司所排斥,甚至禁止使用auto_ptr呢?
让我们继续往下看去。

#include<memory> // auto_ptr
#include<iostream>

using namespace std;

void test_ptr()
{
	int* p1 = new int(1);
	cout << "*p1 = " << *p1 << endl;

	// 指针赋值时,p2此时也指向p1指向的那块空间。
	int* p2 = p1;
	cout << "*p2 = " << *p2 << endl;
	cout << "*p1 = " << *p1 << endl;
}

void test_auto_ptr()
{
	auto_ptr<int>ap(new int(10));
	cout << "*ap = " << *ap << endl;

	auto_ptr<int>ap2(ap);
	cout << "*ap2 = " << *ap2 << endl;
	cout << "*ap = " << *ap << endl;
}

int main()
{
	test_ptr();
	//test_auto_ptr();
	return 0;
}

这里是引用
经过试验我们发现了auto_ptr的缺陷所在:当auto_ptr对象要进行拷贝构造时,新的对象可以访问拷贝来的资源,而被拷贝对象则无法访问了,这个实际的指针用法并不相符。

这中现象我们称为:悬空指针(没有指向一块实际内存的指针)
这种做法我们称为:管理权转移(ap没有权利访问原本属于它的资源了)。

听起来好像很高大上,底层实现很简单,如下所示:

namespace kz
{
template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* mp)
			:_ptr(mp)
		{}

		auto_ptr(auto_ptr& ap)  // 拷贝构造
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		auto_ptr& operator=(auto_ptr& ap) // 赋值
		{
			if (_ptr != ap._ptr) // 自己给自己赋值
			{
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}

			return *this;
		}

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

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

		~auto_ptr()
		{
			cout << "delete" << endl;
			delete _ptr;
		}
	private:
		T* _ptr;
	};
}

由于auto_ptr这样在拷贝、赋值方面的奇葩行为,最开始的智能指针auto_ptr并不受人待见,但是由于以及出来的标准就不会再做更改,所以写出管理权转移操作的大佬当时也是追悔莫及。


(三)c++11智能指针

但是骂归骂,在使用异常时,智能指针在简化代码方面的优势也十分明显,因此大家在使用时还是会自行封装智能指针,为此在c++11中又引入了几个智能指针unique_ptr, shared_ptr, weak_ptr;那么说到这里就不得不提一句boost库了,

这里是引用
由于c++标准更新缓慢,很多大佬等不及体验新版本,而且委员会也有委员想要测试预上线功能的体验效果,因此就有委员会成员发起组织了Boost社区,封装了一系列的很有用的库,c++11的智能指针、右值引用等都脱胎于Boost库。在这里插入图片描述

unique_ptr

见名知意,该智能指针为了解决auto_ptr的管理权转移漏洞,直接简单粗暴地禁止智能指针进行拷贝与赋值

	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* p = nullptr)
			:_ptr(p)
		{}

		// 防止拷贝的两种做法

		/*// c98 -- 拷贝构造和赋值 声明为私有成员
	private:
		unique_ptr(unique_ptr& up);
		unique_ptr& operator=(unique_ptr& up);*/

		// c++11  -- delete不需要的成员函数
		unique_ptr(unique_ptr& up) = delete;
		unique_ptr& operator=(unique_ptr& up) = delete;

		// ... 模拟指针行为

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "~unique_ptr()" << endl;
				delete _ptr;
			}
		}

	private:
		T* _ptr;
	};

这里是引用在这里插入图片描述


shared_ptr

见名知意:shared既共享,unique_ptr限制了拷贝与赋值,也就是一份资源只能有一个智能指针指向它,
而shared是使用引用计数来标识可以有多个指针指向同一份资源,引用计数就是一个整数,记录此时有多少个指针指向这份资源

举个栗子:
10个学生来上晚自习,自习室代表资源,则unique是一个学生用一个自习室,shared是10个学生可以共用一个自习室,此时引用计数为10;
释放资源时,unique是一个学生使用一个自习室,来时开门,走时锁门,
shared是最后一个走的学生锁门,此时引用计数应当变为0,表示自习室中没有学生。

shared_ptr有两点需要注意:

1. 引用计数要存储在哪个区域

要实现shared_ptr,我们肯定是要有一个引用计数,
首先我们知道引用计数需要被多个对象共享,既然要让不同对象看到同一个引用计数,那么它就不能是在栈区,因为各个对象的栈区数据是独立的;
剩下还有两种方法:1. 设置为静态成员,2. 开辟到堆区

  • 设置为static成员

这里是引用
静态成员在整个类中只有一个,可以被所以类成员共享,那么设置为静态成员确实可以保证不同对象看到同一份数据,如果该智能指针只申请一份资源还可以满足,
但当资源数增加时,使用不同的资源的对象也是看到的那一个引用计数,显然不符合实际情况,实际应当是一个资源匹配一个引用计数才对。

  • 同资源一样,在堆区开辟
    同资源一起,每次申请新的资源时,同时申请一个引用计数。

这里是引用

这里是引用
此时就完美解决了不同资源各自拥有自己的引用计数问题。

template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* p = nullptr)
			:_ptr(p)
		{
			_rcount = new int(_ptr != nullptr);
		}

		shared_ptr(shared_ptr& sp)
			:_ptr(sp._ptr)
			,_rcount(sp._rcount)
		{
			if (_ptr) ++(*_rcount);
		}

		shared_ptr& operator=(shared_ptr& sp)
		{
			if (_ptr != sp._ptr)
			{
				// 释放之前的资源
				Destory();
				// 链接现在的资源
				if (sp._ptr)
				{
					_ptr = sp._ptr;
					_rcount = sp._rcount;
				}
			}
		}

		// 释放资源
		void Destory()
		{
			if (--(*_rcount) == 0)
			{
				cout << "Destory()" << endl;
				delete _ptr;
				delete _rcount;
				_ptr = _rcount = nullptr;
			}
		}

		~shared_ptr()
		{
			Destory();
		}

	private:
		T* _ptr;
		int* _rcount;
	};
2.多线程,智能指针引用计数的访问
void Func(kz::shared_ptr<int>& sp, int n)
{
	for (int i = 0; i < n; ++i)
		kz::shared_ptr<int>tmp(sp);
}

void test_shared2()
{
	kz::shared_ptr<int>sp(new int(1));
	int n = 10000;
	thread t1(Func, ref(sp), n);
	thread t2(Func, ref(sp), n);

	t1.join();
	t2.join();
	cout << sp.use_count() << endl;
}
  • 注:线程传参是需要将引用对象放入函数模板**ref()**中,保证是引用传参;
  • 智能指针是C++中的一种特殊指针,它可以自动管理动态分配的内存,避免内存泄漏和悬挂指针等问题。智能指针通常使用引用计数来追踪资源的所有权,并在不再需要时自动释放资源。
    如果不加引用,智能指针的行为可能会变得不可预测,甚至会导致程序崩溃。这是因为智能指针的拷贝构造函数和析构函数都使用了引用计数,通过引用计数来管理资源的生命周期。如果没有使用引用,拷贝构造函数和析构函数无法正确地增加和减少引用计数,从而导致计数不一致,资源无法正确释放。
    因此,使用智能指针时,应该始终使用引用来传递和操作智能指针对象,以确保引用计数的正确性和资源的正确释放。

下方为多线程情况下,运行的结果:

在这里插入图片描述
有结果可知,我们上面的实现无法满足多线程的要求,那么问题出在哪里,
我们可以看一看_rcount的值:2, 4,以及同时出现两个0(析构两次),这说明我们的_rcount控制上有问题,
因为++,–操作不是原子的,所以多线程访问时应该互斥访问 – 加锁

	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* p = nullptr)
			:_ptr(p)
			,_rcount(new int())
			,_mtx(new mutex)
		{
			if(_ptr) AddRcount();
		}

		shared_ptr(shared_ptr& sp)
			:_ptr(sp._ptr)
			,_rcount(sp._rcount)
			,_mtx(sp._mtx)
		{
			if (_ptr) AddRcount();
		}

		void AddRcount()
		{
			_mtx->lock();
			++(*_rcount);
			_mtx->unlock();
		}

		shared_ptr& operator=(shared_ptr& sp)
		{
			if (_ptr != sp._ptr)
			{
				// 释放之前的资源
				Destory();
				// 链接现在的资源
				if (sp._ptr)
				{
					_ptr = sp._ptr;
					_rcount = sp._rcount;
					_mtx = sp._mtx;
					AddRcount();
				}
			}
		}

		T* get()
		{
			return _ptr;
		}

		int use_count()
		{
			return *_rcount;
		}

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

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

		// 释放资源
		void Destory()
		{
			bool lockFlag = false; 
			_mtx->lock();
			if (--(*_rcount) == 0)
			{
				cout << "Destory()" << endl;
				delete _ptr;
				delete _rcount;
				lockFlag = true;
				_ptr = _rcount = nullptr;
			}
			_mtx->unlock();

			if (lockFlag) // _mtx需要先解锁再释放,不能直接在上方释放
			{
				delete _mtx;
				_mtx = nullptr;
			}
		}

		~shared_ptr()
		{
			Destory();
		}

	private:
		T* _ptr;
		int* _rcount;
		mutex* _mtx;
	};

这里是引用

3.多线程,智能指针指向资源的访问

上方我们对智能指针内部引用计数的访问进行了加锁,保证了线程安全,
那么对智能指针指向资源的访问是否线程安全?

这里是引用
显而易见,对资源的访问也不是线程安全的,因为对资源的访问为用户自己的行为,需要用户自行加锁。

void Func3(kz::shared_ptr<int>& sp, int n, mutex& pmtx)
{
	for (int i = 0; i < n; ++i)
	{
		pmtx.lock();
		++(*sp);
		pmtx.unlock();
	}
}

void test_shared3()
{
	kz::shared_ptr<int>sp(new int(1));
	int n = 10000;
	mutex pmtx;
	thread t1(Func3, ref(sp), n, ref(pmtx));
	thread t2(Func3, ref(sp), n, ref(pmtx));

	t1.join();
	t2.join();
	cout << *sp << endl;
}

在这里插入图片描述

智能指针的循环引用
struct ListNode
{
	kz::shared_ptr< ListNode> _prev;
	kz::shared_ptr< ListNode> _next;
	int _val;
};

void test_cycle()
{
	kz::shared_ptr<ListNode>d1(new ListNode);
	kz::shared_ptr<ListNode>d2(new ListNode);

	d1->_next = d2; 
	d2->_prev = d1;
}

这里是引用

循环等待是shared_ptr的引用计数特性给自己埋下的一个坑,这里的问题就在于next与prev指向智能指针对象时,引用计数进行了++,
导致在析构时rcount值不为0,为了解决这个这个问题,有有了weak_ptr。


weak_ptr

weak_ptr专门为shared_ptr设计,用来避免对shared_ptr拷贝或赋值时改变引用计数rcount的值。

namespace kz
{
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr(T* p = nullptr)
			:_ptr(p)
		{}

		weak_ptr(shared_ptr<T>& sp)  // 模拟库中weak_ptr的实现逻辑,真正的底层并不是如此简单
			:_ptr(sp.get())
		{}

		weak_ptr& operator=(shared_ptr<T>& sp)
		{
			if (_ptr != sp.get())
			{
				if (sp.get())
					_ptr = sp.get();
			}

			return *this;
		}

	private:
		T* _ptr;
	};
}

struct ListNode
{
	kz::weak_ptr<ListNode> _prev;
	kz::weak_ptr<ListNode> _next;
	int _val;
};

void test_cycle()
{
	kz::shared_ptr<ListNode>d1(new ListNode);
	kz::shared_ptr<ListNode>d2(new ListNode);

	d1->_next = d2; 
	d2->_prev = d1;
}

在这里插入图片描述

标准库中无法打印其他信息,通过引用计数我们也可以看出与我们实现的一致:weak_ptr不会增加引用计数

在这里插入图片描述


删除器

删除器顾名思义就是用来删除操作的,上方我们看到,我们的析构操作都是使用的delete,那么与之对应的申请空间的操作符就是new,
也就是说,我们上方的智能指针所控制的资源只能是单个的资源,并且只能是通过new申请到的;
如果我们传入的是malloc、或new [ ] 申请的资源,delete很可能不会如我们所愿地完成资源的释放。

这里是引用

为了完成资源申请与释放的两两配对,引入了删除器的概念。

删除器:就是自行设置删除函数。

template<class T>
struct FreeFunc
{
	void operator()(T* p)
	{
		cout << "free" << endl;
		free(p);
	}
};

template<class T>
struct DeleteArrayFunc
{
	void operator()(T* p)
	{
		cout << "delete array" << endl;
		delete[]p;
	}
};


struct Date
{
	~Date()
	{}

	int _year;
	int _month;
	int _day;
};

void test_deleter()
{
	std::shared_ptr<int[]>sp1(new int[10] {10});
	std::shared_ptr<Date>sp2(new Date[10], DeleteArrayFunc<Date>());
	std::shared_ptr<int>sp3((int*)malloc(sizeof(int)), FreeFunc<int>());
}

这里是引用

如此就解决了申请与释放操作相匹配的问题。


总结

  1. 智能指针类似于之前学过的迭代器,但又有所不同,
    智能指针是借助对象的局部作用域有效,自动释放资源,顺便对资源进行访问,他管理资源的申请和释放
    迭代器是用来遍历容器,访问数据,并不控制资源的申请和释放。
  2. 关于shared_ptr:
    智能指针是一种用于管理动态分配的内存资源的工具,它可以自动地在适当的时机释放所管理的内存,避免内存泄漏和悬挂指针等问题。智能指针通常用于以下场景:
    动态内存管理:在C++中,使用new关键字进行动态内存分配,而使用智能指针可以自动管理分配的内存,避免忘记释放内存或者释放过早导致的问题。
    资源管理:除了内存,智能指针还可以用于管理其他类型的资源,比如文件句柄、网络连接等。通过使用智能指针,可以确保在不再需要资源时正确地释放它们,避免资源泄漏。
    异常安全性:在面对异常情况时,智能指针可以确保资源的正确释放。当发生异常时,智能指针会自动调用析构函数来释放资源,从而保证程序的异常安全性。
    循环引用的管理:在存在循环引用的情况下,使用智能指针可以解决内存泄漏的问题。智能指针使用引用计数的方式来管理资源的生命周期,当引用计数为0时,自动释放资源。

总的来说,智能指针提供了一种方便、安全和可靠的方式来管理动态分配的内存和其他资源,可以减少手动内存管理的工作量,提高程序的健壮性和可维护性。



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

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

相关文章

060:mapboxGL点击某处,通过flyTo,以动画的形式聚焦到此点

第060个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中点击某处,通过flyto,以动画的形式聚焦到此点。这里用到了flyTo的方法,里面可以设置bearing,zoom,pitch等众多的属性内容。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示…

数据结构与算法-(10)---列表(List)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

Linux 最大可以打开多少文件描述符?

Linux 最大可以打开多少文件描述符&#xff1f; 在日常开发中&#xff0c;对文件的操作可谓是再寻常不过的意见事情。那么你是否有这样一个疑问&#xff0c; 我最多可以打开多少个文件呢&#xff1f; 在Linux系统中&#xff0c;当某个程序打开文件时&#xff0c;内核返回相应…

SQL查询命令互转vba格式

最近搞个Excel的vba查询数据库&#xff0c;发现vba有代码行长度限制需要转换下就弄了这个&#xff0c;布局和功能暂且这样了&#xff0c;哪位大佬如果有兴趣的可以再美化下&#xff01; 这次更新了SQL命令互转VBA格式&#xff0c; SQL原始格式要分行的不能一坨贴进去&#xff0…

Java日志系统之JUL

目录 JUL介绍 JUL的使用 日志级别 指定日志输出地址 Logger对象的父子关系 Logger读取配置文件 JUL介绍 Java自带的框架&#xff0c;使用简单&#xff0c;无需引入依赖 JUL的使用 public class JULTest {Testpublic void testLogger() throws Exception{//获取日志记录…

2 用TensorFlow构建一个简单的神经网络

上一篇&#xff1a;1 如何入门TensorFlow-CSDN博客 环境搭建 后续介绍的相关代码都是在pycharm运行&#xff0c;pycharm安装略。 打开pycharm&#xff0c;创建一个新的项目用于tensorflow编码练习&#xff0c;在Terminal输入命令&#xff1a; # 依赖最新版本的pip pip inst…

[AutoSAR系列] 1.2 AutoSar 综述

AutoSAR是一种汽车工业领域的标准化软件架构,旨在简化不同汽车制造商之间的软件开发和交互。该标准于2003年由一系列欧洲汽车制造商成立的AutoSAR联盟制定并发布,目前已经成为全球范围内的标准。下面将对AutoSAR的概念、架构和实现进行综述。 1. 概述 AutoSAR是汽车电子控制…

Qt 读写文件(QFileQTextStreamQDataStream) 详解

一、读写文本文件 (QFile 类) Qt QFile类是一个用于读取和写入文件的类&#xff0c;它提供了对文件的访问、读取和写入等操作。它既可以操作文本文件&#xff0c;也可以操作二进制文件。 QFile类的功能包括&#xff1a; 打开、关闭文件读取文件内容写入文件内容支持文本模式…

如何让ChatGPT生成图片?

目录 一、那么如何解决让ChatGPT具有画图能力的问题呢&#xff1f; 二、那ChatGPT为什么能生成图片呢&#xff1f; 我们都知道ChatGPT只是个纯文本的AI模型&#xff0c;不具备画图能力。它可以生成文本&#xff0c;但如果让他生成图片就会显示如下的声明&#xff1a; 但通过本…

前端多媒体处理工具——ffmpeg的使用

写在前面 在前端领域&#xff0c;FFmpeg 是一个非常有用的工具&#xff0c;它提供了多种媒体格式的封装和解封装&#xff0c;包括多种音视频编码、多种协议的流媒体、多种色彩格式转换、多种采样率转换、多种码率切换等。可以在多种操作系统安装使用。 安装 下载FFmpeg 在网…

服务器数据恢复-某银行服务器硬盘数据恢复案例

服务器故障&分析&#xff1a; 某银行的某一业务模块崩溃&#xff0c;无法正常使用。排查服务器故障&#xff0c;发现运行该业务模块的服务器中多块硬盘离线&#xff0c;导致上层应用崩溃。 故障服务器内多块硬盘掉线&#xff0c;硬盘掉线数量超过服务器raid阵列冗余级别所允…

文件目录(文件控制块FCB,目录结构,索引结点)

1.文件控制块&#xff08;实现文件目录的关键数据结构) 目录文件中的一条记录就是文件控制块&#xff08;FCB&#xff09; FCB的有序集合称为“文件目录”&#xff0c;一个FCB就是一个文件目录项。 1.FCB的组成 FCB中包含了文件的基本信息&#xff08;文件名、物理地址、逻…

墨迹天气商业版UTF-8模板,Discuz3.4灰白色风格(带教程)

1.版本支持&#xff1a;Discuzx3.4版本&#xff0c;Discuzx3.3版本&#xff0c;DiscuzX3.2版本。包括网站首页&#xff0c;论坛首页&#xff0c;论坛列表页&#xff0c;论坛内容页&#xff0c;论坛瀑布流,资讯列表页(支持多个)&#xff0c;产品列表页(支持多个)&#xff0c;关于…

【TES600】青翼科技基于XC7K325T与TMS320C6678的通用信号处理平台

板卡概述 TES600是一款基于FPGA&#xff0b;DSP协同处理架构的通用高性能实时信号处理平台&#xff0c;该平台采用1片TI的KeyStone系列多核浮点/定点DSP TMS320C6678作为主处理单元&#xff0c;采用1片Xilinx的Kintex-7系列FPGA XC7K325T作为协处理单元&#xff0c;具有1个FMC…

Android Studio(2022.3.1)设置阿里云源-新旧版本

新版本 #settings.gradle.ktsmaven { url uri("https://maven.aliyun.com/repository/public/") }maven { url uri("https://maven.aliyun.com/repository/google/") }maven { url uri("https://maven.aliyun.com/repository/jcenter/") }ma…

VBA操作数据库

相关背景&#xff1a; 对于数据分析同学&#xff0c;一般SQL&#xff0c;EXCEL是必备技能&#xff0c;但对于VBA和Python可能有的同学不会&#xff1b;在处理本地数据上(诸如excel、txt|csv文本&#xff09;&#xff0c;后续尝试使用VBA或者Python写一个sql查询的GUI界面&…

dubbogo-1 基础rpc服务

文章目录 基本环境处理编译pb接口开启rpc调用业务观察qa1 能取出protoc里面的字段值吗&#xff1f; 基本环境处理 https://cn.dubbo.apache.org/zh-cn/overview/quickstart/go/install/ 这里没有 protoc-gen-go --version 执行 go get -u github.com/golang/protobuf/protoc…

嵌入式养成计划-45----QT--事件机制--定时器事件--键盘事件和鼠标事件--绘制事件

一百一十五、事件机制 当这件事情发生时&#xff0c;会自动走对应的函数处理&#xff08;重写的事件函数&#xff09; 115.1 事件处理简介 什么是事件&#xff1f; (重点) 件是由窗口系统或者自身产生的&#xff0c;用以响应所发生的各类事情&#xff0c;比如用户按下并释放…

vscode调试技巧 断言 assert

目录 调试技巧标题debug release介绍调试技巧断点 断点的意思 就是代码执行到断点处停下来&#xff0c;让你去调试。不管前面有多少代码&#xff0c;直接跳到断点处&#xff08;当然前面的已经执行&#xff09;逐过程 不会进入调用函数内部&#xff0c;不管里面怎么执行。 逐语…

精讲stable diffusion的controlNet插件

controlNet插件是stable diffusion的一个重要插件&#xff0c;甚至可以说正是因为有了controlNet插件&#xff0c;stable diffusion才会具有midjourney所不具备的独特魅力&#xff01; 我们今天就一起来学习下controlNet插件的安装和每个模型的用法 插件主页 独立的controlN…