[C++11#48][智能指针] RAII原则 | 智能指针的类型 | 模拟实现 | shared_ptr | 解决循环引用

目录

一.引入

1. 为什么需要智能指针?

2. 什么是内存泄漏?

内存泄漏分类

3.回忆 this

二. 原理

1. RAII 资源获取即初始化

2.像指针一样

三. 使用

1. 问题: string 的浅拷贝

2.解决

auto_ptr

自定义 auto_ptr

unique_ptr - 独占式智能指针

shared_ptr - 共享式智能指针

⭕ shared_ptr 的赋值 sum

weak_ptr - 弱引用指针

详解

模拟实现:

四.思考

1. 智能指针的线程安全性

2. 循环引用问题

总结


前言:

java 有虚拟机,有垃圾回收,但性能就没那么高了

C++要手动 delete,但是有了异常之后,一切就变得不可控了

所以就创建智能指针接收,出作用域后自动析构

在现代C++编程中,内存管理一直是一个非常重要的课题。智能指针的出现解决了传统指针所带来的内存泄漏、异常安全等问题。本文将从智能指针的需求、RAII思想、常见的智能指针类型以及相关实现原理等方面进行详细探讨。

重点如下:

一.引入

1. 为什么需要智能指针?

在传统的C++编程中,手动管理内存非常繁琐且容易出错。考虑下面的例子:

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;int *p2 = new int;cout << div() << endl;delete p1;delete p2;
}int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

在此代码中,虽然我们用 try-catch 捕获异常,并确保资源在异常时能够释放,但这会导致代码冗余且难以维护。此外,如果在 new 操作时抛出异常(例如内存不足),没有捕获到的 p1p2 将导致内存泄漏。

例如:

抛异常后,会直接跳转到catch部分,导致原本的delete被跳过,导致内存泄漏

智能指针通过将资源的生命周期与对象生命周期绑定,使得即便抛出异常,内存也能正确释放,从而极大地提升了代码的健壮性和可维护性。

2. 什么是内存泄漏?

内存泄漏是指程序由于疏忽或错误,未能释放已不再使用的内存。这并不是指内存物理上消失,而是应用程序在分配了一段内存后,因设计错误失去对该段内存的控制,导致这部分内存无法被使用,从而浪费了内存资源。

内存泄漏的危害
特别是对于长期运行的程序(如操作系统、后台服务等),内存泄漏会导致可用内存逐渐减少,进而使得系统响应越来越慢,直至最终卡死或崩溃。

示例代码:

void MemoryLeaks() {// 1.内存申请了忘记释放int *p1 = (int *)malloc(sizeof(int));int *p2 = new int;// 2.异常安全问题int *p3 = new int[10];Func();  // 函数抛异常导致 delete[] p3 未执行,p3 没有被释放。delete[] p3;
}

内存泄漏分类

在C/C++程序中,内存泄漏主要分为两类:

  1. 堆内存泄漏(Heap leak)
    堆内存是通过 malloc / calloc / realloc / new 等分配的内存,用完后必须通过 free 或者 delete 释放。若程序设计错误导致内存没有释放,则会产生堆内存泄漏,导致内存浪费。
  2. 系统资源泄漏
    程序使用的系统资源(如套接字、文件描述符、管道等)若未使用相应的函数释放,会导致系统资源浪费,严重时可能会导致系统性能下降或不稳定。

如何检测内存泄漏

  1. Linux下检测
    使用Linux平台的内存泄漏检测工具。
  2. Windows下检测
    使用第三方工具,例如VLD(Visual Leak Detector)。
  3. 其他工具
    可以使用内存泄漏工具对比不同检测工具的优劣,选择合适的工具。

如何避免内存泄漏

  1. 良好的设计规范
    在工程的前期,遵循良好的编码规范,确保所有申请的内存都能被及时释放。
  2. RAII思想与智能指针
    使用RAII(Resource Acquisition Is Initialization)思想或智能指针来管理内存与资源,避免手动释放内存时的遗漏和错误。
  3. 私有内存管理库
    有些公司会开发自己的私有内存管理库,这些库通常带有内存泄漏检测功能。
  4. 使用内存泄漏检测工具
    在出问题时,使用内存泄漏工具进行检测和调试,确保问题能及时修复。

