C++智能指针原理和使用(续)

文章目录

  • 智能指针的原理
    • auto_ptr和unique_ptr的模拟实现
    • boost
    • shared_ptr(重点)
      • 拷贝赋值
      • shared_ptr底层实现的代码
  • 智能指针的使用
    • 定制删除器
      • shared_ptr
      • unique_ptr
  • 模拟实现定制删除器
  • shared_ptr和weak_ptr
    • shared_ptr循环引用问题(重点)
    • weak_ptr
  • shared_ptr的线程安全问题
  • 内存泄漏

智能指针的原理

智能指针就像是两个指针管理同一块资源,智能指针可以解决多次析构的问题
在这里插入图片描述

auto_ptr和unique_ptr的模拟实现

1. RAII构造函数保存资源,析构函数释放资源
2.这两个智能指针不太重要

auto_ptr

namespace wbc
{
	template<class T>
	class auto_ptr
	{
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& p)
			:_ptr(p._ptr)
		{
			// 管理权的转移
			p._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& p)
		{
			if (this != &p)
			{
				// 释放当前指针中的资源
				if (_ptr)
					delete _ptr;

				// 转移资源,浅拷贝
				_ptr = p._ptr;
				p._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;
	};

unique_ptr

template<class T>
class unique_ptr
{
public:
	explicit 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>& p) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& p) = delete;
	// 移动构造
	unique_ptr(unique_ptr<T>&& p)
		:_ptr(p._ptr)
	{
		p._ptr = nullptr;
	}

	unique_ptr<T>& operator=(unique_ptr<T>&& p)
	{
		delete _ptr;
		_ptr = p._ptr;
		p._ptr = nullptr;
	}

private:
	T* _ptr;
};

boost

Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,C++11智能指针都是从boost库中出来的
1.C++ 98 中产了第⼀个智能指针auto_ptr
2.C++ boost给出了更实用的scoped_ptr/scoped_arrayshared_ptr/shared_array和weak_ptr等
3.C++ TR1,引入了shared_ptr等,不过注意的是TR1并不是标准版
4.C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

shared_ptr(重点)

1. 重点要看看shared_ptr是如何设计的,尤其是引用计数的设计,主要这里一份资源就需要一个引用计数,所以引用计数才用静态成员的方式是无法实现的(静态的成员是所有对象所共享的)
2. 一块资源对应一块引用计数

在这里插入图片描述

3.要使用堆上动态开辟的方式,构造智能指针对象时来一份资源,就要new一个引用计数出来。多个shared_ptr指向资源时就++引用计数,shared_ptr对象析构时就–引用计数,引用计数减到0时代表当前析构的shared_ptr是最后一个管理资源的对象,则析构资源。

template<class T>
class shared_ptr
{
public:
	// 构造引用计数初始化为1
	shared_ptr(T* ptr)
		:_ptr(ptr),
		_count(new int(1))
	{}

	// 拷贝构造引用计数交给另一个指针管理
	// 引用计数++
	shared_ptr(const shared_ptr<T>& p)
		:_ptr(p._ptr),
		_count(p._count)
	{
		(*_count)++;
	}

