【C++】-- 智能指针

news2024/9/21 4:24:24

目录

智能指针意义

智能指针的使用及原理

RAII

智能指针的原理

std::auto_ptr

std::auto_ptr的模拟实现 

std::unique_ptr

std::unique_ptr模拟实现

std::shared_ptr

std::shared_ptr的模拟实现

循环引用问题


智能指针意义

#问:为什么需要智能指针?

#include <iostream>
#include <stdexcept>

int div()
{
    int a, b;
    std::cin >> a >> b;
    if (b == 0)
        throw std::invalid_argument("除0错误");
    return a / b;
}

void Func()
{
    // 1、如果p1这里new 可能会抛异常。
    // 2、如果p2这里new 可能会抛异常。
    // 3、如果div调用也可能会抛异常。
    int *p1 = new int;
    int *p2 = new int;
    std::cout << div() << std::endl;
    delete p1;
    delete p2;
}

int main()
{
    try
    {
        Func();
    }
    catch (std::exception &e)
    {
        std::cout << e.what() << std::endl;
    }
    return 0;
}

        在一异常中,因为异常会导致程序的执行流乱跳,所以很多个会出现异常的代码,放在一起就很容易其中一个抛异常,而导致其余的未执行 / 需要释放的空间未释放。如上:p2出问题,需要释放p1,div出问题,需要释放p1、p2,智能指针就是用来解决这个问题。

智能指针的使用及原理

RAII

        RAII(Resource Acquisition Is Initialization - 获取资源即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。资源:需要手动释放的。(RAII:请求到志愿就初始化)

初始化指的是:
        调用一个其他类的构造函数,利用其他类的生命周期来进行管理。

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

        就是在获取到资源的时候,交给一个对象管理,于是在该对象的生命周期里这个资源始终有效。而这个对象无论如何是异常还是正常结束,都会调用这个对象的析构函数,于是利用这个对象的析构函数释放资源。

这种做法有两大好处:

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

智能指针的原理

最基础的智能指针:

// 使用RAII思想设计的SmartPtr类
template <class T>
class SmartPtr
{
public:
    SmartPtr(T *ptr = nullptr) // 将资源给智能指针
        : _ptr(ptr) // 智能指针将资源保存
    {}
    
    ~SmartPtr()
    {
        if (_ptr)
            delete _ptr;
    }

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

// 1、利用RAII思想设计delete资源的类
// 2、像指针一样的行为
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr) // 将资源给智能指针
		:_ptr(ptr) // 智能指针将资源保存
	{}

	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

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

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

private:
	T* _ptr;
};

int div()
{
    int a, b;
    std::cin >> a >> b;
    if (b == 0)
        throw std::invalid_argument("除0错误");
    return a / b;
}
void Func()
{
    // sp1、sp2出作用域会调用析构函数,抛异常,栈帧会正常结束
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);

	*sp1 = 0;
	*sp2 = 2;

	std::cout << div() << std::endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}

	return 0;
}

总结一下智能指针的原理:
  1. RAII特性。
  2. 重载operator*和opertaor->,具有像指针一样的行为。

Note:

        智能指针看起来很完美,但是又一个致命的问题:智能指针的拷贝问题。默认的拷贝构造只会进行浅拷贝,就会导致一个地址被析构两次。主要原因就是:智能指针管理资源的释放。

解决方案:

问:深拷贝?

        不能,违背了智能指针的功能需求,需要的就是浅拷贝,智能指针不知道该空间有多大,只是对与指针的保存。

(问题先保留看看C++库中的解决方式)

std::auto_ptr

http://www.cplusplus.com/reference/memory/auto_ptr/

        C++98版本的库中就提供了auto_ptr的智能指针。
        auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原理。
#include <iostream>
#include <memory>

class A
{
public:
	~A()
	{
		std::cout << "~A()" << std::endl;
	}

	int _a1 = 0;
	int _a2 = 0;
};

int main()
{
	std::auto_ptr<A> ap1(new A);
	ap1->_a1++;
	ap1->_a2++;

	std::auto_ptr<A> ap2(ap1);
	//ap1->_a1++;
	//ap1->_a2++;

	return 0;
}
        正是这个原因,所以:ap1就空了。

总结:

        std::auto_ptr是采用的资源管理权转移。但是,是不负责任的拷贝,会导致被拷贝对象悬空。所以多年以来被挂在耻辱柱上,很多公司明确要求不能使用它。

std::auto_ptr的模拟实现 

        注意:不是交换 —— 是管理权的转移,所以如果是:

std::auto_ptr<A> ap1(new A);
std::auto_ptr<A> ap2(new A);
ap2 = ap1;

        ap2是获取ap1的资源(资源管理权的转移) ,ap2之前的资源自动调用析构释放,ap1置空(nullptr)

namespace cr
{
	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;
		}

        // 不是交换 —— 是管理权的转移
		// ap1 = ap2;
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				if (_ptr)
				{
					cout << "Delete:" << _ptr << endl;
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}

			return *this;
		}

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

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

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

	private:
		T* _ptr;
	};
}

std::unique_ptr

        C++11中开始提供更靠谱的unique_ptr

https://cplusplus.com/reference/memory/unique_ptr/

        unique_ptr的 实现原理不让你拷贝 ,简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理。只适用于不需要拷贝的场景。

std::unique_ptr模拟实现

namespace cr
{
	template<class T>
	class unique_ptr
	{
	private: // 防止有人跑到类外实现写一个浅拷贝

		// 防拷贝 C++98 - 当时还是boost库中
		// 只声明不实现 
		// unique_ptr(unique_ptr<T>& ap);
		// unique_ptr<T>& operator=(unique_ptr<T>& ap);
	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;
	};
}

std::shared_ptr

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr 。

www.cplusplus.com/reference/memory/shared_ptr/
#include <memory>
#include <iostream>

class A
{
public:
	~A()
	{
		std::cout << "~A()" << std::endl;
	}

	int _a1 = 0;
	int _a2 = 0;
};

void test_shared_ptr()
{
	std::shared_ptr<A> sp1(new A);
	std::shared_ptr<A> sp2(sp1);

	sp1->_a1++;
	sp1->_a2++;

	std::cout << sp2->_a1 << ":" << sp2->_a2 << std::endl;
    
	sp2->_a1++;
	sp2->_a2++;

	std::cout << sp1->_a1 << ":" << sp1->_a2 << std::endl;
}

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

        shared_ptr的 原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源 。例如: 学校老师晚上在下班之前都会通知,让最后走的学生记得把门锁下,并且打扫一下教室。

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

std::shared_ptr的模拟实现

        不能用static修饰计数器,因为这样会让所有的模拟实现的shared_ptr的不同类型的实例化,同用一个计数器。需要的是一个资源,配一个计数器,多个智能指针对象共管静态计数对象,所以资源都只有一个计数,因为静态成员属于整个类,属于类的所有对象。

        所以使用的是一个指针,这个时候这个指针指向的空间就是new出来的,使用构造函数new,这就保证了同一个资源,用一个计数器。并且这样这个指针就会指向需释放的资源,也指向了计数。

        即:每个资源需要管理的时候,会给构造函数,构造new一个计数。

namespace cr
{
	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 (_ptr == sp._ptr)
			{
				return *this;
			}

			// 判断是否释放 -- 防止_ptr原来的数据未计数--,导致内存泄漏
			Release();

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

			(*_pCount)++;

			return *this;
		}

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

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

	private:
		T* _ptr;

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

shared_ptr的问题:

shared_ptr看起来很完美,但是也有问题:

  • 在多线程使用shared_ptr的时候,shared_ptr不是线程安全的。
  • shared_ptr存在一个循环引用的问题。

循环引用问题

(是一个非常特殊的情况下)

#include <iostream>
#include <memory>

class Node
{
public:
    ~Node()
    {
        std::cout << "~Node" << std::endl;
    }

    int val = 0;
    std::shared_ptr<Node> _next;
    std::shared_ptr<Node> _prev;
};

void test_shared_ptr()
{
    std::shared_ptr<Node> s1(new Node);
    std::shared_ptr<Node> s2(new Node);
    
    s1->_next = s2;
    s2->_prev = s1;
}

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

        我们可以发现上面代码并没有像我们想象的一样,自动调用析构函数:

分析:

        当下图的时候,是没有问题的,也就是对于shared_ptr智能指针的使用,shared_ptr的计数器分别都++。

        但是当继续向下执行的时候,就是问题的关键所在。

        此处:shared_ptr类型的s1,经过operator ->的重载,于是变为(Node*)->_next = s2,此处Note中的_next对象是shared_ptr类型的,所以会调用s2中的智能指针的赋值。

        同理:会调用s1中的智能指针的赋值。

        于是s1与s2的计数器分别都++到了2。

 #问:通过以上为什么不会调用析构?

        因为,根据函数的创建顺序,s2后构造所以先析构,s1先构造所以后析构。

        于是s2与s1就分别调用它们的析构函数了,于是计数器分别都--到了1。但是由于:

  • _next:管着右边的节点内存块。
  • _prev:管着左边的节点内存块。

        于是,便出现:理想状态下,_next释放那右边就释放,_prev释放那左边就释放。

        但是会出现一个问题,_next与_prev分别数据不同的成员节点,对于对象中的成员是需要该对象调用析构函数才会释放其中的成员,于是对于左节点是需要计数器--到0,才能释放左节点,_next才会析构。同样的,右节点是需要计数器--到0,才会释放右节点,_prev才会析构

        这就是一个死循环,所以没有调用Node的析构函数,也就不可能看到Node类型对象的析构释放显示。

解决方式:

         这个地方想使用shared_ptr进行Node类型的对象中类似的操作是把必定错的,没有办法,所以C++也为我们提供了一个新的方式:weak_ptr(弱指针)

        weak_ptr与其他的智能指针都不一样,其并不是常规智能指针,没有RAII,不支持直接管理资源。weak_ptr主要用shared_ptr构造,用来解决shared_ptr循环引用问题

https://legacy.cplusplus.com/reference/memory/weak_ptr/?kw=weak_ptr

        主要用share_ptr来构造自己,并不支持用一个资源构造自己,所以其并不支持RAII。

  • 特点:不++share_ptr的计数器。
#include <iostream>
#include <memory>

class Node
{
public:
    ~Node()
    {
        std::cout << "~Node" << std::endl;
    }

    int val = 0;
    std::weak_ptr<Node> _next;
    std::weak_ptr<Node> _prev;
};

void test_shared_ptr()
{
    std::shared_ptr<Node> s1(new Node);
    std::shared_ptr<Node> s2(new Node);
    
    s1->_next = s2;
    s2->_prev = s1;
}

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

        当 _next 与 _prev 是 weak_ptr 的时候,它们不参加资源的释放管理,但是可以访问和修改数据,且并不增加计数,所以不会存在循环引用的问题了。(这个方法可行,但是因为不计数,多以我们要识别到这个问题)

Note:

        想看到计数的对应变化,可以使用shared_ptr的成员函数 use_count

weak_ptr的模拟实现

        对于前面所模拟实现的shared_ptr同样的也具有循环引用的问题。

#include <iostream>

namespace cr
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr): _ptr(ptr), _pCount(new int(1)){}

        // 判断是否释放
		void Release()
		{
            // 减减被赋值对象的计数,如果是最后一个对象,要释放资源
			if (--(*_pCount) == 0)
			{
				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 (_ptr == sp._ptr)
				return *this;

			// 判断是否释放 -- 防止_ptr原来的数据未计数--,导致内存泄漏
			Release();
			// 共管新资源,++计数
			_ptr = sp._ptr;
			_pCount = sp._pCount;
			(*_pCount)++;
			return *this;
		}
		T& operator*(){return *_ptr;}
		T* operator->(){return _ptr;}
	private:
		T* _ptr;
		// 引用计数
		int* _pCount;
	};
}

class Node
{
public:
    ~Node(){std::cout << "~Node" << std::endl;}

    int val = 0;
    cr::shared_ptr<Node> _next;
    cr::shared_ptr<Node> _prev;
};

void test_shared_ptr()
{
    cr::shared_ptr<Node> s1(new Node);
    cr::shared_ptr<Node> s2(new Node);
    s1->_next = s2;
    s2->_prev = s1;
}

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

        于是可以通过模拟实现weak_ptr进行避免我们所模拟实现的shared_ptr,出现循环引用的问题。


#include <iostream>

namespace cr
{
    template <class T>
    class shared_ptr
    {
    public:
        shared_ptr(T *ptr = nullptr) : _ptr(ptr), _pCount(new int(1)) {}

        // 判断是否释放
        void Release()
        {
            // 减减被赋值对象的计数,如果是最后一个对象,要释放资源
            if (--(*_pCount) == 0)
            {
                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 (_ptr == sp._ptr)
                return *this;

            // 判断是否释放 -- 防止_ptr原来的数据未计数--,导致内存泄漏
            Release();
            // 共管新资源,++计数
            _ptr = sp._ptr;
            _pCount = sp._pCount;
            (*_pCount)++;
            return *this;
        }

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

        // 便于外部获取ptr,如weak_ptr
        T *get()const
        {
            return _ptr;
        }

    private:
        T *_ptr;
        // 引用计数
        int *_pCount;
    };

