C++学习进阶:智能指针

目录

前言:

1.知识引入

1.1.异常安全问题

1.2.RALL和智能指针雏形

2.智能指针的发展

2.1.auto_ptr的引入

2.2.unique_ptr的引入 

2.3.救世主shared_ptr(重点)

2.4.weak_ptr的引入(重点)

2.5.测试函数

3.定制删除器

3.1.shared_ptr中删除的漏洞

3.2.定制删除器的实现


前言:

这是一篇又臭又长又精华的博客,需要每一个模块认真学习,仔细理解,这一部分也是C++面试常考的内容,那么废话不多说,just do it!

在这一篇章中我们需要重点学习:RAII思想、shared_ptr的实现和相关问题

1.知识引入

1.1.异常安全问题

在C++学习进阶:异常-CSDN博客我们引入这一段代码是为了提出异常的重新抛出这个作法,但是异常重新抛出一定就是解决异常安全问题的最优法吗?

#include<iostream>
#include<string.h>
using namespace std;int Exception(int i)
{int arr[5] = { 0, 1, 2, 3, 4 };if (i >=5){throw "数组越界";}else{return arr[i];}
}
void Print()
{int* array = new int[10];try{int i = 0;cin >> i;cout << Exception(i) << endl;// 不出现异常也是需要释放空间delete[] array;cout<<"释放array资源"<<endl;}catch(...){    delete[] array;cout<<"释放array资源"<<endl;}
}int main()
{try{Print();}catch (const char* errmsg){cout << errmsg << endl;}catch (...){cout << "未知异常" << endl;}
}

我们看回这段代码,对于Print中,我们发现当不发生异常时我们也是需要继续的释放掉array这段空间,也就是在代码简洁上重新抛出显然是不太适合这个场景的。那么如何解决这个问题呢?先不着急,我们先引入一个新概念RAII。

1.2.RALL和智能指针雏形

RAII(Resource Acquisition Is Initialization)是C++编程中的一种技术,用于管理资源(如内存、文件句柄、网络连接等)的生命周期。RAII的核心思想是,将资源的获取(即初始化)与资源的释放(即析构)绑定到对象的生命周期上。通过这样做,可以确保资源在不再需要时得到正确的释放,从而避免资源泄漏和其他相关问题。

显然十分抽象,接下来我们结合上一段代码来体会一下RAII思想。

#include<iostream>using namespace std;// 智能指针的引入
template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;cout << "~SmartPtr() 已经释放传入的指针资源" << endl;}
private:T* _ptr;
};int Exception(int i)
{int arr[5] = { 0, 1, 2, 3, 4 };if (i >= 5){throw "数组越界";}else{return arr[i];}
}
void Print()
{int* array = new int[10];SmartPtr<int> s(array);int i = 0;cin >> i;cout << Exception(i) << endl;}int main()
{try{Print();}catch (const char* errmsg){cout << errmsg << endl;}catch (...){cout << "未知异常" << endl;}
}
  1. 我们引入一个类,来实现传入指针的释放,这里我们传入了外部资源的指针,当我们在类内部释放这个指针,外部资源的指针同时也被释放(共用一块地址)。
  2. 而这个类SmartPtr就是智能指针,不过这是我们自己手搓的一个雏形,功能较少
  3. RAII思想:就是将具有资源的、且不方便管理资源的对象绑定到容易管理资源的另一个对象上,来实现资源的管理。而智能指针是这个思想的一种体现

另外我们通过黑窗口打印,也发现无论出现异常还是不出现异常,这个指针的资源最终都得到释放

 通过上面的例子我们大概知道智能指针本质上就是实现一个对指针进行操作的对象,而在C++中封装为了在原生指针的基础上的一个具有多样功能的指针类,因而可以构造出“智能”指针对象……

2.智能指针的发展

智能指针是C++中用于自动管理内存生命周期的类模板。它们被设计为像原生指针一样使用,但提供了额外的功能来确保在适当的时候自动删除所指向的对象。智能指针通过封装原生指针并重载相关的操作符(如*->)来实现这一点,使得它们可以像使用普通指针一样方便。