总结:

  • 事前预防:使用智能指针等技术。
  • 事后检测:使用内存泄漏检测工具。

3.回忆 this

在C++中,返回this指针是一种常见的做法,指向当前正在被成员函数操作的对象实例,即对自己的调用处理,特别是在实现链式调用(也称为流畅接口)时。这允许方法在修改对象的状态后返回对象的引用,从而可以在单个语句中连续调用多个方法。
以下是一个使用this指针实现链式调用的简单例子:

#include <iostream>
class Printer {
public:Printer& setFont(const std::string& font) {this->font = font;return *this; // 返回对象的引用}Printer& setSize(int size) {this->size = size;return *this; // 返回对象的引用}void print() const {std::cout << "Printing with font " << font << " and size " << size << std::endl;}
private:std::string font;int size;
};
int main() {Printer printer;// 链式调用printer.setFont("Arial").setSize(12).print();return 0;
}

在这个例子中,Printer类有两个方法setFontsetSize,它们都接受参数并修改对象的内部状态。每个方法在修改状态后都返回this指针的引用,这样就可以在调用一个方法后立即调用另一个方法,形成一个链式调用。


这里是详细解释:

  • setFont方法接受一个字符串参数font,并将其存储在私有成员变量font中。之后,它返回*this,即对象自身的引用。
  • setSize方法接受一个整数参数size,并将其存储在私有成员变量size中。同样,它返回*this
  • main函数中,我们创建了一个Printer对象,并通过链式调用的方式设置了字体和大小,然后调用print方法来打印结果。
    通过返回this指针,我们能够构建出流畅的接口,让代码更加易读和易用。

二. 原理

⭕ 智能指针的原理:

  1. RAII特性智能指针通过构造和析构自动管理资源,避免手动释放内存。
  2. 重载操作符:通过重载 *-> 操作符,智能指针能够像普通指针一样使用,具备解引用和访问对象成员的能力。

1. RAII 资源获取即初始化

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种利用对象生命周期来管理资源的技术。通过RAII思想,资源的分配在对象的构造函数中完成,资源的释放则在析构函数中进行。智能指针正是运用了RAII思想,使得资源管理更加安全。

例如,使用RAII设计的智能指针 SmartPtr

template <class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr) : _ptr(ptr) {}~SmartPtr() {if (_ptr) delete _ptr;}
private:T* _ptr;
};

SmartPtr 中,构造函数获取资源,析构函数负责释放资源。这就确保了无论函数如何退出,资源都能得到正确释放。

2.像指针一样

上述的 SmartPtr 类只是简单的内存管理类,还不能完全称为智能指针。为了让它像普通指针一样使用,还需要重载 *-> 操作符,使其具备指针的行为。

示例代码:

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;
};struct Date {int _year;int _month;int _day;
};int main() {SmartPtr<int> sp1(new int);*sp1 = 10;SmartPtr<Date> sparray(new Date);sparray->_year = 2018;sparray->_month = 1;sparray->_day = 1;
}


三. 使用

1. 问题: string 的浅拷贝

析构了两次的原因。因为我们没写拷贝构造。

那我们写个拷贝构造进行深拷贝解决一下?
不可以。不能深拷贝,因为智能指针模仿的就是原生指针的行为,期望指向的就是同一个。

测试:

template<class T>
class Smartptr
{
public://RAII//保存资源Smartptr(T* ptr):_ptr(ptr){}//释放资源~Smartptr(){delete _ptr;cout << _ptr << endl;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}
private:T* _ptr;
};int main()
{Smartptr<int> sp1(new int);Smartptr<int> sp2(sp1);return 0;
}

浅拷贝,重复释放,导致内存泄漏

_CrtIsValidHeapPointer(block)。这个错误通常表示你的程序遇到了堆内存问题,可能是由于非法访问或者尝试释放已经被释放过的内存导致的

于是实践当中出现了四种智能指针:

  • C++98 auto_ptr(不好用)
  • C++11 unique_ptr
  • C++11 shared_ptr
  • C++11 weak_ptr

2.解决

C++11及其之后的标准中引入了多种智能指针类型,主要包括 auto_ptr(已废弃)、unique_ptrshared_ptrweak_ptr。下面将对这些智能指针进行介绍。

