四种常见智能指针的介绍

一、介绍

当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针(smart pointer),从而实现指针指向的对象的共享。

智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。

一句话简单概括:智能指针是帮助我们管理动态内存分配的,会自动帮忙释放new出来的内存,从而避免内存泄漏的问题。

二、为什么要使用智能指针

答:使用智能指针可以有效的避免内存泄漏。

什么是内存泄漏?

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

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

如下例子:人为原因导致忘记释放;代码逻辑原因抛出异常使内存释放执行不了。

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;
    
}

常见的内存泄漏

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

三、智能指针的使用及原理

RAII思想

当我们使用一块资源时,一般会经历三个步骤:a.获取资源(创建对象);b.使用资源;c.销毁资源(析构对象)。但我们通常会忘记c销毁资源,从而导致一些内存问题的产生,而RAII的思想,就可以巧妙的解决c步骤的发生。

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术 。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。实际上是把管理一份资源的责任托管给了一个对象。这种做法有两大好处:不需要显式地释放资源;采用这种方式,对象所需的资源在其生命期内始终保持有效 。

原理

智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。

即具有RAII思想,且重载有operator*和operator->像指针一样的行为的一种指针类型。

一个智能指针模板类,至少具有如下格式:

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;
};

四、常见的智能指针类型

头文件 #include <memory>

1.auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针,其实现原理是管理权转移的思想。

这就导致了很不好的情况产生如下代码,将对象p1转移给p2时,不会发生任何问题,但是当访问到被转移后的p1时,就会报错。

auto_ptr<string> p1(new string("I reigned loney as a cloud."));
auto_ptr<string> p2;
p2=p1; //auto_ptr不会报错
cout << *p2 << endl;  // 访问没有问题
cout << *p1 << endl;  // 会报错

在p1赋值给p2后,首先p2会先将自己原先托管的指针释放掉,然后接收托管p1所托管的指针,然后p1所托管的指针置NULL,也就是p2托管了p1的指针,而p1放弃了托管,所以再次访问到p1时,就会发生错误。

即auto_ptr:存在潜在的内存崩溃问题。所以在C++11中,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 (this != &ap)  // 检测是否为自己赋值
		{ 
			if (_ptr)  // 先释放自己的空间,再获取别的对象
			{
				std::cout << "Delete:" << _ptr << std::endl;
				delete _ptr;
			}
            // 转移ap中资源到当前对象中
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
		return *this;
	}

	~auto_ptr()
	{
		if (_ptr)
		{
			std::cout << "Delete:" << _ptr << std::endl;
			delete _ptr;
		}
	}

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

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

private:
	T* _ptr;
};

2.unique_ptr

unique_ptr避免了auto_ptr中出现的问题,而它的解决方法也更简单粗暴,即禁用拷贝赋值。

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

unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露,例如,以new创建对象后因为发生异常而忘记调用delete时的情形特别有用。

unique_ptr特性

  1. 基于排他所有权模式:两个指针不能指向同一个资源。
  2. 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值。
  3. 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
  4. 在容器中保存指针是安全的。
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 不允许
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 允许

当程序试图将一个 unique_ptr 赋值给另一个时,如果源unique_ptr 是个临时右值(#2),编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做(#1)。

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。

另外:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++11标准中的库函数std::move(),能够允许将一个unique_ptr赋给另一个。

std::unique_ptr<std::string> p1(new std::string("test move copy"));
std::unique_ptr<std::string> p2 = std::move(p1);
std::cout << p1.get() << std::endl;
std::cout << p2.get() << std::endl;

但是,通过运行结果,可以看出,赋值后,p1还是被置空了,结果与auto_ptr一致了。所以,unique_ptr会禁止拷贝复制。

与auto_ptr区别:禁用了拷贝赋值操作。

模拟实现:

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

	// 防拷贝 C++11, 禁用拷贝赋值
	unique_ptr(unique_ptr<T>& ap) = delete;
	unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;

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

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

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

private:
	T* _ptr;
};

3.shared_ptr

原理:

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

  1. shared_ptr在其内部,给每个资源都维护了一份计数,用来记录该份资源被几个对象共享,发生拷贝或赋值时+1。可以通过成员函数use_count()来查看资源的所有者个数 。
  2. 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

成员函数:

  • use_count 返回引用计数的个数。
  • unique 返回是否是独占所有权( use_count 为 1)。
  • swap 交换两个 shared_ptr 对象(即交换所拥有的对象)。
  • reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少。
  • get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的。

use_count 使用:

int main()
{
    std::shared_ptr<std::string> sp1;

    std::shared_ptr<std::string> sp2(new std::string("test"));

    std::cout << "sp1	use_count() = " << sp1.use_count() << std::endl;    // 未发生拷贝或复制 0
    std::cout << "sp2	use_count() = " << sp2.use_count() << std::endl << std::endl;  // 拷贝1次 +1

    // 共享
    sp1 = sp2;  // 赋值一次,sp2 +1,sp1内部变为sp2,所以结果应该与sp2一致

    std::cout << "sp1	use_count() = " << sp1.use_count() << std::endl;  // 打印 2
    std::cout << "sp2	use_count() = " << sp2.use_count() << std::endl << std::endl;  // 打印 2

    std::shared_ptr<std::string> sp3(sp1);  // sp3拷贝sp1; sp1 次数+1,sp3与sp1 一致
    std::cout << "sp1	use_count() = " << sp1.use_count() << std::endl;  // 打印 3
    std::cout << "sp2	use_count() = " << sp2.use_count() << std::endl;  // 打印 3
    std::cout << "sp2	use_count() = " << sp3.use_count() << std::endl << std::endl;  // 打印 3

    return 0;
}

运行结果:

可以发现,三次发生计数+1操作的位置在sp2(new std::string("test"))sp1 = sp2; sp3(sp1)三个拷贝、赋值的地方。

reset使用:

int main()
{
    std::shared_ptr<std::string> sp1;

    std::shared_ptr<std::string> sp2(new std::string("test"));

    std::cout << "sp1	use_count() = " << sp1.use_count() << std::endl;    // 未发生拷贝或复制 0
    std::cout << "sp2	use_count() = " << sp2.use_count() << std::endl << std::endl;  // 拷贝1次 +1

    // 共享
    sp1 = sp2;  // 赋值一次,sp2 +1,sp1内部变为sp2,所以结果应该与sp2一致

    std::cout << "sp1	use_count() = " << sp1.use_count() << std::endl;  // 打印 2
    std::cout << "sp2	use_count() = " << sp2.use_count() << std::endl << std::endl;  // 打印 2

    std::shared_ptr<std::string> sp3(sp1);  // sp3拷贝sp1; sp1 次数+1,sp3与sp1 一致
    std::cout << "sp1	use_count() = " << sp1.use_count() << std::endl;  // 打印 3
    std::cout << "sp2	use_count() = " << sp2.use_count() << std::endl;  // 打印 3
    std::cout << "sp2	use_count() = " << sp3.use_count() << std::endl << std::endl;  // 打印 3

    // reset使用
    sp1.reset();
    std::cout << "sp1	use_count() = " << sp1.use_count() << " " << sp1 << std::endl;
    std::cout << "sp2	use_count() = " << sp2.use_count() << " " << sp2 << std::endl;
    std::cout << "sp3	use_count() = " << sp3.use_count() << " " << sp3 << std::endl << std::endl;


    return 0;
}

由运行结果可见,sp1调用reset后,sp1本身被置空,它对应的计数也变为0,而sp2和sp3,会随着sp1调用reset导致计数会减1。

unique使用:

std::shared_ptr<int> p1(new int(10));
std::shared_ptr<int> p2 = p1;

// #1
std::cout << "p1 is unique: " << std::boolalpha << p1.unique() << std::endl; // 输出:p1 is unique: false
std::cout << "p2 is unique: " << std::boolalpha << p2.unique() << std::endl; // 输出:p2 is unique: false

// #2
std::shared_ptr<int> p3(new int(20));
std::cout << "p3 is unique: " << std::boolalpha << p3.unique() << std::endl; // 输出:p3 is unique: true

// #3
p2.reset();
std::cout << "p1 is unique: " << std::boolalpha << p1.unique() << std::endl; // 输出:p1 is unique: true
std::cout << "p2 is unique: " << std::boolalpha << p2.unique() << std::endl; // 输出:p2 is unique: false

#1 p1进行了一次赋值给p2,所以发生了一次共享,所以不是独占所有权false。

#2 p3,它指向一个新的int类型的对象,此时p3是该对象的唯一所有者。

#3 使用reset函数将p2重置为空,此时p1是该对象的唯一所有者,unique函数返回true。由于p2已经被置空,它不再指向任何资源,输出为false。

swap使用

    // 创建两个 shared_ptr 对象
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::shared_ptr<int> ptr2 = std::make_shared<int>(100);
	std::shared_ptr<int> ptr3 = ptr1;

    std::cout << "ptr1: " << *ptr1 << ", use_count: " << ptr1.use_count() << std::endl;
    std::cout << "ptr2: " << *ptr2 << ", use_count: " << ptr2.use_count() << std::endl;

    // 使用 swap 交换资源
    ptr1.swap(ptr2);

    std::cout << "ptr1: " << *ptr1 << ", use_count: " << ptr1.use_count() << std::endl;
    std::cout << "ptr2: " << *ptr2 << ", use_count: " << ptr2.use_count() << std::endl;