	~shared_ptr()
	{
		if (--(*_count) == 0)
		{
			delete _count;
			delete _ptr;
		}
	}

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

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

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

int main()
{
	wbc::shared_ptr<Date> p1(new Date);
	wbc::shared_ptr<Date> p2(p1);
	wbc::shared_ptr<Date> p3(p2);

	// cout << p1.use_count << endl;
	
	// p4和p1,p2,p3各自有一份自己的引用计数
	wbc::shared_ptr<Date> p4(new Date);

	return 0;
}

说明sp1,sp2,sp3浅拷贝,共同管理同一块资源
sp4管理另一份资源
在这里插入图片描述

拷贝赋值

1.拷贝赋值分两种情况:
1、有多个指针指向同一块资源,引用计数减减即可,另一份引用计数加加
2、只有一个指针指向同一块资源,引用计数减到0,销毁这块指针,然后指向新的资源,引用计数加加

2.自己给自己赋值的问题:
1、(p4 = p4)如果引用计数是1的话,减减为0,会delete,就产生了野指针
2、(p1 = p2,p1和p2指向同一块资源,对象的地址是不一样的),走一遍拷贝赋值相当于做无用功
应该比较同一块资源的地址或者是引用计数的地址

在这里插入图片描述

// 浅拷贝 + 引用计数
// p1 = p4
shared_ptr<T>& operator=(shared_ptr<T>& p)
{
	if (_ptr != p._ptr)
	{
		if (--(*_count) == 0)
		{
			delete _count;
			delete _ptr;
		}

		_ptr = p._ptr;
		_count = p._count;
		(*_count)++;
	}
	return *this;
}
// 赋值拷贝 
p1 = p4;
p1 = p2;

shared_ptr底层实现的代码

template<class T>
class shared_ptr
{
public:
	// 构造引用计数初始化为1
	shared_ptr(T* ptr)
		:_ptr(ptr),
		_count(new int(1))
	{}

	// 拷贝构造引用计数交给另一个指针管理
	// 引用计数++
	shared_ptr(const shared_ptr<T>& p)
		:_ptr(p._ptr),
		_count(p._count)
	{
		(*_count)++;
	}

	~shared_ptr()
	{
		if (--(*_count) == 0)
		{
			delete _count;
			delete _ptr;
		}
	}

	// 浅拷贝 + 引用计数
	// p1 = p4
	shared_ptr<T>& operator=(shared_ptr<T>& p)
	{
		if (_ptr != p._ptr)
		{
			if (--(*_count) == 0)
			{
				delete _count;
				delete _ptr;
			}

			_ptr = p._ptr;
			_count = p._count;
			(*_count)++;
		}
		return *this;
	}

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

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

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

int main()
{
	wbc::shared_ptr<Date> p1(new Date);
	// 拷贝构造
	wbc::shared_ptr<Date> p2(p1);
	wbc::shared_ptr<Date> p3(p2);

	// cout << p1.use_count << endl;
	
	// p4和p1,p2,p3各自有一份自己的引用计数
	wbc::shared_ptr<Date> p4(new Date);

	// 赋值拷贝 
	// p1 = p4;
	p1 = p2;

	return 0;
}

智能指针的使用

定制删除器

1.智能指针析构时默认是进行delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃
2. 因为new[]经常使用,所以为了简洁一点,unique_ptr和shared_ptr都特化了一份[]的版本,就可以管理new[]的资源了
3. 智能指针支持在构造时给一个删除器,所谓删除器本质就是一个可调用对象这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调用删除器去释放资源

int main()
{
	// 智能指针底层都是new出来的,最后都是delete的
	// 会出现new[] 和 delete不匹配的问题
	std::shared_ptr<Date> p1(new Date);
	std::shared_ptr<Date> p2(new Date[10]);

	// Date[]会被识别为指针
	std::shared_ptr<Date[]> p3(new Date[10]);
	std::unique_ptr<Date[]> p4(new Date[10]);

	return 0;
}

shared_ptr

这种文件指针没有特化,需要用删除器解决
在这里插入图片描述

shared_ptr的删除器
在这里插入图片描述

可调用对象可以是仿函数,lambda,函数指针
在这里插入图片描述
shared_ptr删除器建议用lambda
构造的时候传定制删除器
还是用lambda更方便,就不需要写仿函数了
删除器可以解决一切问题

// 仿函数
class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};

// 函数指针
template<class T>
void DeleteArrayFunc(T* ptr)
{
	delete[] ptr;
}

int main()
{
	// 仿函数
	std::shared_ptr<FILE> p5(fopen("test.cpp", "r"), Fclose());
    
    // 函数指针
    // DeleteArrayFunc必须实例化,加上<Date>
    std::shared_ptr<Date> p5(new Date[10],       DeleteArrayFunc<Date>);

	// lambda
	std::shared_ptr<FILE> p5(fopen("test.cpp", "r"), [](FILE* ptr)
		{
			fclose(ptr);
		});
	std::shared_ptr<Date> p2(new Date[10], [](Date* ptr)
		{delete[] ptr; });

	return 0;
}

unique_ptr

在这里插入图片描述
类声明的时候传定制删除器
在这里插入图片描述
unique_ptr删除器建议用仿函数

class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};

