【C++】智能指针(万字详解)
🌈欢迎来到C++专栏~~智能指针
- (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
- 目前状态:大三非科班啃C++中
- 🌍博客主页:张小姐的猫~江湖背景
- 快上车🚘,握好方向盘跟我有一起打天下嘞!
- 送给自己的一句鸡汤🤔:
- 🔥真正的大师永远怀着一颗学徒的心
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
- 🎉🎉欢迎持续关注!

文章目录
- 🌈欢迎来到C++专栏~~智能指针
- 一. 为什么需要智能指针?
- 😎智能指针的原理
- 二. C++中的智能指针登场
- 🥑auto_ptr
- 🥑unique_ptr
- 🥑shared_ptr
- 🎨基本设计
- 🎨模拟实现
- 🎨线程安全问题
- 🎨定制删除器
- 🥑weak_ptr
- 💦shared_ptr的循环引用问题
- 💦weak_ptr解决循环引用
- C++11和boost中智能指针的关系
- 常见面试题
- 📢写在最后

一. 为什么需要智能指针?
下面我们先分析一下下面这段程序有没有什么内存方面的问题?提示一下:注意分析MergeSort函数中的问题
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;//p1抛异常,直接跳catch,没毛病int* p2 = new int;//p2跑异常,程序从这一步跳catch,p1无法释放,资源泄露cout << div() << endl; // div抛异常,调到catch,p1, p2无法释放,资源泄露delete p1;delete p2;
}int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
问题分析:上面的问题分析出来我们发现有什么问题?
- 会有严重的内存泄漏的问题:如果用户输入的除数为0,那么div函数中就会抛出异常,这时程序的执行流会直接跳转到主函数中的catch块中执行,最终导致func函数中申请的内存资源没有得到释放
对此我们需要重新捕获异常,捕获后先将之前申请的内存资源释放,然后再将异常重新抛出
还有一种方法就是:智能指针
//利用RAII思想设计delete资源的类
template<class T>
class Smartptr
{
public:Smartptr(T* ptr):_ptr(ptr){}~Smartptr(){delete _ptr;}T& operator*() //解引用{return *ptr;}T* operator->() //自定义类型{return ptr;}private:T* _ptr;
};void Func()
{int* p1 = new int;//p1抛异常,直接跳catch,没毛病Smartptr<int> sp1(p1);//栈帧结束会调用析构函数Smartptr<int> sp2(new int);cout << div() << endl; // div抛异常,调到catch,p1, p2无法释放,资源泄露
}
代码中将申请到的内存空间交给了一个SmartPtr对象进行管理
- 构造的时候,
SmartPtr将传入的需要被管理的内存空间保存起来 - SmartPtr对象析构时,SmartPtr的析构函数中会自动将管理的内存空间进行释放
- 为了让SmartPtr对象能够像原生指针一样使用,还需要对
*和->运算符进行重载
如此一来,无论是正常返回,抛异常的返回,只要SmartPtr对象的生命周期结束就会调用其对应的析构函数,进而完成内存资源的释放。
😎智能指针的原理
实现智能指针时需要考虑以下三个方面的问题:
- 在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源
- 对
*和->运算符进行重载,使得该对象具有像指针一样的行为 - 智能指针的拷贝问题
运用了RAII的思想:RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术
我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
为什么要解决智能指针对象的拷贝问题呢?
对于当前实现的SmartPtr类,如果用一个SmartPtr对象来拷贝构造另一个SmartPtr对象,或是将一个SmartPtr对象赋值给另一个SmartPtr对象,都会导致程序崩溃
int main()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2(sp1); //拷贝构造SmartPtr<int> sp3(new int);SmartPtr<int> sp4(new int);sp3 = sp4; //拷贝赋值return 0;
}
编译器默认生成的拷贝构造对内置类型完成值拷贝,即浅拷贝,因此用 sp1 拷贝构造 sp2 后,相当于 sp1 和 sp2 管理了同一块内存空间,当 sp1 和 sp2 析构时就会导致这块空间被释放两次
- 那么这里可以使用深拷贝吗?不可以,违背了功能的需求
- 智能指针就是要模拟原生指针的行为,当我们将一个指针赋值给另一个指针时,目的就是让这两个指针指向同一块内存空间,所以这里本就应该进行浅拷贝,但单纯的浅拷贝又会导致空间被多次释放,因此根据解决智能指针拷贝问题方式的不同,从而衍生出了不同版本的智能指针
二. C++中的智能指针登场
🥑auto_ptr
💥 管理权转移:资源管理权转移,不负责任的拷贝,会导致被拷贝对象悬空
int main()
{auto_ptr<A> ap1(new A);ap1->_a1++;ap1->_a2++;auto_ptr<A> ap2(ap1);return 0;
}