由运行结果可以看出,ptr1.swap(ptr2);执行后,ptr1与ptr2对应的值以及计数发生了交换。

shared_ptr的线程安全问题

shared_ptr的线程安全分为两方面:

  1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或--,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、--是需要加锁的,也就是说引用计数的操作是线程安全的。
  2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

模拟实现:

template<class T>
class shared_ptr
{
public:
	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();
	}

	// sp1(sp2)
	shared_ptr(const shared_ptr<T>& sp)
		: _ptr(sp._ptr)
		, _pCount(sp._pCount)
	{
		(*_pCount)++;
	}

	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		//if (this == &sp)
		if (_ptr == sp._ptr)
		{
			return *this;
		}

		// 减减被赋值对象的计数,如果是最后一个对象,要释放资源
		/*if (--(*_pCount) == 0)
		{
			delete _ptr;
			delete _pCount;
		}*/
		Release();

		// 共管新资源,++计数
		_ptr = sp._ptr;
		_pCount = sp._pCount;

		(*_pCount)++;

		return *this;
	}

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

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

private:
	T* _ptr;

	// 引用计数
	int* _pCount;
};

4.weak_ptr

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的 shared_ptr。weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作,它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。

weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

class B;
class A
{
	public :
	std::shared_ptr<B> pb_;
	~A()
	{
		std::cout << "A delete\n";
	}
};
class B
{
	public :
	std::shared_ptr<A> pa_;
	
	~B()
	{
		std::cout << "B delete\n";
	}
};
void fun()
{
	std::shared_ptr<B> pb(new B());
	std::shared_ptr<A> pa(new A());
	pb->pa_ = pa;
	pa->pb_ = pb;
	std::cout << pb.use_count() << std::endl;
	std::cout << pa.use_count() << std::endl;
} 

int main()
{
	fun();
	return 0;
}

可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用):

(没有调用析构)

如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_; 改为weak_ptr pb; 运行结果如下:

这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。

注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb->print(); 英文pb是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】每日一题 2023_12_31 一年中的第几天(日期)

文章目录 随便聊聊时间题目&#xff1a;一年中的第几天题目描述代码与解题思路 随便聊聊时间 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 12 月的打卡勋章&#xff0c;get&#xff01; 题目&#xff1a;一年中的第几天 题目链接&#xff1a;1154. 一年中…

2023 年四川省职业院校技能大赛“信息安全管理与评估”样题

2023 年四川省职业院校技能大赛&#xff08;高等职业教育&#xff09; “信息安全管理与评估”样题 竞赛需要完成三个阶段的任务&#xff0c;分别完成三个模块&#xff0c;总分共计 1000分。三个模块内容和分值分别是&#xff1a; 第一阶段&#xff1a;模块一 网络平台搭建与设…

【Proteus仿真】【STM32单片机】自动除湿器系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用按键、LCD1602液晶、DHT11温湿度、继电器除湿模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示DHT11传感器检测的湿度值阈值…

【实用工具】Tmux使用指南

Tmux 三个重要概念 session&#xff08;会话&#xff09;、window&#xff08;窗口&#xff09;、pane&#xff08;面板&#xff09; 一个会话可以有多个窗口&#xff0c;一个窗口可以划分为多个面板 注意在tmux中使用快捷命令的话&#xff0c;需要加上前缀ctrlb 关于session的…

不擅长设计也能做好邮件营销:实用技巧与指南分享

电子邮件营销是跨境电商中非常常见的品牌推广方式之一。但对于那些不擅长设计的人来讲&#xff0c;设计一封引人注目的电子邮件可能是一个磨练。本文将分享一些提议&#xff0c;协助跨境电商顾客处理不擅长设计问题。 不擅长设计的情况下怎么进行邮件营销&#xff1f;首先我们…

C语言——扫雷

扫雷是一款经典的小游戏&#xff0c;那如何使用C语言实现一个扫雷游戏呢&#xff1f; 一、全部源码 直接把全部源码放在开头&#xff0c;如有需要&#xff0c;直接拿走。 源码分为三个文件&#xff1a; test.cpp/c 主函数的位置 #include "game.h"int main() {…

NGUI基础-三大基础组件之Panel组件

目录 Panel组件 Panel的作用&#xff1a; 注意&#xff1a; 相关关键参数讲解&#xff1a; Alpha&#xff08;透明度值&#xff09;&#xff1a; Depth&#xff08;深度&#xff09;&#xff1a; Clippinng&#xff08;裁剪&#xff09;&#xff1a; ​编辑 None Tex…

Qt sender()函数

sender函数原型&#xff1a; QObject *sender() const; 如果在由信号激活的插槽中调用该函数&#xff0c;返回指向发送信号的对象的指针&#xff0c;否则返回0&#xff0c;该指针仅在从该对象的线程上下文调用此函数的槽执行期间有效。 主要代码如下&#xff1a; 其中运用了Q…