// 仿函数
// 模版的位置要传类型
std::unique_ptr<FILE, Fclose> up1(fopen("test.cpp","r"));

// lambda
// decltype可以推导类型
auto fcloseFunc = [](FILE* ptr) {fclose(ptr); };
// 用lambda的uid要生成删除器的对象,生成不出来
// lambda禁用了默认构造,
// 所以后面参数要传对象去拷贝构造
// 这样才可以
std::unique_ptr<FILE, decltype(fcloseFunc)> up3(fopen("test.cpp", "r"),fcloseFunc);

// 函数指针
unique_ptr<Date, void(*)(Date*)> up3(new Date[10], DeleteArrayFunc<Date>);

1. shared_ptr 除了支持用指向资源的指针构造,还支持 make_shared 用初始化资源对象的值
直接构造。
2. 了解一下即可make_shared是模版要传类型实例化,make_shared比shared_ptr更加高效一些make_shared开一次空间,shared_ptr开两次空间,因为创建一个shared_ptr就需要一个引用计数,非常多个shared_ptr就会产生多个引用计数的碎片,影响效率,而make_shared会把资源和引用计数开到一起,就跟之前的new那里的引用计数一样开到资源的前面

shared_ptr<Date> sp3(new Date(2024, 1, 5));
shared_ptr<Date> sp4 = make_shared<Date>(2024, 1, 5);

3. shared_ptr 和 unique_ptr 都支持了operator bool的类型转换(自定义类型转为内置类型),如果智能指针对象是一个空对象没有管理资源,则返回false,否则返回true,意味我们可以直接把智能指针对象给if判断是否为空

int main()
{
	std::shared_ptr<Date> sp1(new Date);
	std::shared_ptr<Date> sp2(new Date[10], [](Date* ptr) {delete[] ptr; });

	shared_ptr<Date> sp3(new Date(2024, 1, 5));
	shared_ptr<Date> sp4 = make_shared<Date>(2024, 1, 5);
	shared_ptr<Date> sp5;

	// if(sp1.operator bool())
	if (sp1)
	{
		cout << "sp1 is not nullptr" << endl;
	}
	// if(!sp5.operator bool())
	if (!sp5)
	{
		cout << "sp5 is nullptr" << endl;
	}

	return 0;
}
  1. shared_ptr 和 unique_ptr 都得构造函数都使用explicit 修饰(不支持隐式类型转化),防止普通指针隐式类型转换成智能指针对象。
// Date是Date*隐式类型转化为临时对象,临时对象拷贝构造sp6
// shared_ptr<Date> sp6 = new Date(2025,1,5);
// unique_ptr<Date> sp7 = new Date(2025, 1, 5);

模拟实现定制删除器

#include<functional>
#include<memory>

template<class T>
class shared_ptr
{
public:
	// 构造引用计数初始化为1
	// 没传定制删除器会走默认的function
	// 所以new的这个要写默认的function
	shared_ptr(T* ptr)
		:_ptr(ptr),
		_count(new int(1))
	{}

	template<class D>
	shared_ptr(T* ptr,D del)
		:_ptr(ptr),
		_count(new int(1)),
		_del(del)
	{}


	// 拷贝构造引用计数交给另一个指针管理
	// 引用计数++
	shared_ptr(const shared_ptr<T>& p)
		:_ptr(p._ptr),
		_count(p._count)
	{
		(*_count)++;
	}

	~shared_ptr()
	{
		if (--(*_count) == 0)
		{
			delete _count;
			_del(_ptr);
			// delete _ptr;
		}
	}