    // 辅助型智能指针,使命配合解决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(const weak_ptr<T> &wp)
            : _ptr(wp._ptr)
        {}

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

class Node
{
public:
    ~Node() { std::cout << "~Node" << std::endl; }

    int val = 0;

    cr::weak_ptr<Node> _next;
    cr::weak_ptr<Node> _prev;
};

void test_shared_ptr()
{
    cr::shared_ptr<Node> s1(new Node);
    cr::shared_ptr<Node> s2(new Node);
    s1->_next = s2;
    s2->_prev = s1;
}

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

需要掌握:

  • 为什么需要智能指针?

        主要的原因还是因为内存泄漏:忘记释放的问题,更重要还有异常安全的问题。

  • RAII?

        资源获得,资源请求机立即初始化。指的就是把资源交给一个对象,利用构造将其资源交给一个对象去管理。

  • 发展历史
  • auto_ptr / unique_ptr / shared_ptr / weak_ptr之间的区别与使用场景。
  • 模拟实现简洁版智能指针。
  • 什么是循环引用?如何解决循环引用?解决的原理是什么?

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

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

相关文章

R语言绘制SCI论文中常见的箱线散点图,并自动进行方差分析计算显著性水平

显著性标记箱线散点图 本篇笔记的内容是在R语言中利用ggplot2&#xff0c;ggsignif&#xff0c;ggsci&#xff0c;ggpubr等包制作箱线散点图&#xff0c;并计算指定变量之间的显著性水平&#xff0c;对不同分组进行特异性标记&#xff0c;最终效果如下。 加载R包 library(ggplo…

SQL注入漏洞利用(上)

SQL注入漏洞SQL注入漏洞SQL注入原理SQL注入带来的危害SQL注入分类数字型注入实操字符型注入实操类型检测and测试绕过密码&#xff1a;or 11 --搜索型注入实操SQL注入漏洞 攻击者利用Web应用程序对用户输入验证上的疏忽&#xff0c;在输入的数据中包含对某些数据库系统有特殊意…

离散数学笔记_第一章:逻辑和证明(2 )

1.2 命题逻辑的应用1.2.1 语句翻译 1.2.2 系统规范说明 1.2.3 布尔搜索 1.2.4 逻辑谜题泥巴孩子谜题骑士和流氓&#xff08;考研逻辑题&#xff09;1.1.2.5 逻辑电路1.2.1 语句翻译 &#x1f433;为啥要翻译语句&#xff1f; ➡因语言常常有二义性&#xff08;有歧义&#x…

Window.location 详细介绍

如果你需要获取网站的 URL 信息&#xff0c;那么 window.location 对象就是为你准备的。使用它提供的属性来获取当前页面地址的信息&#xff0c;或使用其方法进行某些页面的重定向或刷新。 https://www.samanthaming.com/tidbits/?filterJS#2 window.location.origin → htt…

Dbeaver连接Hive数据库操作指导

背景&#xff1a;由于工作需要&#xff0c;当前分析研究的数据基于Hadoop的Hive数据库中&#xff0c;且Hadoop服务端无权限进行操作且使用安全模式&#xff0c;在研究了Dbeaver、Squirrel和Hue三种连接Hive的工具&#xff0c;在无法绕开useKey认证的情况下&#xff0c;只能使用…

基于vscode开发vue项目的详细步骤教程

1、Vue下载安装步骤的详细教程(亲测有效) 1_水w的博客-CSDN博客 2、Vue下载安装步骤的详细教程(亲测有效) 2 安装与创建默认项目_水w的博客-CSDN博客 目录 五、vscode集成npm开发vue项目 1、vscode安装所需要的插件&#xff1a; 2、搭建一个vue小页面(入门vue) 3、大致理解…

近期常见组件漏洞更新:

&#xff08;1&#xff09;mysql 5.7 在2023年1月17日&#xff0c;发布了到5.7.41版本 mysql 8.0 在2023年1月17日&#xff0c;发布了到8.0.32版本 MySQL :: Download MySQL Community Serverhttps://dev.mysql.com/downloads/mysql/ &#xff08;2&#xff09;Tomcat8在202…

react react-redux数据共享学习记录

react react-redux数据共享1.目的2.数据共享版本2.1Person模块的添加2.1.1 Containers下的Person2.1.2 actions下的person.js2.1.3 reducers下的person.js2.2 store.js的改写&#xff01;2.3 组件中取出状态的时候&#xff0c;记得“取到位”3.纯函数1.目的 前面的react和reac…

精确光度预测计算工具:AGi32 Crack

什么是AGi32&#xff1f; AGi32首先是一种用于精确光度预测的计算工具&#xff1a;一种技术工具&#xff0c;可以计算任何情况下的照度&#xff0c;协助灯具放置和瞄准&#xff0c;并验证是否符合任意数量的照明标准。 然而&#xff0c;要增强对光度学结果的理解&#xff0c;还…

创建SpringBoot注意事项

作为一个java小白&#xff0c;你是否因为创建SpringBoot项目那些莫名其妙的错误搞得头皮发麻。不要慌张&#xff0c;这篇文章能帮你解决90%的问题【持续更新…】 本文结合创建SpringBoot项目的完整过程来讲 在idea中新建项目 虽然SpringBoot项目是由maven内核组成的&#xff0…

【Linux驱动】驱动设计硬件基础----串口、I2C、SPI、以太网接口、PCIE

1.前言 常见的外设接口与总线的工作方式&#xff0c;包括串口、I2C、SPI、USB、以太网接口、PCI和PCI-E、SD和SDIO等。 2.串口 RS-232、RS-422与RS-485都是串行数据接口标准&#xff0c;最初都是由电子工业协会&#xff08;EIA&#xff09;制订并发布的。 3.I2C I2C&…

canvas复习笔记(绘制直线、矩形、圆形、圆弧)

canvas 画一条直线 <body><canvasid"c"width"300"height"200"style"border: 1px solid #ccc;"></canvas> </body><script>// 2、获取 canvas 对象const cnv document.getElementById("c");…

数据结构与算法之Huffman tree(赫夫曼树 / 霍夫曼树 / 哈夫曼树 / 最优二叉树)

目录赫夫曼树概述定义构造赫夫曼树步骤代码实现赫夫曼树概述 HuffmanTree因为翻译不同所以有其他的名字&#xff1a;赫夫曼树、霍夫曼树、哈夫曼树 赫夫曼树又称最优二叉树&#xff0c;是一种带权路径长度最短的二叉树。所谓树的带权路径长度&#xff0c;就是树中所有的叶结点…

【IDEA】【工具】幸福感UP!开发常用的工具 插件/网站/软件

IDEA 插件 CodeGlance Pro —— 代码地图 CodeGlance是一款非常好用的代码地图插件&#xff0c;可以在代码编辑区的右侧生成一个竖向可拖动的代码缩略区&#xff0c;可以快速定位代码的同时&#xff0c;并且提供放大镜功能。 使用:可以通过Settings—>Other Settings—&g…

Linux学习(9.1)文件系统的简单操作

以下内容转载自鸟哥的Linux私房菜 原文&#xff1a;鸟哥的 Linux 私房菜 -- Linux 磁盘与文件系统管理 (vbird.org) 磁盘与目录的容量 df&#xff1a;列出文件系统的整体磁盘使用量&#xff1b;du&#xff1a;评估文件系统的磁盘使用量(常用在推估目录所占容量) df du 实体…

【数据库的基础知识(2)】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

【蓝桥杯每日一题】递推算法

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; 蓝桥杯 &#x1f319;我与杀戮之中绽放&#xff0c;亦如黎明的花…

Python字典-- 内附蓝桥题:统计数字

字典 ~~不定时更新&#x1f383;&#xff0c;上次更新&#xff1a;2023/02/28 &#x1f5e1;常用函数&#xff08;方法&#xff09; 1. dic.get(key) --> 判断字典 dic 是否有 key&#xff0c;有返回其对应的值&#xff0c;没有返回 None 举个栗子&#x1f330; dic …

实际案例呈现,教你如何掌握询盘细节

以下通过一个实际案例教大家如何掌握询盘细节&#xff0c;让你的回复率直线上升&#xff01; 这个朋友是从事首饰珠宝行业的&#xff0c;他在阿里巴巴上收到了一封来自摩洛哥买家的询盘&#xff0c;回复后客户就没有音讯了。 摩洛哥买家询盘内容&#xff1a; Hello sir Aft…

这么强才给我28k,我头都不回,转身拿下40k~

时间真的过得很快&#xff0c;眨眼就从校园刚出来的帅气小伙变成了油腻大叔&#xff0c;给各位刚入道的测试朋友一点小建议&#xff0c;希望你们直通罗马吧&#xff01; 如何选择自己合适的方向 关于选择测试管理&#xff1a; 第一&#xff0c;你一定不会是一个喜欢技术&…