auto_ptr

  • C++98 一般实践中,很多公司明确规定不要用 auto_ptr
  • 管理权转移,拷贝时,会把被拷贝对象的资源管理权转移给拷贝对象

隐患:导致被拷贝对象悬空,访问就会出问题

int main()
{bit::auto_ptr<A> ap1(new A(1));bit::auto_ptr<A> ap2(new A(2));bit::auto_ptr<A> ap3(ap1);// 崩溃//ap1->_a++;ap3->_a++;return 0;
}
自定义 auto_ptr
  • RAII
  • 像指针一样
  • 实现拷贝
namespace bit
{template<class T>class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}~auto_ptr(){cout << "delete:" << _ptr << endl;delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// ap3(ap1)// 管理权转移auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}private:T* _ptr;};

例如:将 ap1 转化给 ap3 后置空,但留下了隐患

int main()
{bit::auto_ptr<int> ap1(new int);bit::auto_ptr<int> ap2(ap1);(*ap2)++;(*ap1)++;return 0;
}

异常:

优化发展

最早是在 boost 库中出现

boost库出现的智能指针
scoped_ptr
shared_ptr/weak_ptr

  • C++11也搞了智能指针。
    C++11 unique_ptr 就是抄的 scoped_ptr
    C++11 也有 shared_ptr/weak_ptr

unique_ptr - 独占式智能指针

unique_ptr 通过防止拷贝来确保一个对象只能由一个 unique_ptr 所管理。它的拷贝构造函数和赋值运算符都被删除了:

template <class T>
class unique_ptr {
public:unique_ptr(T* ptr = nullptr) : _ptr(ptr) {}~unique_ptr() { delete _ptr; }unique_ptr(const unique_ptr&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;
private:T* _ptr;
};

适合不需要共享资源的场景。

C++中发展出了 unique_ptr,解决方案非常简单粗暴。 --> 防拷贝,因此也叫做唯一指针

禁拷贝的时候,也要防赋值,完整实现如下:

template<class T>
class unique_ptr
{
public://保存资源unique_ptr(T* ptr):_ptr(ptr){}//释放资源~unique_ptr(){delete _ptr;cout << _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;}T& operator[](size_t pos){return _ptr[pos];}
private:T* _ptr;
};

需要拷贝的时候,怎么办呢?

shared_ptr - 共享式智能指针

shared_ptr 允许多个指针共享同一份资源,并通过引用计数来管理资源的生命周期。当最后一个 shared_ptr 销毁时,资源才会被释放:

(面试要手撕,就撕这个)

通过引用计数来实现,期望一个资源一个引用计数

template <class T>
class shared_ptr {
public:shared_ptr(T* ptr = nullptr) : _ptr(ptr), _count(new int(1)) {}~shared_ptr() { Release(); }shared_ptr(const shared_ptr& sp) : _ptr(sp._ptr), _count(sp._count) {(*_count)++;}shared_ptr& operator=(const shared_ptr& sp) {if (this != &sp) {Release();_ptr = sp._ptr;_count = sp._count;(*_count)++;}return *this;}
private:void Release() {if (--(*_count) == 0) {delete _ptr;delete _count;}}T* _ptr;int* _count;
};

测试:

int main()
{// C++11bit::shared_ptr<A> sp1(new A(1));bit::shared_ptr<A> sp2(new A(2));bit::shared_ptr<A> sp3(sp1);sp1->_a++;sp3->_a++;cout << sp1->_a << endl;bit::shared_ptr<A> sp4(sp2);bit::shared_ptr<A> sp5(sp4);
}

赋值的实现

测试 sp1=sp5 sp3=sp5

  1. sp1._pcount--
  2. 赋值
shared_ptr<T>& operator=(const shared_ptr<T>& sp){//避免对同一份资源拷贝,进行一下判断要if (_ptr == sp._ptr)return *this;//左边资源要注意是不是最后一个了if (--(*_pcount) == 0){delete _ptr;delete _pcount;}//赋值拷贝_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);return *this;}

特例的解决:

  1. sp3=sp3 会出现野指针
  2. sp4 =sp5

在最开始的时候,最好用资源来判定一下

⭕ shared_ptr 的赋值 sum
  1. 避免对同一份资源拷贝,进行一下判断要
  2. 左边资源要注意是不是最后一个了
  3. 最后 赋值拷贝

shared_ptr 适用于多个对象共享同一资源的场景,但需要注意循环引用的问题。

weak_ptr - 弱引用指针

weak_ptr 是为了解决 shared_ptr 的循环引用问题而引入的,它不增加资源的引用计数,只作为一种弱引用存在,指向 shared_ptr 所管理的资源。通过 lock 方法,可以从 weak_ptr 获取一个 shared_ptr

template <class T>
class weak_ptr {
public:weak_ptr() : _ptr(nullptr) {}weak_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr) {}weak_ptr& operator=(const shared_ptr<T>& sp) { _ptr = sp._ptr; return *this; }
private:T* _ptr;
};
详解

引入:结构体内改成智能指针,解决类型不匹配

struct ListNode
{int val;//ListNode* _next;//ListNode* _prev;shared_ptr<ListNode> _next;shared_ptr<ListNode> _prev;//这里主要是看释放没有~ListNode(){cout << "~ListNode()" << endl;}
};void test_shared_ptr2()
{//ListNode* n1 = new ListNode;//ListNode* n2 = new ListNode;shared_ptr<ListNode> n1(new ListNode);shared_ptr<ListNode> n2(new ListNode);//n1,n2改成shared_ptr这里会出现类型不匹配的问题//一个是智能指针自定义类型对象,一个是普通类内置类型对象//因此上面也改成智能指针对象,就可以了n1->_next = n2;n2->_prev = n1;//delete n1;//delete n2;
}

发现把next,prev屏蔽掉就可以释放了。

原因是构成了循环引用
刚开始都是只有一个指向,引用计数都是1

命运在此刻形成了闭环

解决:weak_ptr 可以访问,但不参与资源的管理

可以看到weak_ptr构造函数只有无参构造、拷贝构造、还有shared_ptr拷贝构造。并没有指针构造!也就是说不支持RAII并且并不会把资源交给它管理。并且赋值也有shared_ptr的赋值

#include <iostream>
#include <memory>using namespace std;struct ListNode {
int val;
weak_ptr<ListNode> _next;
weak_ptr<ListNode> _prev;ListNode(int x) : val(x), _next(nullptr) {cout << "ListNode(" << val << ") created" << endl;
}~ListNode() {cout << "ListNode(" << val << ") destroyed" << endl;
}
};int main() {{// 创建两个节点,n1 和 n2shared_ptr<ListNode> n1 = make_shared<ListNode>(1);shared_ptr<ListNode> n2 = make_shared<ListNode>(2);// 创建循环引用n1->_next = n2;n2->_prev = n1;// 打印节点信息cout << "n1's next node value: " << n1->_next->val << endl;if (auto prev = n2->_prev.lock()) {  // 使用 weak_ptr::lock() 获取 shared_ptrcout << "n2's prev node value: " << prev->val << endl;}} // 离开作用域,n1 和 n2 应该会被销毁cout << "Exiting main function..." << endl;return 0;
}

  • 我们创建了两个节点 n1n2,并且设置了 _next_prev 的链接。
  • 通过使用 weak_ptr::lock() 来安全地获取 shared_ptr,避免直接使用 weak_ptr 导致的问题。
  • 在退出 main 函数前,n1n2 会被销毁,因为没有引用计数,所以内存都可以正常释放。

解决原理:

使 _next 和 _prev 不参与计数了

模拟实现:
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;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}private:T* _ptr;
};