	// 浅拷贝 + 引用计数
	// p1 = p4
	shared_ptr<T>& operator=(shared_ptr<T>& p)
	{
		if (_ptr != p._ptr)
		{
			if (--(*_count) == 0)
			{
				delete _count;
				delete _ptr;
			}

			_ptr = p._ptr;
			_count = p._count;
			(*_count)++;
		}
		return *this;
	}

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

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

private:
	T* _ptr;
	int* _count;
	function<void(T*)> _del = [](T* p) {delete[] p; };
};

shared_ptr和weak_ptr

在这里插入图片描述
在这里插入图片描述

shared_ptr循环引用问题(重点)

1. shared_ptr大多数情况下管理资源非常合适,支持RAII,也支持拷贝。但是在循环引用的场景下会导致资源没得到释放内存泄漏,所以我们要认识循环引用的场景和资源没释放的原因,并且学会使用weak_ptr解决这种问题

极端场景会出现循环引用问题

struct ListNode
{
	int _data;

	ListNode* _next;
	ListNode* _prev;
	
	std::shared_ptr<ListNode> _next;
	std::shared_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;
    
    return 0;
}

n1和n2析构后,n1和n2的引用计数减到1,会出现循环引用的问题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用weak_ptr解决了循环引用的问题

struct ListNode
{
	int _data;

	// ListNode* _next;
	// ListNode* _prev;
	// std::shared_ptr<ListNode> _next;
	// std::shared_ptr<ListNode> _prev;

	// 这里改成weak_ptr,当n1->_next = n2;绑定shared_ptr时
	// 不增加n2的引用计数,不参与资源释放的管理,就不会形成循环引用了
	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);

	// use_count打印引用计数的个数
    cout << n1.use_count() << endl;
    cout << n2.use_count() << endl;

    n1->_next = n2;
    n2->_prev = n1;

    cout << n1.use_count() << endl;
    cout << n2.use_count() << endl;
    
    return 0;
}

weak_ptr

1. weak_ptr不支持RAII,也不支持访问资源,所以我们看问档发现weak_ptr构造时不支持绑定到资源只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引用计数,那么就可以解决上述的循环引用问题
2. 问题:weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的。
3. 解决方法:weak_ptr支持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引用计数,weak_ptr想访问资源时,可以调用lock返回一个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是一个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

weak_ptr解决悬空和野指针的问题,遇到了循环引用的时候

用引用计数检查shared_ptr资源是否已经被释放了,wp1指向的引用计数减为0,但是不释放引用计数
在这里插入图片描述
expired
use_count == 0 返回true 过期
use_count != 0 返回false 没有过期
在这里插入图片描述

int main()
{
	std::shared_ptr<string> sp1(new string("111111"));
	std::shared_ptr<string> sp2(sp1);
	std::weak_ptr<string> wp = sp1;

	// expired检查资源没有过期返回false
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	// sp1和sp2都指向了其他资源,则weak_ptr就过期了
	sp1 = make_shared<string>("222222");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	sp2 = make_shared<string>("333333");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	return 0;
}

weak_ptr的底层:
如果有weak_ptr的话,引用计数减到0,还会继续套一层引用计数;
引用计数减到0,如果没有weap_ptr就直接释放引用计数
在这里插入图片描述

lock:锁住资源即使是引用计数减到0后,weak_ptr还要保住这份资源
在这里插入图片描述
使用一个sp3自立一个大哥锁住这份资源,即使是sp1不指向这块资源了

wp = sp1;
//std::shared_ptr<string> sp3 = wp.lock();
auto sp3 = wp.lock();
cout << wp.expired() << endl;// 0
cout << wp.use_count() << endl;// 2

sp1 = make_shared<string>("55555");
cout << wp.expired() << endl;// 0
cout << wp.use_count() << endl;// 1

shared_ptr的线程安全问题

1. 引用计数++,加多了,然后减少了,就会没有析构
2. 引用计数- -,减多了,减到了负数,就会提前析构
3. 引用计数加多了,减少了(引用计数不是0)就需要使用锁

