C++ 智能指针深度剖析

news2024/11/17 20:43:32

文章目录

  • 1. 前言
  • 2. 为什么需要智能指针?
  • 3. 内存泄漏
    • 3.1 内存泄漏的概念及危害
    • 3.2 内存泄漏的分类
    • 3.3 如何检测内存泄漏
    • 3.4 如何避免内存泄漏
  • 4. 智能指针的使用及原理
    • 4.1 RAII思想
    • 4.2 智能指针的原理
    • 4.3 C++智能指针发展历史
    • 4.4 std::auto_ptr
    • 4.5 std::unique_ptr
    • 4.6 std::shared_ptr
    • 4.7 shared_ptr的循环引用问题
    • 4.8 定制删除器


1. 前言

C++提供了智能指针的概念,它可以自动管理动态分配的内存资源。智能指针是一种对象,它表现得像一个常规指针,但具有附加的内存管理功能。本篇文章将着重讲解智能指针的作用、内存泄漏以及智能指针的使用和原理。

2. 为什么需要智能指针?

在某些场景下,我们在堆上申请了一块空间,但是却无法正常释放。

下面我们先来分析下面这段程序有没有什么内存方面的问题?

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

问题说明:

  1. 如果在 p1 这里使用 new 分配内存时抛出异常,那么异常将传播到 Func() 函数的调用点。由于没有捕获该异常的代码,异常会继续传播到 main() 函数中的异常处理部分。此时,p1 指针没有被成功分配内存,因此不会发生内存泄漏。在异常处理部分,您可以选择适当地清理和处理异常情况。
  2. 如果在 p2 这里使用 new 分配内存时抛出异常,与第一个问题类似,异常也会传播到 Func() 函数的调用点。同样地,没有捕获该异常的代码,它会继续传播到 main() 函数中的异常处理部分。此时,p1 指针已成功分配内存,但 p2 指针没有成功分配内存。因此,在异常处理部分中,您需要确保释放已分配的内存(delete p1)并处理异常情况。
  3. 如果在 div() 函数中抛出异常,它会立即终止函数的执行,并且异常会传播到调用 div() 的地方,也就是 Func() 函数的调用点。同样地,由于没有对该异常进行捕获,它会继续传播到 main() 函数中的异常处理部分。在此过程中,p1p2 指针已成功分配内存,因此您需要确保在异常处理部分中释放已分配的内存(delete p1delete p2)并处理异常情况。

在异常处理部分,我们可以选择清理资源、记录日志或向用户显示错误信息等。同时,确保释放已分配的内存,以避免内存泄漏。在使用 new 分配内存时,可以使用智能指针(如 std::unique_ptrstd::shared_ptr)来管理动态内存,而不是手动使用 newdelete。智能指针可以自动处理内存释放,避免了手动释放内存的繁琐和容易出错的过程。

3. 内存泄漏

3.1 内存泄漏的概念及危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

以下是两种内存泄漏的情况:

void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	// 2.异常安全问题
	int* p3 = new int[10];
	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放
	delete[] p3;
}

3.2 内存泄漏的分类

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

  • 堆内存泄漏(Heap leak)

    堆内存指的是程序执行中依据须要分配通过malloc/calloc/realloc/new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

  • 系统资源泄漏

    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

3.3 如何检测内存泄漏

在linux下内存泄漏检测:Linux下几款内存泄露检查工具。
在windows下使用第三方工具:VLD(Visual LeakDetector)内存泄露库。
其他工具:内存泄漏工具比较:内存泄露检测工具比较。

3.4 如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。(这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。)

  2. 采用RAII思想或者智能指针来管理资源。

  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

  4. 出问题了使用内存泄漏工具检测。(不过很多工具都不够靠谱,或者收费昂贵。)

总结:

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

4. 智能指针的使用及原理

4.1 RAII思想

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

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