但一个对象的管理权转移后也就意味着,该对象不能再用对原来管理的资源进行访问了,否则程序就会崩溃,因此使用auto_ptr之前必须先了解它的机制,否则程序很容易出问题,很多公司也都明确规定了禁止使用auto_ptr
😎auto_ptr的模拟实现
实现步骤如下:
- 1️⃣在构造函数中获取资源,在析构函数中释放资源,利用对象的生命周期来控制资源(
RAII) - 2️⃣对
*和->运算符进行重载,使auto_ptr对象具有指针一样的行为 - 3️⃣在拷贝构造函数中,用传入对象管理的资源来构造当前对象,并将传入对象管理资源的指针置空
- 4️⃣在拷贝赋值函数中,先将当前对象管理的资源释放,然后再接管传入对象管理的资源,最后将传入对象管理资源的指针ap置空
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; //管理权转移后,ap置空}auto_ptr<T>& operator=(auto_ptr<T>& ap){//检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;//转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}~auto_ptr(){delete _ptr;}//像指针一样使用T& operator*() //解引用{return *_ptr;}T* operator->() //自定义类型{return _ptr;}private:T* _ptr;
};
🥑unique_ptr
C++11中引入的智能指针,unique_ptr通过 防止拷贝(+ delete )的方式解决智能指针的拷贝问题
简单粗暴,不让拷贝
void test_unique_ptr()
{std::unique_ptr<A> up1(new A);//std::unique_ptr<A> up2(up1);//出错
}
但是总会有需要拷贝的场景吧
模拟实现如下:
- 在构造函数中获取资源,在析构函数中释放资源,利用对象的生命周期来控制资源
- 对
*和->运算符进行重载,使unique_ptr对象具有指针一样的行为 - 用C++98的方式将拷贝构造函数和拷贝赋值函数声明为私有(只声明不实现),或者用
C++11的方式在这两个函数后面加上=delete,防止外部调用
template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr = nullptr):_ptr(ptr){}//防止拷贝unique_ptr(unique_ptr<T>& ap) = delete;unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;~unique_ptr(){delete _ptr;}//像指针一样使用T& operator*() //解引用{return *_ptr;}T* operator->() //自定义类型{return _ptr;}private:T* _ptr;
};
如果面试官要我们现场手撕,那我们就撕一个unique_ptr
🥑shared_ptr
🎨基本设计
是通过引用计数的方式来实现多个shared_ptr对象之间共享资源
- 每一个被管理的资源都有一个对应的引用计数,通过这个引用计数记录着当前有多少个对象在管理着这块资源
- 当新增一个对象管理这块资源时则将该资源对应的引用计数进行
++,每个对象释放时,--计数 - 最后一个析构的对象,释放资源

通过这种引用计数的方式就能支持多个对象一起管理某一个资源,也就是支持了智能指针的拷贝,并且只有当一个资源对应的引用计数减为0时才会释放资源,因此保证了同一个资源不会被释放多次
void test_shared_ptr()
{ljj::shared_ptr<A> sp1(new A);ljj::shared_ptr<A> sp2(sp1);sp1->_a1++;sp1->_a2++;std::cout << sp2->_a1 << ":" << sp2->_a2 << std::endl;//1 1sp2->_a1++;sp2->_a2++;std::cout << sp1->_a1 << ":" << sp1->_a2 << std::endl;//2 2
}

🎨模拟实现
shared_ptr的模拟实现步骤如下:
- 1️⃣增加一个成员变量
count,表示智能指针对象管理的资源对应的引用计数 - 2️⃣在构造的时候获取资源,并将count设置为1, 说明只有一个新建的对象在管理
- 3️⃣拷贝构造的时候,传入对象一起管理它管理的资源,同时将该资源对应的count
++ - 4️⃣在拷贝赋值函数中,先将当前对象管理的资源对应的引用计数
--(如果减为0则需要释放),然后再与传入对象一起管理它管理的资源,同时需要将该资源对应的引用计数++(看下图) - 5️⃣析构时候需要将
count--,如果减为0,彻底释放资源 - 6️⃣对
*和->运算符进行重载,使shared_ptr对象具有指针一样的行为