weak_ptr 支持了 shared_ptr

注意sharde_ptr的get要加一个cosnt不然会报错,const对象不能调用非const的普通对象的。


四.思考

1. 智能指针的线程安全性

智能指针中的引用计数并非线程安全的。在多线程环境中,多个线程同时对引用计数进行操作,可能会导致计数不准确。为了解决这个问题,C++11中的 shared_ptr 实现是线程安全的,它通过原子操作确保引用计数的正确性。用户在访问被智能指针管理的对象时,需要自行加锁。之后的文章会再详细解释。

2. 循环引用问题

shared_ptr 的一个常见问题是循环引用。例如,两个对象相互持有对方的 shared_ptr,将导致它们的引用计数永远无法变为0,从而无法释放资源。此时,使用 weak_ptr 可以打破循环引用

总结

智能指针通过 RAII 思想有效地管理了资源,极大减少了内存泄漏的风险。在 C++11 中,unique_ptrshared_ptrweak_ptr 等不同类型的智能指针提供了丰富的功能,以应对不同的场景需求。理解并合理使用智能指针,是现代 C++ 编程的基本功之一。

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

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

相关文章

原生 iOS 引入 Flutter 报错 kernel_blob.bin 找不到

情况 在一次原生 iOS 项目中引入 Flutter 的过程中&#xff0c;在模拟器中运行出现报错&#xff1a; 未能打开文件“kernel_blob.bin”&#xff0c;因为它不存在。 如下图&#xff1a; 模拟器中一片黑 原因&解决方案 这个是因为 Flutter 的打包 iOS framework 命令中…