使用RAII思想设计的SmartPtr类

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	cout << div() << endl;
}
int main()
{
	try {
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

4.2 智能指针的原理

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此,AutoPtr模板类中还得需要将*->重载下,才可让其像指针一样去使用。

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
private:
	T* _ptr;
};
struct Date
{
	int _year;
	int _month;
	int _day;
};
int main()
{
	SmartPtr<int> sp(new int);
	*sp = 10;
	SmartPtr<Date> sparray(new Date);
	sparray->_year = 2024;
	sparray->_month = 1;
	sparray->_day = 1;
	return 0;
}

智能指针的原理:

  1. RAII特性。
  2. 重载operator*opertaor->,具有像指针一样的行为。

4.3 C++智能指针发展历史

思考一下,前面我们简单实现的智能指针存在一个大坑。那就是当一个指针拷贝构造或者赋值给另外一个指针时,由于我们想让两个指针指向同一块空间,所以我们实现的是浅拷贝,但是指向了同一块空间就会有析构函数调用两次的风险。由于这一个大坑,智能指针进行了很多次优化更迭。

  1. 在C++98的时候其实就已经在库中实现了智能指针,也就是第一个智能指针**auto_ptr**。
  • 既然有析构两次的风险,那么auto_ptr在每一次拷贝构造或赋值后直接将自己置空,这样实现了管理权转移。但是对于不了解auto_ptr的人来说,使用它无疑是巨大的风险。
  1. 因此,在很长一段时间里,程序员都习惯于使用boost库中的智能指针(scoped_ptrshared_ptrweak_ptr)。
  2. C++11在boost库的基础上完善并推出了unique_ptrshared_ptrweak_ptr,这与以上boost库中的三个智能指针相对应。

接下来我们来看看这些智能指针的功能和区别。

4.4 std::auto_ptr

std::auto_ptr文档

C++98版本的库中就提供了auto_ptr的智能指针。下面演示auto_ptr的使用及问题。

auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份my_ptr::auto_ptr来了解它的原理。

// C++98 管理权转移 auto_ptr
namespace my_ptr
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			// 检测是否为自己给自己赋值
			if (this != &ap)
			{
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;
				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

int main()
{
	std::auto_ptr<int> sp1(new int);
	std::auto_ptr<int> sp2(sp1); // 管理权转移

	// sp1悬空
	*sp2 = 10;
	cout << *sp2 << endl;
	cout << *sp1 << endl; // error
	return 0;
}

auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr

4.5 std::unique_ptr

unique_ptr文档

unique_ptr的实现原理:独占所有权,简单粗暴的防拷贝,下面简化模拟实现了一份my_ptr::unique_ptr来了解它的原理。

namespace my_ptr
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		unique_ptr(const unique_ptr<T>&sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;
	private:
		T* _ptr;
	};
}
int main()
{
	my_ptr::unique_ptr<int> sp1(new int);
	//my_ptr::unique_ptr<int> sp2(sp1); // error

	std::unique_ptr<int> sp1(new int);
	//std::unique_ptr<int> sp2(sp1); // error

	return 0;
}

unique_ptr直接禁用了拷贝和赋值,独占指针所有权,在一些场景下,还是可以用到它的。

4.6 std::shared_ptr

std::shared_ptr文档

shared_ptr是实际运用中使用最多的智能指针,通过引用计数支持拷贝赋值,非常优雅!

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。

  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。

  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

namespace my_ptr
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				//cout << "delete->" << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

		~shared_ptr()
		{
			release();
		}

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

		// sp1 = sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				++(*_pcount);
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* _pcount;
	};
}

4.7 shared_ptr的循环引用问题

请先分析以下代码:

struct ListNode
{
	int _data;
	my_ptr::shared_ptr<ListNode> _prev;
	my_ptr::shared_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	my_ptr::shared_ptr<ListNode> node1(new ListNode);
	my_ptr::shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

输出结果:

在这里插入图片描述

可以看到这里并未调用ListNode的析构函数,为什么呢?

循环引用分析:

  1. node1node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。

  2. node1_next指向node2node2_prev指向node1,引用计数变成2。

  3. node1node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。

  4. 也就是说_next析构了,node2就释放了。

  5. 也就是说_prev析构了,node1就释放了。