template<class T>
class shared_ptr
{
public://RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr),_pCount(new int(1)) //构造的时候设为1{}//拷贝构造shared_ptr(shared_ptr<T>& sp):_ptr(sp._ptr),_pCount(sp._pCount) //把引用指针给与,共同管理{(*_pCount)++;}// sp1 = sp5shared_ptr<T>& operator=(shared_ptr<T>& sp){//防止同一块资源之间赋值if (_ptr != sp._ptr){//sp的资源-- 并且判断是否要置空if ((*_pCount)-- == 0){cout << "delete: " << _ptr << endl;delete _ptr;delete _pt;}//一起管理新资源,++计数_ptr = sp._ptr;_pCount = sp._pCount;(*_pCount)++;return *this;}}~shared_ptr(){if (--(*_pCount) == 0 && _ptr){std::cout << "Delete" << _ptr << std::endl;delete _ptr;delete _pCount;}}//像指针一样使用T& operator*() //解引用{return *_ptr;}T* operator->() //自定义类型{return _ptr;}private:T* _ptr;int* _pCount; // 引用计数//int count;
};
为什么引用计数要设计成指针?放在堆区
首先count不能设置成一个int类型的成员变量,这意味着每一个对象都有属于自己的count,如果多个对象要管理一个资源的时候,岂不是乱套了?

还有就是count也不能定义成一个静态的成员变量,因为静态成员变量是所有类型对象共享的,所以无论什么类型的对象都是共用一个count

所以每个资源需要管理时,给构造函数,构造new一个引用计数指针,在堆区开辟一块空间用于存储其对应的引用计数,如果有其他对象也想要管理这个资源,那么除了将这个资源给它之外,还需要把这个引用计数也给它

🎨线程安全问题
后续补上
🎨定制删除器
当智能指针对象的生命周期结束时,所有的智能指针默认都是以delete的方式将资源释放,这是不太合适的,因为智能指针并不是只管理以new方式申请到的内存空间,智能指针管理的也可能是以new[]的方式申请到的空间,或管理的是一个文件指针
struct Node
{int _val;ljj::weak_ptr<Node> _next;ljj::weak_ptr<Node> _prev;~Node(){cout << "~Node" << endl;}
};
void test_shared_ptr2()
{std::shared_ptr<Node> n1(new Node);std::shared_ptr<Node> n1(new Node[5]);//errorstd::shared_ptr<FILE> sp2(fopen("test.cpp", "r")); //errorstd::shared_ptr<int> n3(new int[5]);//内置类型没问题
}
这里为什么内置类型可以通过,但是自定义类型就会报错呢? 涉及指针偏移
- 对于内置类型,new实际在底层调用了
malloc,调用delete最终还是会调用到free - 自定义类型(看下图)

不管它底层崩不崩,我们要匹配好:new[]的方式申请到的内存空间必须以delete[]的方式进行释放,而文件指针必须通过调用fclose函数进行释放
这时就需要用到定制删除器来控制释放资源的方式,C++标准库中的shared_ptr提供了如下构造函数:
template <class U, class D>
shared_ptr (U* p, D del);//构造函数
参数说明:
p:需要让智能指针管理的资源del:删除器,这个删除器是一个可调用对象,比如函数指针、仿函数、lambda表达式以及被包装器包装后的可调用对象
//仿函数
template<class T>
struct DeleteArray
{void operator()(T* ptr){cout << "delete[]" << ptr << endl;delete[] ptr;}
};//定值删除器
void test_shared_ptr2()
{//仿函数对象std::shared_ptr<Node> n1(new Node);std::shared_ptr<Node> n2(new Node[5], DeleteArray<Node>());std::shared_ptr<int> n3(new int[5], DeleteArray<int>());//内置类型没问题//lambda对象std::shared_ptr<Node> l1(new Node);std::shared_ptr<Node> l2(new Node[5], [](Node* ptr) { delete[] ptr; });std::shared_ptr<int> l3(new int[5], [](int* ptr) { delete[] ptr; });std::shared_ptr<int> l4((int*)malloc(sizeof(12)), [](int* ptr) { free(ptr); });std::shared_ptr<FILE> l5(fopen("test.txt", "w"), [](FILE* ptr) { fclose(ptr); });
}
最好用的肯定是lambda对象
在unique_ptr中却是在模板参数中给的,只能在模板参数中传类型,不能用lambda对象