智能指针主要有四种,分别是:auto_ptr(C++98标准,已弃用)、unique_ptr(C++11标准)、shared_ptr(C++11标准)和weak_ptr(C++11标准) ,为什么需要这几种智能指针呢?接下来我们通过几个场景来讲一下智能指针的发展史。

2.1.auto_ptr的引入

template<class T>
class SmartPtr
{// RAII思想
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;cout << "~SmartPtr() 资源已经释放" << endl;}// 模拟指针功能// 实现解引用T& operator*() { return *_ptr; }// 对多成员类型访问T* operator->() { return _ptr; }
private:T* _ptr;
};
int main()
{zhong::SmartPtr<int> sp(new int(1));cout << *sp << endl;*sp = 2;cout << *sp << endl;// zhong::SmartPtr<int> sp2(sp);
}

首先我们定义了一个智能指针类,然后我们可以对他进行操作,比如值的修改和打印,左上角为注释了// zhong::SmartPtr<int> sp2(sp);调用函数的结果。

因为我们是通过共用同一块地址空间来实现智能指针的,而当我们析构时,因为有两个对象 ,所以这两个对象都需要被析构,而用的是同一份地址,也就是这块地址被析构了两次,因此崩溃了。

这里就是智能指针雏形的缺陷所在,为了解决这个问题C++98对这个智能指针做了一定的优化。

// C++98的智能指针// 进行管理权的转移template<class T>class auto_ptr{	public:auto_ptr(T* ptr):_ptr(ptr){}// 管理权转移 将旧对象的指针置空 防止二次析构auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}~auto_ptr(){delete _ptr;cout << "~auto_ptr() 资源已经释放" << endl;}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }private:T* _ptr;};

 我们通过它的拷贝构造函数可知,对于拷贝对象,直接把自身地址传给被拷贝对象,然后将自身地址置为空。这样子我们解决:同一份地址被析构两次的问题。通过析构一份为空的地址和析构一份具有意义的地址来实现的。


2.2.unique_ptr的引入 

但是这样子会不会有点捞!!!实际场景下我们可能同时对这两个对象都需要进行操作!而其中一个对象我们已经置空了,那么就会产生对空指针的使用错误,显然是不符合实际应用。这时unique_ptr就横空出世了!

	template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){delete _ptr;cout << "~unique_ptr() 资源已经释放" << endl;}unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;T& operator*() { return *_ptr; }T* operator->() { return _ptr; }private:T* _ptr;};

为了解决拷贝构造导致的共用同一块地址析构两次和auto_ptr将其中一个对象值为空的这两个问题,unique_ptr察觉到了问题的根本来源,就是拷贝构造的问题,所以秉着不失败就是最大的成功这个伟大真理,直接删除了智能指针的拷贝构造功能。 


2.3.救世主shared_ptr(重点)

这样确实是能够从根源上解决问题,但是这是合理的吗??? 所以这时主角shared_ptr闪亮登场!

	// 合理的智能指针template<class T>class shared_ptr{public:shared_ptr(T* ptr):_ptr(ptr), _p_r_count(new int(1)){}~shared_ptr(){release();cout << "~shared_ptr() 资源已经释放" << endl;}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_p_r_count(sp._p_r_count){++(*_p_r_count);}shared_ptr<T>& operator=(const shared_ptr<T>& sp) {// 自己给自己赋值if (sp._ptr != this->_ptr){// = 本质上替换左边的智能指针// 需要考虑是否需要释放release();_ptr = sp._ptr;_p_r_count = sp._p_r_count;++(*_p_r_count);return *this;}}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }private:// 引用计数为0时对资源的释放void release(){// 当引用计数为0时才释放资源// 判断的同时已经对引用计数--了if (--(*_p_r_count) == 0){delete _ptr;delete _p_r_count;}}private:T* _ptr;// 维护引用计数的指针int* _p_r_count;};

在讲shared_ptr是怎么实现之前,我们先来回顾智能指针雏形的问题 

退出当前调用栈时,两个对象对应的同一块空间无法避免析构两次!!!

 为了解决这个问题:shared_ptr维护了一个int*类型的指针,用来存放这个智能指针对象的地址被引用了几次,即这个地址维护了几个智能指针的问题。