  6. 但是_next属于node1的成员,node1释放了,_next才会析构,而node1_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

图解:

在这里插入图片描述

那么我们如何解决此问题呢?

这里我们就来认识一下专门用来配合shared_ptr使用的指针——weak_ptr

先来看一下它的简单实现版本:

namespace my_ptr
{
	// 简化版本的weak_ptr实现
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

可以看到weak_ptr用于获得shared_ptr管理的资源的非拥有访问权。通过 weak_ptr,可以检查资源是否存在,weak_ptr不会增加资源的引用计数。

因此上述问题的解决方案就是:在引用计数的场景下,把节点中的_prev_next改成weak_ptr就可以了。

struct ListNode
{
	int _data;
	my_ptr::weak_ptr<ListNode> _prev;
	my_ptr::weak_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	my_ptr::shared_ptr<ListNode> node1(new ListNode);
	my_ptr::shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

这样node1->_next = node2;node2->_prev = node1;weak_ptr_next_prev不会增加node1node2的引用计数,很好的解决了这里的问题。

4.8 定制删除器

当使用智能指针时,会出现下面问题:

shared_ptr<int> sp1(new int[10]);

当调用sp1的析构函数时,默认调用的是delete,而new []应该调用delete [],我们该如何告诉编译器要使用的delete方式呢?

在这里插入图片描述

shared_ptr的构造函数有一个模板参数类型D可以让我们传参要调用的删除方法。

因此,我们可以这样使用:

shared_ptr<int> sp2(new int[10], [](int* ptr) { delete[] ptr; });
shared_ptr<FILE> sp3(fopen("test.txt", "r"), [](FILE* ptr) { fclose(ptr); });

拓展

除了会用,让我们想想shared_ptr该如何实现?

我们可以通过function包装器来定义成员函数去接收构造函数中的删除器参数。

namespace my_ptr
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		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)
		{}

		// function<void(T*)> _del;

		void release()
		{
			if (--(*_pcount) == 0)
			{
				//cout << "delete->" << _ptr << endl;
				//delete _ptr;
				_del(_ptr);

				delete _pcount;
			}
		}

		~shared_ptr()
		{
			release();
		}

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

		// sp1 = sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				++(*_pcount);
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* _pcount;

		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};
}

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

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

相关文章

计算机组成原理之机器:存储器之辅助存储器

计算机组成原理之机器&#xff1a;存储器之辅助存储器 笔记来源&#xff1a;哈尔滨工业大学计算机组成原理&#xff08;哈工大刘宏伟&#xff09; Chapter3&#xff1a;存储器之辅助存储器 3.1 概述 3.2 磁记录原理 通不同方向电流时磁化方向不同&#xff0c;由此区分写入…

vue 使用谷歌地图 @googlemaps/js-api-loader 进行模糊搜索

<template><div class"map"><div class"mapLeftStyle"><el-inputv-model"input"placeholder"请输入内容"class"controls"input"chnageinput"><i slot"prefix" class"e…

2007-2022年上市公司迪博内部控制评价缺陷数量数据

2007-2022年上市公司迪博内部控制评价缺陷数量数据 1、时间&#xff1a;2007-2022年 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、辖区、证监会行业、申万行业、是否存在财报内控重大缺陷、财报内控重大缺陷数量、是否存在财报内控重要缺陷、财报内…

Linux第73步_学习Linux设备树和“OF函数”

掌握设备树是 Linux驱动开发人员必备的技能&#xff01; 1、了解设备树文件 在3.x版本以前的Linux内核源码中&#xff0c;存在大量的“arc/arm/mach-xxx”和“arc/arm/plat-xxx”文件夹&#xff0c;里面很多个“.c”和“.h”文件&#xff0c;它们用来描述设备信息。而现在的A…

使用Portainer让测试环境搭建飞起来

Docker的用处不多加赘述&#xff0c;Docker目前有以下应用场景&#xff1a; 测试&#xff1a;Docker很适合用于测试发布&#xff0c;将 Docker 封装后可以直接提供给测试人员进行运行&#xff0c;不再需要测试人员与运维、开发进行配合&#xff0c;进行环境搭建与部署。 测试…

【考研数学】李林《880》vs 李永乐《660》完美使用搭配

没有说谁一定好&#xff0c;只有适不适合自身情况&#xff0c;针对自身弱点选择性价比才最高。 两者侧重点不同&#xff0c;660适合强化前期&#xff0c;弥补基础的不足&#xff0c;880适合强化后期&#xff0c;题型全面&#xff0c;提高我们对综合运用知识的能力。 选择习题…

动手学深度学习PyTorch版

基本的数据操作 import torch # 创建一个行向量&#xff0c;默认为从0开始的12个整数 # n维数组也称为张量 x torch.arange(12) x # 张量的形状 x.shape# 张量的大小,张量所有元素的个数 x.numel()#修改张量的形状 x x.reshape(3,4)#生成形状为3*4的两个向量&#xff0c;向…

C#实现归并排序算法

C#实现归并排序算法 以下是 C# 中的归并排序算法实现示例&#xff1a; using System;class MergeSortAlgorithm {// 合并两个子数组static void Merge(int[] arr, int left, int mid, int right){// 计算左子数组和右子数组的长度int n1 mid - left 1;int n2 right - mid;/…