int main()
{//不能用lambdastd::unique_ptr<Node> up(new Node[5]);//error//模板中传的是类型,不是传对象不能加(),std::unique_ptr<Node, DeleteArray<Node>> up(new Node[5]);
}
🎃模拟删除器的实现:
- 要在当前模拟实现的shared_ptr的基础上支持定制删除器,就只能给
shared_ptr类再增加一个模板参数,在构造shared_ptr对象时就需要指定删除器的类型。然后增加一个支持传入删除器的构造函数,在构造对象时将删除器保存下来,在需要释放资源的时候调用该删除器进行释放即可。最好在设置一个默认的删除器,如果用户定义shared_ptr对象时不传入删除器,则默认以delete的方式释放资源
namespace ljj
{//默认删除器template<class T>class Delete{void operator()(T* ptr){delete ptr;}};template<class T, class D = Delete<T>>class shared_ptr{public:void Release(){if (--(*_pCount) == 0 && _ptr){//cout << "Delete" << _ptr << endl;//delete _ptr;//D del;//del(_ptr);D()(_ptr);//无参构造对象,operator()去决定,是free还是delete等等delete _pCount;}}~shared_ptr(){Release();}private:T* _ptr;int* _pCount; // 引用计数};
}
🥑weak_ptr
💦shared_ptr的循环引用问题
shared_ptr的循环引用问题在一些特定的场景下才会产生。比如定义如下的结点类,并在结点类的析构函数中打印一句提示语句,便于判断结点是否正确释放
struct Node
{int _val;std::shared_ptr<Node> _next;std::shared_ptr<Node> _prev;~Node(){cout << "~Node" << endl;}
};
现在以new的方式在堆上构建两个结点,并将这两个结点连接起来,又为了防止抛异常我们把类型改成智能指针
struct Node
{int _val;std::shared_ptr<Node> _next;std::shared_ptr<Node> _prev;~Node(){cout << "~Node" << endl;}
};
int main()
{std::shared_ptr<Node> n1 (new Node);std::shared_ptr<Node> n2 (new Node);n1->_next = n2; //智能指针和原生指针不能直接赋值n2->_prev = n1;//...return 0;
}

此时两个结点都没有被释放,但如果去掉连接结点时的两句代码中的任意一句,那么这两个结点就都能够正确释放,根本原因就是因为这两句连接结点的代码导致了循环引用
下面来一探究竟吧
循环引用导致资源未被释放的原因: 很绕!
- n2先析构,n1再析构,所以引用计数都变成了1
- 右边的节点释放取决于
_next析构,_next什么时候析构取决于左边节点的析构(作为成员); - 左边节点的析构取决于
_prev的析构,prev什么时候析构取决于右边节点的析构;右边的节点释放又取决于_next析构

二者互相纠缠,对此shared_ptr也是无能为力,又要引进一员大将weak_ptr
💦weak_ptr解决循环引用
weak_ptr就是shared_ptr的小跟班,不是常规智能指针,没有RAII,不支持直接资源管理
- weak_ptr主要用shared_ptr构造,用来解决引用问题:构造出来的
weak_ptr对象不参与资源释放管理,可以访问和修改资源,但不会增加这块资源对应的引用计数
解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
原理就是:node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和
_prev不会增加node1和node2的引用计数,引用计数都是1,就可以按先后顺序释放了
struct Node
{int _val;std::weak_ptr<Node> _next;std::weak_ptr<Node> _prev;~Node(){cout << "~Node" << endl;}
};//循环引用
void test_weak_ptr()
{std::shared_ptr<Node> n1 (new Node);std::shared_ptr<Node> n2 (new Node);cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2; //智能指针和原生指针不能直接赋值;所以next变成智能指针n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;
}
通过use_count获取这两个资源对应的引用计数就会发现,在结点连接前后这两个资源对应的引用计数就是1,根本原因就是weak_ptr不会增加管理的资源对应的引用计数
🎃weak_ptr的模拟实现