ES之三:springboot集成ES

一.选择版本很重要&#xff0c;不然会找不到好多方法 明明有Timeout方法&#xff0c;不报红&#xff0c;运行时&#xff0c;报错&#xff0c;找不到该类 ClassNotFoundException 为了避免使用的Elasticsearch版本和SpringBoot采用的版本不一致导致的问题&#xff0c;尽量使用…

高校大模型实验室大模型应用平台

大模型应用平台是一款专为高校大模型应用场景教学和科研打造的知识库问答系统。该平台易于使用&#xff0c;知识库支持常见的txt、doc、pdf、md等数据文件上传&#xff0c;同时提供了简洁易懂的操作配置界面&#xff0c;使用户可以轻松地搭建和训练AI应用&#xff0c;并快速进行…

arm64高速缓存基础知识

高速缓存的替换策略 随机法&#xff1a;随机地确定替换的高速缓存行&#xff0c;由一个随机数产生器产生随机数来确认替换行 FIFO法&#xff1a;选择最先调入的高速缓存行进行替换 LRU法&#xff1a;最少使用的行优先替换。 高速缓存的共享属性 内部共享的高速缓存通常指的…

Flutter基本组件Text使用

Text是一个文本显示控件&#xff0c;用于在应用程序界面中显示单行或多行文本内容。 Text简单Demo import package:flutter/material.dart;class MyTextDemo extends StatelessWidget {const MyTextDemo({super.key});overrideWidget build(BuildContext context) {return Sca…

飞速了解Conda的作用和安装使用教程

当我们想要在github上克隆不同的项目下来运行时&#xff0c;会发现项目的语言环境或包的版本不同&#xff0c;出现版本冲突问题会导致程序无法运行、兼容性问题频出。我们常常需要管理多个项目&#xff0c;每个项目可能依赖于不同的包版本或编程语言环境。如果不加以管理&#…

agentuniverse快速开始和踩坑

https://github.com/alipay/agentUniverse/tree/mastergithub地址:https://github.com/alipay/agentUniverse/tree/master 老大看了演示demo也想跟着做个agent工具,但踩坑太多,含泪写下博客 前置环节 git clone https://github.com/alipay/agentUniverse.git conda create -n…

AndroidStudio清除重置Http Proxy代理的方式

问题背景 在国内做代码开发的都知道&#xff0c;在国际互联网我们存在看不见的墙&#xff0c;导致无法访问一些代码库和资源&#xff0c;所以在使用开发工具拉取第三方库的时候总会遇到无法连接或者连接超时的情况&#xff0c;所以就会使用一些安全的网络代理工具&#xff0c;辅…

【JavaSE】Java基本数据类型缓存池

new Integer(18) 、 Integer.valueOf(18) 、Integer.valueOf(300) 的区别 new Integer(18) &#xff1a;每次都会创建一个新对象Integer.valueOf(x)&#xff1a; x in [-128, 127]&#xff1a;使用缓存池中的对象x not in [-128, 127]&#xff1a;创建新对象 Integer缓存池大…

【Qt】事件分发器

事件分发器 概述 在 Qt 中&#xff0c;事件分发器(Event Dispatcher) 是⼀个核⼼概念&#xff0c;⽤于处理 GUI 应⽤程序中的事件。事件分发器负责将事件从⼀个对象传递到另⼀个对象&#xff0c;直到事件被处理或被取消。每个继承⾃ QObject类 或 QObject类 本⾝都可以在本类中…

《纳瓦尔宝典》的核心思想在于阐述如何通过智慧和策略实现财富自由和生活幸福