力扣--滑动窗口438.找到字符串中所有字母异位词

思路分析&#xff1a; 使用两个数组snum和pnum分别记录字符串s和p中各字符出现的次数。遍历字符串p&#xff0c;统计其中各字符的出现次数&#xff0c;存储在pnum数组中。初始化snum数组&#xff0c;统计s的前m-1个字符的出现次数。从第m个字符开始遍历s&#xff0c;通过滑动窗…

STM32(14)USART

USART:一种片上外设&#xff0c;用来实现串口通信&#xff0c;就是stm32内部的串口 USART简介 串并转换电路 串行通信和并行通信 串行&#xff1a;一根数据线&#xff0c;逐个比特位发送 为什么要串并转换 移位寄存器 USART的基本模型 通过查询SR&#xff08;状态寄存器&…

w022郑州大学招新赛选拔赛

A-SW的与众不同数组_2022学年第一学期郑州大学ACM招新赛&选拔赛 (nowcoder.com) #include <bits/stdc.h> #define int long long using namespace std;void solve(){int n;cin >> n;vector<int> v;for(int i 1; i < n; i){int x;cin >> x;v.p…

Java集合面试题(day 02)

&#x1f4d1;前言 本文主要是【JAVA】——Java集合面试题的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&am…

一文扫荡,12个可视化图表js库,收藏备用。

hello&#xff0c;我是贝格前端工场&#xff0c;可视化图表在web前端开发中经常碰到&#xff0c;是不是很疑惑这些炫酷的图表是怎么实现的&#xff0c;其实是通过js库开发的&#xff0c;本文带来12个javascript库的介绍&#xff0c;欢迎关注我&#xff0c;阅读精彩内容。 一、什…

【新版Hi3521DV200处理器性能】

新版Hi3521DV200处理器性能 Hi3521DV200是针对多路高清/超高清&#xff08;1080p/4M/5M/4K&#xff09;DVR产品应用开发的新一代专业SoC芯片。Hi3521DV200集成了ARM Cortex-A7四核处理器和性能强大的神经网络推理引擎&#xff0c;支持多种智能算法应用。同时&#xff0c;Hi352…

Media Encoder 2024:未来媒体编码的新纪元 mac/win版

随着科技的飞速发展&#xff0c;媒体内容已成为我们日常生活中不可或缺的一部分。为了满足用户对高质量视频内容不断增长的需求&#xff0c;Media Encoder 2024应运而生&#xff0c;它凭借卓越的技术和创新的特性&#xff0c;重塑了媒体编码的未来。 Media Encoder 2024软件获…

绝赞春招拯救计划

huihut/interview: &#x1f4da; C/C 技术面试基础知识总结&#xff0c;包括语言、程序库、数据结构、算法、系统、网络、链接装载库等知识及面试经验、招聘、内推等信息。This repository is a summary of the basic knowledge of recruiting job seekers and beginners in t…

数据结构与算法-插值查找

引言 在计算机科学的广阔天地中&#xff0c;数据结构和算法扮演着至关重要的角色。它们优化了信息处理的方式&#xff0c;使得我们在面对海量数据时能够高效、准确地进行检索与分析。本文将聚焦于一种基于有序数组且利用元素分布规律的查找算法——插值查找&#xff08;Interpo…

活动预告|听云猿生数据创始人 CEO 曹伟分享云数据库行业十余年经验总结

3月16日&#xff0c;KubeBlocks 将携手 OceanBase 开源社区、AutoMQ 带来《LLMs 时代下的企业数据管理与降本增效之路》主题 meetup&#xff0c;扫描下方二维码&#xff0c;即刻报名&#x1f447;。 云猿生数据创始人 & CEO 曹伟将带来《KubeBlocks&#xff1a;把所有数据…

freeRTOS20240308

1.总结任务的调度算法&#xff0c;把实现代码再写一下 2.总结任务的状态以及是怎么样进行转换的

Flutter使用auto_updater实现windows/mac桌面应用版本升级功能

因为windows应用一般大家都是从网上下载的&#xff0c;后期版本肯定会更新&#xff0c;那用flutter开发windows应用&#xff0c;怎么实现应用内版本更新功能了&#xff1f;可以使用auto_updater库&#xff0c; 这个插件允许 Flutter 桌面 应用自动更新自己 (基于 sparkle 和 wi…