-
提供一个无参的构造函数、
shared_ptr对象拷贝构造、weak_ptr对象拷贝赋值、shared_ptr对象拷贝赋值给weak_ptr -
对
*和->运算符进行重载,使weak_ptr对象具有指针一样的行为
//辅助型智能指针,配合解决shared_ptr的循环引用问题
template<class T>
class weak_ptr
{
public:weak_ptr() //无参:_ptr(nullptr){}weak_ptr(const weak_ptr<T>& wp) //weak类型构造:_ptr(wp._ptr){}weak_ptr(const shared_ptr<T>& sp) //shared_ptr类型构造:_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;
};
注意: shared_ptr还会提供一个get函数,用于获取其管理的_ptr
C++11和boost中智能指针的关系
- C++98中产生了第一个智能指针auto_ptr
- C++boost给出了更实用的scoped_ptr、shared_ptr和weak_ptr
- C++TR1,引入了boost中的shared_ptr等。不过注意的是TR1并不是标准版
- C++11,引入了boost中的unique_ptr、shared_ptr和weak_ptr。需要注意的是,unique_ptr对应的就是boost中的scoped_ptr,并且这些智能指针的实现原理是参考boost中实现的
说明一下:boost库是为C++语言标准库提供扩展的一些C++程序库的总称,boost库社区建立的初衷之一就是为C++的标准化工作提供可供参考的实现,比如在送审C++标准库TR1中,就有十个boost库成为标准库的候选方案。
常见面试题
- 为什么需要智能指针? —— 忘记释放/异常安全
- RAII是什么?
- 发展历史
- auto_ptr/unique_ptr/shared_ptr/weak_ptr之间区别和使用场景
- 模拟实现简洁版的智能指针(没说就实现unique,不然就是shared的)
- 什么是循环引用?如何解决循环引用?解决的原理是什么?
📢写在最后