《纳瓦尔宝典》概况 图书概况 《纳瓦尔宝典》是2022年5月10日由中信出版社出版的一本书籍&#xff0c;作者是美国作家埃里克乔根森。该书通过收集和整理硅谷知名天使投资人纳瓦尔拉维坎特在推特、博客和播客等平台上的智慧箴言&#xff0c;形成了一本关于财富积累和幸福人生的…

如何在红米手机中恢复已删除的照片?(6 种方式可供选择)

凭借出色的相机和实惠的价格&#xff0c;小米红米系列已成为全球知名品牌。但是&#xff0c;最近有些人抱怨他们在 红米设备上丢失了许多珍贵的图片或视频&#xff0c;并希望弄清楚如何从小米手机恢复已删除的照片。好吧&#xff0c;在小米设备上恢复已删除的视频/照片并不难。…

AI预测福彩3D采取888=3策略+和值012路或胆码测试9月8日新模型预测第81弹

经过80期的测试&#xff0c;当然有很多彩友也一直在观察我每天发的预测结果&#xff0c;得到了一个非常有价值的信息&#xff0c;那就是9码定位的命中率非常高&#xff0c;70多期一共只错了8次&#xff0c;这给喜欢打私房菜的朋友提供了极高价值的预测结果~当然了&#xff0c;大…

牛客周赛 Round 59(下)

逆序数 题目描述 登录—专业IT笔试面试备考平台_牛客网 运行代码 #include<bits/stdc.h> using namespace std; typedef long long ll; int main() {ll n,k;cin>>n>>k;ll sum(n*(n-1))/2;cout<<sum-k<<endl;return 0; } 代码思路 组合数的计…

手机到了外地ip地址就变了吗

手机到了外地IP地址就变了吗&#xff1f;随着智能手机的普及&#xff0c;人们越来越频繁地使用手机进行各种网络活动。然而&#xff0c;关于手机IP地址是否会随着地理位置的变化而改变&#xff0c;许多用户仍心存疑惑。本文将深入探讨这一问题&#xff0c;揭示IP地址变化的奥秘…

【C/C++】“秒懂”学C/C++不可错过的“经典编程题” — 日期类的经典运用 (含题链接)

“秒懂”学C/C不可错过的“经典编程题” — 日期类的经典运用 (含题链接&#xff09; 1. 计算日期到天数转换(1). 解题思路&#xff1a;(2). 代码实现&#xff1a; 2. 打印日期(1). 解题思路&#xff1a;(2). 代码实现&#xff1a; 3. 日期累加(1). 解题思路&#xff1a;(2). 代…

顶点照明渲染路径

1. 顶点照明渲染路径处理光照的方式 基本思想就是所有的光都按照逐顶点的方式进行计算的&#xff0c;在内置渲染管线中&#xff0c;它只会最多记录8个光源的数据&#xff0c;只会将光相关的数据填充到那些逐顶点相关的内置光源变量 顶点照明渲染路径仅仅是前向渲染路径的一个…

【数据结构】希尔排序(缩小增量排序)

目录 一、基本思想 1.1 引入希尔排序的原因 1.2 基本思想 二、思路分析 三、gap分组问题 四、代码实现 4.1 代码一&#xff08;升序&#xff09; 4.2 代码二&#xff08;升序&#xff09; 五、易错提醒 六、时间复杂度分析 七、排序小tips 一、基本思想 1.1 引入希尔…

【即时通讯】轮询方式实现

技术栈 LayUI、jQuery实现前端效果。django4.2、django-ninja实现后端接口。 代码仓 - 后端 代码仓 - 前端 实现功能 首次访问页面并发送消息时需要设置昵称发送内容为空时要提示用户不能发送空消息前端定时获取消息&#xff0c;然后展示在页面上。 效果展示 首次发送需要…

【老课推荐】基于LangChain和知识图谱的大模型医疗问答机器人项目

在当今数据驱动和人工智能主导的时代&#xff0c;大模型和知识图谱的结合是一个重要的研究和应用方向。大模型实战课程通过48课时&#xff0c;分为六个主要章节&#xff0c;涵盖了从基本概念到高级应用的多方面内容。学员将通过本课程学习如何使用LangChain和OpenAI进行开发&am…