解决上述问题有两种方法:
可以解决同时加的问题,可以让线程一个加完了另外一个再加,按顺序地加

  • 用锁的方法,这个要学了多线程后更好理解
  • 用atomic< int >* _count; 原子操作

1. shared_ptr的引用计数对象在堆上,如果多个shared_ptr对象在多个线程中,进行shared_ptr的拷贝析构时会访问修改引用计数,就会存在线程安全问题,所以shared_ptr引用计数是需要加锁或者原子操作保证线程安全的。
2.shared_ptr指向的对象也是有线程安全的问题的,但是这个对象的线程安全问题不归shared_ptr管,它也管不了,应该有外层使用shared_ptr的人进行线程安全的控制。
3. 下面的程序会崩溃或者AA资源没释放,wbc::shared_ptr引用计数从int*改成atomic< int >*就可以保证引用计数的线程安全问题,或者使用互斥锁加锁也可以。

智能指针本身是线程安全的,指向的资源不是线程安全的

#include<iostream>
#include<functional>
#include<memory>
#include<atomic>
#include<mutex>
#include<thread>

namespace wbc
{
	template<class T>
	class shared_ptr
	{
	public:
		// 构造引用计数初始化为1
		// 没传定制删除器会走默认的function
		// 所以new的这个要写默认的function
		shared_ptr(T* ptr)
			:_ptr(ptr),
			_pcount(new atomic<int>(1))
		{
		}

		template<class D>
		shared_ptr(T* ptr, D del)
			: _ptr(ptr),
			_pcount(new atomic<int>(1)),
			_del(del)
		{
		}


		// 拷贝构造引用计数交给另一个指针管理
		// 引用计数++
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr),
			_pcount(sp._pcount)
		{
			(*_pcount)++;
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				_del(_ptr);
				delete _pcount;
				// delete _ptr;
			}
		}

		// 浅拷贝 + 引用计数
		// p1 = p4
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				if (--(*_pcount) == 0)
				{
					delete _ptr;
					delete _pcount;
				}

				_pcount = sp._pcount;
				_ptr = sp._ptr;
				++(*_pcount);
			}
			return *this;
		}

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

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

		int use_count()
		{
			return *_pcount;
		}

	private:
		T* _ptr;
		// int* _count;
		atomic<int>* _pcount;// 原子操作

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

struct AA
{
	int _a1 = 0;
	int _a2 = 0;
	~AA()
	{
		cout << "~AA()" << endl;
	}
};

int main()
{
    wbc::shared_ptr<AA> p(new AA);
    const size_t n = 100000;

    mutex mtx;
    auto func = [&]()
    {
        for (size_t i = 0; i < n; ++i)
        {
            // 这里智能指针拷贝会++计数
            wbc::shared_ptr<AA> copy(p);
            /*{
                unique_lock<mutex> lk(mtx);
                copy->_a1++;
                copy->_a2++;
            }*/
        }
    };

    thread t1(func);
    thread t2(func);

    t1.join();
    t2.join();

    cout << p->_a1 << endl;
    cout << p->_a2 << endl;

    cout << p.use_count() << endl;

    return 0;
}

内存泄漏

1. 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存,一般是忘记释放或者发生异常释放程序未能执行导致的。
2. 内存泄漏的危害:普通程序运行一会就结束了出现内存泄漏问题也不大,进程正常结束,页表的映射关系解除,物理内存也可以释放。
服务器等内存泄漏会导致内存不断变少,导致卡死。

int main()
{
	// 申请⼀个1G未释放,这个程序多次运⾏也没啥危害
	// 因为程序⻢上就结束,进程结束各种资源也就回收了
	char* ptr = new char[1024 * 1024 * 1024];
	cout << (void*)ptr << endl;
	return 0;
}

解决方法:

1.尽量使用智能指针来管理资源,如果自己场景比较特殊,采用RAII思想自己造个轮子管理
2. 定期使用内存泄漏工具检测,尤其是每次项目快上线前,不过有些工具不够靠谱,或者是收费
3.总结一下:
内存泄漏非常常见,解决方案分为两种:
1、事前预防型,如智能指针等。(公司比较倾向于第一种)
2、事后查错型,如泄漏检测工具。

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

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

相关文章

LLM:GPT 系列

阅读原文&#xff1a; LLM&#xff1a;Qwen 系列 GPT&#xff08;Generative Pre-trained Transformer&#xff09;是生成式预训练语言模型&#xff0c;基于 Transformer 架构&#xff0c;专注于通过自回归的方式生成自然语言文本&#xff0c;即给定一个输入序列 x { x 1 , …

ollama+langchain+deepseek本机跑通大模型

一、部署deepseek Ollama&#xff0c;这是是一个开源的大语言模型平台&#xff0c;它允许用户在本地环境中运行、创建和共享大型语言模型。Ollama提供了丰富的功能和特性&#xff0c;使得用户可以在自己的计算机上轻松地部署和运行大型语言模型。官网&#xff1a;https://ollam…

同步buck型降压DCDC电路设计

参考资料&#xff1a; 嵌入式-硬件-DCDC&#xff08;BUCK&#xff09;电路分析_dcdc buck-CSDN博客 1、基本原理 上图左是异步BUCK&#xff0c;右图是同步BUCK。右边使用元件较多&#xff0c;但是效率高&#xff0c;发热小。同样输出功率的前提下&#xff0c;虽然右边元件多&…

数据挖掘智能Agent

&#x1f917; CodeGenie - 智能编程助手 数据处理和分析对于数据分析工作人员来说&#xff0c;往往既复杂又令人头疼&#xff0c;需要耗费大量精力进行重复性工作。为了解决这一问题&#xff0c;我们开发了一款集成了自然语言处理和代码生成功能的智能编程助手——CodeGenie。…

人工智能之自然语言处理技术演进

自然语言处理技术演进 自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是人工智能的重要分支&#xff0c;旨在使计算机能够理解、生成和处理人类语言。近年来&#xff0c;NLP技术经历了从规则驱动到数据驱动的革命性演进&#xff0c;尤其是在…

保姆级GitHub大文件(100mb-2gb)上传教程

GLF&#xff08;Git Large File Storage&#xff09;安装使用 使用GitHub desktop上传大于100mb的文件时报错 The following files are over 100MB. lf you commit these files, you will no longer beable to push this repository to GitHub.com.term.rarWe recommend you a…

Spring Boot(7)Spring Boot 注解全解析:深入理解与应用

搞个引言 在现代 Java 开发中&#xff0c;Spring Boot 凭借其便捷性和高效性成为了众多开发者的首选框架。而注解作为 Spring Boot 的核心特性之一&#xff0c;极大地简化了开发过程&#xff0c;提高了代码的可读性和可维护性。本文将深入探讨 Spring Boot 中几种重要的注解&a…

【在idea中配置两个不同端口,同时运行两个相同的主程序springboot】

step1&#xff1a; step2&#xff1a; step3&#xff1a;指定端口号&#xff0c;点击apply 经过以上步骤后&#xff0c;idea下面就会出现service选项 启动两个springboot就完成了&#xff08;我的启动失败是因为redis没有启动&#xff0c;springboot没有连接到redis报错&a…

基于Flask的全国婚姻关系数据可视化分析系统的设计与实现

【FLask】基于Flask的全国婚姻关系数据可视化分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统采用Python作为后端开发语言&#xff0c;结合Flask后端框架和Bootstra…

基于Kotlin中Flow扩展重试方法

最近项目中统一采用Kotlin的Flow来重构了网络请求相关代码。 目前的场景是&#xff0c;接口在请求的时候需要一个accessToken值&#xff0c;因为此值会过期或者不存在&#xff0c;需要刷新&#xff0c;因此最终方案是在使用Flow请求的时候先获取accessToken值然后再进行接口请求…

达梦 跟踪日志诊断