yolov8 tracking编码为web 和 rtsp流输出

1 基础工作 打开cmd 输入 conda env list 输入 conda activate py38 查看 nvidia-smi 查看 nvcc&#xff0c;如下图所示 cuda为11.7 &#xff0c;为确认可以查看program files 下面的cuda 安装&#xff0c;看到11.7 就行了&#xff0c;读者可以自行确认自己的版本。 查看nvid…

【深度解析C++】const成员函数

系列文章目录 &#x1f308;座右铭&#x1f308;&#xff1a;人的一生这么长、你凭什么用短短的几年去衡量自己的一生&#xff01; &#x1f495;个人主页:清灵白羽 漾情天殇_计算机底层原理,深度解析C,自顶向下看Java-CSDN博客 ❤️相关文章❤️&#xff1a;Cthis指针&#xf…

【Redis技术专区】「原理分析」探讨Redis 6.0为何需要启用多线程?

探讨Redis 6.0为何需要启用多线程 背景介绍开启多线程多线程的CPU核心配置IO多线程模式单线程处理方式多线程处理方式 为什么要开启多线程&#xff1f;充分利用多核CPU提高网络I/O效率响应现代应用需求 多线程实现启用多线程 最后总结 背景介绍 在Redis 6.0版本中&#xff0c;…

期权二叉树估值与图计算

传统期权二叉树的算法都是基于数组的&#xff0c;对于没有编程基础的人来说非常不直观。二叉树是一种特殊的图&#xff0c;可以用python networkx这个图算法库实现&#xff0c;这个库不仅包含常用的图算法&#xff0c;还包含简单的绘图功能&#xff0c;非常适合研究分析使用。 …

git分支场景操作,应用场景

文章目录 git分支操作1.git branch--目前处在的分支上2.git checkout--要切换的分支名字3.git merge--要合并的分支名字4.git branch -d--要删除的分支名字 git分支操作 假设目前我们目前有三个版本 1.git branch–目前处在的分支上 现在要开发一个新功能在新的分支上 新建一…

Python序列之字典

系列文章目录 Python序列之列表Python序列之元组Python序列之字典&#xff08;本篇文章&#xff09;Python序列之集合 Python序列之字典 系列文章目录前言一、字典是什么&#xff1f;二、字典的操作1.创建&#xff08;1&#xff09;通过{}、dict()创建&#xff08;2&#xff0…

Cisco无线Mobility Express配置Image TFTP服务器

思科的无线AP&#xff1a; 1800&#xff0c; 2800&#xff0c; 3800系列 这一类的AP本身可以做为无线控制器使用&#xff0c;被称为Mobility Express&#xff0c;简称为ME 可以管理多少AP 最多可管理 25个 是否需要license才能管理 不需要license 支持哪些型号的AP注册 只要…

Rust学习笔记003:语句和表达式+函数+控制语句:IF,MATCH,LOOP

语句&#xff08;Statements&#xff09;和表达式&#xff08;Expressions&#xff09; 1. 语句&#xff08;Statements&#xff09;&#xff1a; 语句是执行一些操作但不返回值的代码单元。例如&#xff0c;声明变量、赋值、函数调用、宏调用等都是语句。 // 声明变量的语句…

Python教程(19)——python异常处理

异常处理 什么是异常异常处理方式try-except语句捕获异常类型 相关的异常类型 什么是异常 在计算机编程中&#xff0c;异常&#xff08;Exception&#xff09;是指在程序执行过程中发生的错误或异常情况。当出现异常时&#xff0c;程序无法正常继续执行&#xff0c;因此需要采…

【Redis-05】Redis如何实现保存键值对的保存及过期键的管理策略

在之前的文章我们介绍过&#xff0c;Redis服务器在启动之初&#xff0c;会初始化RedisServer的实例&#xff0c;在这个实例中存在很多重要的属性结构&#xff0c;同理本篇博客中介绍的数据库实现原理也会和其中的某些属性相关&#xff0c;我们继续看一下吧。 1.服务器和客户端…

JavaSE语法之十二:Object类

文章目录 一、概念二、获取对象信息三、对象比较equals方法四、hashcode方法 一、概念 Object是Java默认提供的一个类。Java里面除了Object类&#xff0c;所有的类都是存在继承关系的&#xff0c;默认会继承Object父类&#xff0c;即所有的类的对象都可以使用Object的引用进行…

安装与部署Hadoop

一、前置安装准备1、机器2、java3、创建hadoop用户 二、安装Hadoop三、环境配置1、workers2、hadoop-env.sh3、core-site.xml4、hdfs-site.xml5、linux中Hadoop环境变量 四、启动hadoop五、验证 一、前置安装准备 1、机器 主机名ip服务node1192.168.233.100NameNode、DataNod…