相关文章:
【C++】智能指针(万字详解)
🌈欢迎来到C专栏~~智能指针 (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort目前状态:大三非科班啃C中🌍博客主页:张小姐的猫~江湖背景快上车🚘,握好方向盘跟我有一起打天下嘞!送给自己的一句鸡汤&…...
使用docker配置mysql主从复制
1.新建主服务器容器实例: docker run -p 3307:3306 --name mysql \ -v /docker/mysql/data:/var/lib/mysql \ -v /docker/mysql/conf:/etc/mysql/conf \ -v /docker/mysql/log:/var/log/mysql \ -e MYSQL_ROOT_PASSWORDroot \ -d mysql:5.7 设置容器卷之后…...
v3 异步组件及分包使用
1 app.vue <template> <!-- vue3异步组件必须使用suspense --> <Suspense> <template #default> <!-- 异步组件 --> <SyncVue></SyncVue> </template> <template v-slot:fallback> <!-- 优先显示骨架屏 --> <…...
实用调试技巧【上篇】
🔴本文章是在 Visual Studio 2022(VS2022)编译环境下进行操作讲解 文章目录🥳1. 什么是bug?🥳2.调试有多重要?2.1. 我们是如何写代码的?2.2.调试是什么?2.3.调试的基本步…...
JavaScript 教程
手册简介JavaScript 是世界上最流行的脚本语言。 JavaScript 是属于 web 的语言,它适用于 PC、笔记本电脑、平板电脑和移动电话。 JavaScript 被设计为向 HTML 页面增加交互性。 许多 HTML 开发者都不是程序员,但是 JavaScript 却拥有非常简单的语法。几…...
在SpringBoot里面使用原生的Servlet
在SpringBoot里面使用Servlet 首先在主程序中添加注解主程序添加ServletComponentScan // 加上这个注解之后就可以使用原生的组件了 HttpServlet 继承HttpServlet 重写方法 添加WebServlet 第一种方式使用注解 WebServlet(value "/helsk") public class HelloSe…...
商标被驳回,先别慌!挽回商标有办法
商标注册是一个漫长的等待过程,提交了注册申请之后不代表就能得心应手。商标局在接收到申请后,便会开始各阶段审查,面对不符合条件的商标会予以商标驳回。商标局基于什么原因而驳回注册申请呢?驳回后还有必要进行商标驳回复审吗?今天心周企…...
VMware安装Linux虚拟机后忘记root密码处理方法
OS版本:Red Hat 7.7 问题说明: 之前用VMWare安装了一台Linux虚机,由于长期没使用,导致忘记了root密码。所以需要修改root密码。 Root密码修改 现将修改root密码的操作步骤记录如下。 1.启动虚拟机,出现启动倒计时…...
Centos安装OpenResty
文章目录一. OpenResty是什么二. OpenResty的安装1. 安装开发库2. 安装OpenResty仓库3. 安装OpenResty4. 安装opm工具5. 目录结构6. 配置nginx的环境变量7. 启动和运行8. 配置文件修改三. 小案例1. 案例说明2. OpenResty监听请求3. 编写业务代码4. 获取请求参数一. OpenResty是…...
阿里云部署SpringBoot项目
目录 步骤1:购买服务器(新用户免费试用一个月) 步骤2:查看服务器相关信息 编辑 步骤3:设置安全组 步骤4:远程连接 步骤5:使用FinalShell连接阿里云服务器 步骤6:阿里云服务器上安装JDK 编辑 步骤7…...
EdgeCOM嵌入式边缘计算机的参数配置
EdgeCOM嵌入式边缘计算机的参数配置: 下面以 eth0 为例进行命令说明。 在 Linux 系统下,使用 ifconfig 命令可以显示或配置网络设备,使用 ethtool 查询及 设置网卡参数。 设置 IP 地址,查看当前网卡详情: rootfl-imx6u…...
字节软件测试岗:惨不忍睹的三面,幸好做足了准备,月薪15k,拿到offer
我今年25岁,专业是电子信息工程本科,19年年末的时候去面试,统一投了测试的岗位,软件硬件都有,那时候面试的两家公司都是做培训的,当初没啥钱,他们以面试为谎言再推荐去培训这点让我特别难受。 …...
【编程基础之Python】5、安装Python第三方模块
【编程基础之Python】5、安装Python第三方模块安装Python第三方模块为什么需要安装第三方模块Python包管理器介绍pippip installpython -m pip installcondaconda install在Windows环境中安装Python模块安装numpy安装pandas安装matplotlib在Linux环境中安装Python模块在PyCharm…...
JavaScript 教程导读
JavaScript 是 Web 的编程语言。所有现代的 HTML 页面都使用 JavaScript,可以用于改进设计、验证表单、检测浏览器、创建cookies等。JavaScript 非常容易学。本教程将教你学习从初级到高级JavaScript知识。JavaScript 在线实例本教程包含了大量的 JavaScript 实例&a…...
BigDecimal
文章目录1. BigDecimal 的舍入模式(RoundingMode)1.1 ROUND_UP1.2 ROUND_DOWN1.3 ROUND_HALF_UP1.4 ROUND_HALF_DOWN1.5 ROUND_CEILING1.6 ROUND_FLOOR1.7 ROUND_HALF_EVEN1.8 ROUND_UNNECESSARY2. BigDecimal的运算——加减乘除2.1 加法 add()函数 减法…...
代码随想录【Day15】|102. 二叉树的层序遍历、226. 翻转二叉树、101. 对称二叉树
102. 二叉树的层序遍历 题目链接 题目描述: 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 难点: 思路: 需要借用一个辅助数据结构即队列来实现…...
Python学习笔记:快速上手:基础知识
快速上手:基础知识 数和表达式 除法 >>> 1 / 2 0.5 >>> 1 / 1 1.0整除 >>> 1 // 2 0 >>> 1 // 1 1 >>> 5.0 // 2.4 2.0求余(求模): x % y 等价于x - ((x // y) * y)。 …...
excel学习笔记-导入外部文件,报错,数值格式变换,日期格式的转化,求和快捷键,冻结窗格
这里写目录标题一、导入外部文件1.导入csv文件2.导入txt文件3.修改txt内容,需要刷新才能看见更改二、报错三、数值格式变换四、日期格式的转化五、ALT ,求和快捷键六、冻结窗格一、导入外部文件 1.导入csv文件 2.导入txt文件 3.修改txt内容,…...
06 OpenCV‘阈值处理、自适应处理与ostu方法
1 基本概念 CV2中使用阈值的作用是将灰度图像二值化,即将灰度图像的像素值根据一个设定的阈值分成黑白两部分。阈值处理可以用于图像分割、去除噪声、增强图像对比度等多个领域。例如,在物体检测和跟踪中,可以通过对图像进行阈值处理来提取目…...
月薪过万的那些人,大部分都是做什么工作的?
三百六十行,行行出状元。不管是什么行业,月薪过万都是有的。只不过有些行业就是比较容易出现月薪过万,换句话说,就是这个行业内出现月薪过万的人数比较多。先说结论,综合来看月薪过万的这部分90后,大部分集…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...