目录标题 参考连接**性能诊断&#xff1a;跟踪日志诊断****总结** 参考连接 性能诊断 -> 跟踪日志诊断 性能诊断&#xff1a;跟踪日志诊断 备份现有的日志配置文件 在修改文件之前&#xff0c;建议先备份原始文件&#xff0c;以防万一需要恢复。 cp /opt/dmdbms/dmdata/DA…

RFID智能仓储管理系统:助力仓储数字化升级

​在现代物流与仓储管理中&#xff0c;RFID智能仓储管理系统凭借其高效、精准的特点&#xff0c;已成为企业提升仓库运作效率的重要工具。结合RFID仓库管理、RFID库存管理及智能仓储管理系统等技术&#xff0c;RFID智能仓储解决方案能够实现仓储全流程的自动化与智能化&#xf…

AI 编程私有化部署,在使用 cline 时,可能无法避免私隐的泄漏问题

摘录&#xff1a;Cline Privacy Policy https://github.com/cline/cline/blob/main/docs/PRIVACY.md Key Points Cline operates entirely client-side as a VS Code extensionNo code or data is collected, stored, or transmitted to Clines servers 问题是&#xff1a…

【进阶】MySQL高级篇超详讲解!!!

Mysql服务器内部架构&#xff08;了解&#xff09; 连接层 负责客户端的链接&#xff0c;验证账号密码等授权认证 服务层 对sql进行解析&#xff0c;优化&#xff0c;调用函数&#xff0c;如果是查询操作&#xff0c;有没有缓存等操作。 引擎层 是真正负责数据存储和提取…

【学术投稿-第四届智能电网和绿色能源国际学术会议(ICSGGE 2025)】CSS基本选择器详解:掌握基础,轻松布局网页

可线上 官网&#xff1a;www.icsgge.org 时间&#xff1a;2025年2月28-3月2日 目录 前言 一、基本选择器简介 1. 元素选择器&#xff08;Type Selector&#xff09; 基本语法 示例 注意事项 2. 类选择器&#xff08;Class Selector&#xff09; 基本语法 示例 注意…

编程题-字母异位词分组(中等-重点)

题目&#xff1a; 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 解法一&#xff08;for循环遍历-时间复杂度超限&#xff09;&#xff1a; 由于互为字母异位词的…

Spring Boot整合DeepSeek实现AI对话(API调用和本地部署)

本篇文章会分基于DeepSeek开放平台上的API&#xff0c;以及本地私有化部署DeepSeek R1模型两种方式来整合使用。 本地化私有部署可以参考这篇博文 全面认识了解DeepSeek利用ollama在本地部署、使用和体验deepseek-r1大模型 Spring版本选择 根据Spring官网的描述 Spring AI是一…

阿里云IOT消息处理

文章主要讲述了阿里云IOT平台如何处理设备上报的消息、如何将消息路由到不同的处理逻辑、如何进行消息转发与转换等操作。 一、接收IOT消息 1.创建订阅 2.案列代码 官网案例代码&#xff1a;如何将AMQP JMS客户端接入物联网平台接收消息_物联网平台(IoT)-阿里云帮助中心 代码…

YOLO11网络结构以及改进1

YOLO11 1.YOLO11网络结构图在哪里&#xff1f;2.对应的网络结构图3.每一个模块详解3.1 Conv模块3.2关于卷积模块3.3 关于给各个模块指定参数的细节 4.加入CBAM 1.YOLO11网络结构图在哪里&#xff1f; 2.对应的网络结构图 3.每一个模块详解 3.1 Conv模块 位置&#xff1a;ultr…

数据结构——队列、哈希存储(2025.2.11)

目录 一、队列 1.定义 2.应用 3.分类 &#xff08;1&#xff09;逻辑结构 &#xff08;2&#xff09;物理结构 顺序队列 链式队列 二、哈希存储 1.定义 2.哈希冲突 &#xff08;1&#xff09;开放定址法 &#xff08;2&#xff09;再哈希法 &#xff08;3&#xf…