具体实现:

  1. 通过析构某一个智能指针对象时,先判断当前智能指针的引用计数是否大于1,如果大于1说明这个地址仍被其他对象使用中,不能释放这个地址,但是可以释放当前智能指针对象,并将这个智能指针的引用数减一。
  2. 我们在进行拷贝构造,也就是增加该地址对应的智能指针对象时,需要增加引用计数。

这样子我们就完善了智能指针并且解决了问题。


我们已经知道用引用计数可以解决问题,但是为什么一定是int*类型呢?我们画个图来体会一下


那么假如只是在智能指针中维护int类型呢?这时由于对象不同,所以他们各自的int也是不同的,其他的智能指针对象就算拥有同一块_ptr,但是int的地址却是各自开辟的,无法实现三者独立操作。


那么可能有人会提出:如果维护static int这个成员变量时,在静态区中独立开辟,总是可以满足独立操作的需求了吧? 当然如果只是满足独立操作的需求,通过静态区是能够实现的,然而如果我们有另一块内存开辟的资源,就会导致上一块资源对应的static int和这一块对应的static int冲突。


2.4.weak_ptr的引入(重点)

shared_ptr的缺陷:

然而shared_ptr并不是万能的,shared_ptr会出现循环引用的问题,也就是两个智能指针内部维护智能指针对象时可能会出现互相进行指向的问题。

在这段代码中我们直接使用C++原生的shared_ptr来展示shared_ptr存在的缺陷

// shared_ptr的缺陷
struct ListNode
{int _val;std::shared_ptr<ListNode> _next;std::shared_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{std::shared_ptr<ListNode> sp1 = new ListNode;std::shared_ptr<ListNode> sp2 = new ListNode;// 这里会出现循环引用sp1->_next = sp2;sp2->_prev = sp1;
}

运行这段代码时,我们发现我们显性的进行ListNode的析构,但是却没有析构的打印,而是没有任何输出结果的正常退出程序。这也就表示了这一段代码中的ListNode结构体并没有被释放资源,存在内存泄漏的问题!!!

为什么会出现这种问题,已经循环引用是什么意思呢? 如图所示:

当我们运行到函数结束前,我们会出现左图的现象,智能指针的指向图,而当函数退出时,乡音函数内部的sp1、sp2作为临时变量,编译器自动调用它的析构函数进行回收,然而此时我们发现这两个ListNode的资源仍然未被释放,这是为什么呢?

因为ListNode内部维护的智能指针的引用计数仍然不为0,这里因为上面的ListNode中的智能指针指向下一个ListNode,而下面的智能指针指向上一个ListNode,即:如果想要ListNode资源得到释放,就需要内部维护的互相指向的智能指针的引用计数为0,但是他们各自为1。

那么现在我们就要研究一下_next和_prev什么时候释放的问题?

  1. 对于_next而言,如果他需要被释放,也就是引用计数为0,那么就是需要下面的ListNode节点被释放,这时维护_next的ListNode(上面的ListNode)才会释放。
  2. 然而下面的ListNode节点被释放的条件,就是_prev的引用计数为0,而_prev引用计数为0的条件就是上面的ListNode节点被释放。

这时是不是发现:1的条件需要2为结果,2的条件需要1为结果。就产生了因果循环!

所以这时我们发现shared_ptr并不是万能的,他会出现循环引用的问题! 这时C++11就引入了weak_ptr这一个智能指针来解决这个问题。

// 循环引用的问题就是ListNode中维护了shared_ptr指针
struct ListNode
{int _val;std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{std::shared_ptr<ListNode> n1 (new ListNode);std::shared_ptr<ListNode> n2 (new ListNode);// 这里会出现循环引用n1->_next = n2;n2->_prev = n1;
}

这时问题就完美解决了!!! 

weak_ptr的实现:

  1. weak_ptr是专门为了解决shared_ptr循环引用问题而产生的
  2. 不支持RAII
  3. 不参与shared_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;}weak_ptr<T>& operator=(const weak_ptr<T>& sp){_ptr = sp._ptr;return *this;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;// 注意weak_ptr也是拥有引用计数的// 这里我们只是实现简单的逻辑,就不实现了};

 在这一部分中,我们没有实现weak_ptr的引用计数,只是根据std库大致实现了,解决循环引用的weak_ptr具有的功能


2.5.测试函数

附:为了不与库中的智能指针冲突,建议在自己的命名空间实现这几个智能指针

void test1()
{zhong::SmartPtr<int> sp(new int(1));cout << *sp << endl;*sp = 2;cout << *sp << endl;//zhong::SmartPtr<int> sp2(sp);
}
void test2()
{zhong::auto_ptr<int> sp(new int(1));cout << *sp << endl;*sp = 2;cout << *sp << endl;zhong::auto_ptr<int> sp2(sp);cout << *sp2 << endl;// auto_ptr对原对象进行访问 会出现访问空指针错误// cout << *sp << endl;
}
void test3()
{zhong::unique_ptr<int> sp(new int(1));cout << *sp << endl;*sp = 2;cout << *sp << endl;// unique_ptr直接就不允许拷贝构造// zhong::unique_ptr<int> sp2(sp);}
void test4()
{zhong::shared_ptr<int> sp(new int(1));*sp = 2;zhong::shared_ptr<int> sp2(sp);cout << "sp对应值为:" << *sp << endl;cout << "sp2对应值为:" << *sp2 << endl;
}

3.定制删除器

这里我们主要介绍一下shared_ptr的定制删除器,因为其他的智能指针实现也是一致的。为什么需要定制删除器?我们先看回我们的release函数

3.1.shared_ptr中删除的漏洞

void release()
{// 当引用计数为0时才释放资源// 判断的同时已经对引用计数--了if (--(*_p_r_count) == 0){delete _ptr;delete _p_r_count;}
}

我们发现我们释放智能指针指向资源的指针是通过delete _ptr,但是实际场景中我们传入的可能是数组对象,那么这时就需要delete[ ] _ptr,不然就仅仅释放了数组对象对应的首个对象的资源,数组后面的资源并没有的到释放。也就是说release有点写死了。

int main()
{// 我们传入数组对象给shared_ptr,然后析构时程序崩溃std::shared_ptr<string> n1(new string[10]);
}

C++库中是删除数组指针,通过传入删除器对象来实现的,而这个删除器一般就是函数指针、仿函数、lambda表达式这种可调用对象 。需要注意的是:类型需要匹配。

// 定制删除器
template<class T>
struct Delete
{void operator()(T* ptr){delete[] ptr;}
};
int main()
{// 通过仿函数std::shared_ptr<string> n1(new string[10], Delete<string>());// 通过lambda表达式std::shared_ptr<string> n2(new string[10], [](string* ptr) {delete[] ptr; });
}

3.2.定制删除器的实现

接下来我们学习一下shared_ptr是如何实现定制删除器的。

  1. 我们增加数组指针和传入删除器的构造函数
  2. 接着需要重写一下release函数的逻辑,增加删除器删除的代码实现
  3. 用包装器统一传入的函数指针、仿函数、lambda表达式类型
// 合理的智能指针
template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _p_r_count(new int(1)){}template<class D>shared_ptr(T* ptr, D del): _ptr(ptr), _p_r_count(new int(1)), _del(del){}~shared_ptr(){release();// cout << "~shared_ptr() 资源已经释放" << endl;}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _p_r_count(sp._p_r_count){++(*_p_r_count);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){// 自己给自己赋值if (sp._ptr != this->_ptr){// = 本质上替换左边的智能指针// 需要考虑是否需要释放release();_ptr = sp._ptr;_p_r_count = sp._p_r_count;++(*_p_r_count);return *this;}}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }// 返回引用计数长度int use_count() const { return *_p_r_count; }// 提供指向资源的地址T* get() const { return _ptr; }private:// 引用计数为0时对资源的释放void release(){// 当引用计数为0时才释放资源// 判断的同时已经对引用计数--了if (--(*_p_r_count) == 0){// 通过删除器删除指向对象的指针_del(_ptr);delete _p_r_count;}}private:T* _ptr;// 维护引用计数的指针int* _p_r_count;// 增加包装器来接受定制删除器// 并且默认初始化,当没有传入删除器,也就是删除普通指针function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

 对这段代码我们只要求能够理解即可,需要注意的点:

  1. 包装器我们需要进行初始化,当没有传入删除器时,我们通过lambda表达式将删除器设置为删除普通指针,当传入删除器时,我们在release中通过可调用的删除器对象_delete删除。
  2. 传入含有资源指针类型和删除器的构造函数,也需要满足shared_ptr正常的功能和拥有的成员变量

那么到了这里我们智能指针的篇章就告一段落了。

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

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

相关文章

学习Python先从了解Python开始

Python是一种高级编程语言&#xff0c;它的语法简洁易读&#xff0c;功能强大&#xff0c;应用领域广泛。Python不仅适用于数据科学、机器学习、Web开发等领域&#xff0c;还可以用于自动化脚本编写、游戏开发等。在本文中&#xff0c;我们将探讨Python的特点、应用领域以及未来…

网工内推 | 兴业银行总行正编,科技运维部,硕士以上学历

01 兴业银行 招聘岗位&#xff1a;安全渗透专家 职责描述&#xff1a; 1.负责牵头组织本行红蓝对抗、攻防演练等工作&#xff1b; 2.负责牵头制定有效的渗透测试方案&#xff0c;开展对本行防御体系的验证工作&#xff1b; 3.负责牵头组织本行各类应用系统的渗透测试与漏洞扫…

图神经网络与分子表征:7. LEFTNet

在执行性质预测任务时&#xff0c;我们需要考虑两个问题&#xff1a;1. 如何正确的将图结构进行编码&#xff1f;2. 如何汇聚编码信息预测整个分子的任务&#xff1f; LEFTNet 就是通过回答上述问题来进行模型设计的。 原文地址 算法设计 原文中&#xff0c;作者定义了三个图…

小米安卓春招面试一面

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 1.多态 2.hashmap&#xff0c;hashtable和concurrenthashmap&#xff0c;问的定义实现和区别 3.jvm的运行…

02 - ArcGIS For JavaScript-矢量数据的符号化处理(Symbol)

文章目录 综述Symbol的分类Point的符号化Point符号化为二维几何&#xff1a;Point位图符号化&#xff1a;Point的三维结合符号化Point 符号化为GLTF模型 PolylineSymbol-线符号化基本样式管道样式墙体样式条带样式方管样式 PolygonSymbol-面符号化水面效果拉伸效果填充效果 Mes…

PCB----Allegro软件使用小技巧

1.修改画好的同面积&#xff1a; 2.修改铜的网络&#xff0c;或者铺铜铺错网络了&#xff1a; 点击需要修改的铜 在点击要修改成的网络 3.铺铜需要注意&#xff1a; 铜片和铜片之间间隔2个点&#xff08;点设置6.25&#xff0c;如下图1-2&#xff1a;&#xff09; 每一个单独的…

差速机器人模型LQR 控制仿真——路径模拟

LQR路径跟踪要求路径中带角度&#xff0c;即坐标&#xff08;x,y,yaw&#xff09;&#xff0c;而一般我们的规划出来的路径不带角度。这里通过总结相关方法&#xff0c;并提供一个案例。 将点路径拟合成一条完整的线路径算法 将点路径拟合成一条完整的线路径是一个常见的问题…

每日一题 — 最小覆盖子串

76. 最小覆盖子串 - 力扣&#xff08;LeetCode&#xff09; 解法一&#xff1a;暴力遍历哈希表 解法二&#xff1a;滑动窗口哈希表 定义left和right初始化为零&#xff0c;固定left&#xff0c;先向右遍历right&#xff0c;放到哈希表中这个时候我们需要统计有效字符的个数&…

五种主流数据库:集合运算

关系型数据库中的表与集合理论中的集合类似&#xff0c;表是由行&#xff08;记录&#xff09;组成的集合。因此&#xff0c;SQL 支持基于数据行的各种集合运算&#xff0c;包括并集运算&#xff08;Union&#xff09;、交集运算&#xff08;Intersect&#xff09;和差集运算&a…

chromedriver最新版下载地址

地址1.百度网盘 链接(提取码&#xff1a;2vo3)&#xff1a;百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com…

数据很重要,ASM磁盘组损坏,使用AMDU来抢救

欢迎关注“数据库运维之道”公众号&#xff0c;一起学习数据库技术! 本期将为大家分享“数据很重要&#xff0c;ASM磁盘组损坏&#xff0c;使用AMDU来抢救”的处置案例。这个案例对个人来说比较经典&#xff0c;下面我将把自己的处理思路进行整理与总结。 环境信息&#xff1…

安装Milvus的可视化工具Attu教程

提供两种方式来安装可视化工具Attu 一、docker安装 # 执行命令&#xff0c;加个 -d 在后台运行 docker run -d -p 8000:3000 -e MILVUS_URL127.0.0.1:19530 zilliz/attu:v2.2.8 至此安装完成&#xff01; 浏览器输入地址 http:127.0.0.1:8000即可访问 Attu主页 如果拉取最新…

大数据操作第二天

文章目录 大数据命令的方式现在有三个命令的方式 启动一个计算圆周率的jar包方式什么是文件系统数据元数据传统的存储方式分布式存储方式元数据记录文件位置信息副本机制的方式 hafs 存储方式shell 操作大数据的方式创建目录查看文件目录下的方式上传文件的方式 大数据命令的方…

2024年国内五大企业邮箱,哪个最靠谱?

电子邮件是企业办公的重要手段&#xff0c;目前我国五大企业的邮箱都有Zoho Mail公司邮箱、腾讯企业邮箱、阿里企业邮箱、网易企业邮箱、263公司邮箱。可是哪个电子邮件最可靠呢&#xff1f;可靠的企业邮箱必须要安全性高、稳定性高&#xff0c;能够保护企业的隐私不被泄露&…

Git常见命令行操作和IDEA图形化界面操作

设置Git用户名和标签 在安装完Git以后需要设置用户和签名&#xff0c;至于为什么要设置用户签名可以看一下这篇文章【学了就忘】Git基础 — 11.配置Git用户签名说明 - 简书 (jianshu.com) 基本语法&#xff1a; git config --global user.name 用户名 git config --global u…

HTML学习笔记:(一)基础方法

Html格式 里面文件使用平台为&#xff1a;w3school 1、基础功能&#xff1a; <html><head> <title>这是我的第一个html页面,会显示在浏览器的标题栏中</title> </head> <!--修改背景颜色 --> <body bgcolor"yellow"> …

浅浅了解一下 LibTorch

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ LibTorch 是 PyTorch 提供的一个二进制发行版&#xff0c;包含了所有必要的头文件、库和 CMake 配置文件&#xff0c;便于开发者依赖 PyTorch 开发应用。用户可以从 PyTorch 官网下载包含最新 LibTorch…

Vitis HLS 学习笔记--scal 函数-探究

目录 1. Vitis HLS重器-Vitis_Libraries 2. 初识scal() 3. 函数具体实现 3.1 变量命名规则 3.2 t_ParEntries解释 3.3 流类型详解 3.4 双重循环 4. 总结 1. Vitis HLS重器-Vitis_Libraries 在深入探索Vitis HLS&#xff08;High-Level Synthesis&#xff09;的旅程中&…

【Leetcode】代码随想录Day16|二叉树3.0

文章目录 104 二叉树的最大深度559 n叉树的最大深度111 二叉树的最小深度222 完全二叉树的节点个数 104 二叉树的最大深度 递归法&#xff1a;无论是哪一种顺序&#xff0c;标记最大深度 class Solution(object):def depthHelper(self, root, depth):if root:depth 1left_de…

HWOD:合并整型数组

一、知识点 合并整型数组目前有两种方法 合并数组并不一定需要真正的合并 1、下意识的方法 对两个整型数组分别排序&#xff0c;然后合并 2、不排序的方法 遍历两个数组&#xff0c;找出最小值&#xff0c;输出最小值。将两个数组中与最小值相等的位置置为超大值 重复以…