【C++详解】——智能指针
目录
为什么需要智能指针
抛异常引发内存泄漏
内存泄漏
什么是内存泄漏,内存泄漏的危害
内存泄漏分类
检测内存泄漏常用工具
如何避免内存泄漏
智能指针的使用及原理
RAII
智能指针的原理
各类智能指针介绍
auto_ptr
unique_ptr
weak_ptr
定制删除器
为什么需要智能指针
抛异常引发内存泄漏
对于下面这段代码,存在什么问题?
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;
}
分析上述代码,如果在p1处抛异常,代码直接跳转到main函数中捕获异常,不会造成资源泄漏。但是如果在p2被new时,或者调用div()函数时抛异常,代码跳转到main()函数,两个delete语句会被跳过,导致内存泄漏。
内存泄漏
什么是内存泄漏,内存泄漏的危害
内存泄漏
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
常见的内存泄漏
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;
}
内存泄漏分类
C/C++程序中一般我们关心两种方面的内存泄漏:
- 堆内存泄漏(Heap leak):堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
- 系统资源泄漏:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
检测内存泄漏常用工具
以下几种工具可以用来帮助检测是否存在内存泄漏
- 在Linux下内存泄漏检测:Memory Debuggers - eLinux.org
- 在windows下使用第三方工具检测:VLD(Visual LeakDetector)内存泄露库
- 其他检测工具比较:内存泄露检测工具比较 - 默默淡然 - 博客园 (cnblogs.com)
如何避免内存泄漏
- 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
- 采用RAII思想或者智能指针来管理资源(后面将要介绍的智能指针即采用RAII思想)
- 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项
- 出问题了使用内存泄漏工具检测
【总结】
内存泄漏非常常见,解决方案分为两种:1、事前预防型:如智能指针等。 2、事后查错型:“如泄漏检测工具。
智能指针的使用及原理
RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内
存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}
private:T* _ptr;
};
智能指针的原理
上述的SmartPtr还不能将其称为智能指针,上述模拟实习的智能指针只用到了RAII的思想,即构造对象时获取资源、析构对象时回收资源,但是它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。
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;
};
总结一下智能指针的原理:
- RAII特性
- 重载operator*和opertaor->,具有像指针一样的行为
各类智能指针介绍
auto_ptr
早在C++98中就已经出现了智能指针的概念,同时库中也提供了auto_ptr的智能指针。当时采用的是管理权转移的思想,下面简化模拟实现了一份auto_ptr来了解它的原理。
template<class T>
class auto_ptr
{
public://RAII//保存资源auto_ptr(T* ptr):_ptr(ptr){}//释放资源~auto_ptr(){delete _ptr;}//auto_ptr采用资源管理权转移 -- 但是会导致对象悬空auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){sp._ptr = nullptr;}//智能指针要有指针的功能T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}
private:T* _ptr;
};
auto_ptr采用了RAII思想,可以解决抛异常引发的内存泄漏问题,但同时又造成了新的问题。auto_ptr在实现拷贝构造函数时采用了管理权转移的方式。
相信大伙一眼就能看出这种实现思想有很坑的一点,用一个旧的指针去构造一个新的指针之后,只有新的指针指向了内容,旧的指针指向了空,但我们仍可能去解引用旧的指针啊。例如下面的例子一定会引发错误。
void test_auto()
{auto_ptr<int> ap1(new int(1));auto_ptr<int> ap2(ap1);*ap1 = 1; // 管理权转移以后导致ap1悬空,不能访问*ap2 = 1;
}
auto_ptr这一问题也是被骂了很长时间,C++标准委员会也是直接表明不要使用auto_ptr。
unique_ptr
unique_ptr是C++11标准库提供的一个靠谱的智能指针,unique_ptr既体现了RAII的思想,同时也没有auto_ptr的问题,因为它直接就没有拷贝构造函数。
unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份unique_ptr来了解它的原理。
template<class T>
class unique_ptr
{
public://RAII//保存资源unique_ptr(T* ptr):_ptr(ptr){}//释放资源~auto_ptr(){delete _ptr;}//智能指针要有指针的功能T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}//C++11:可以用=delete直接封死拷贝构造和赋值构造unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;private://防拷贝// 拷贝构造和赋值是默认成员函数,我们不写会自动生成,所以我们不需写// C++98思路:只声明不实现,但是用的人可能会在外面强行定义,所以再加一条,声明为私有//unique_ptr(const unique_ptr<T>& up);//unique_ptr<T>& operator=(const unique_ptr<T>& up);T* _ptr;
};
shared_ptr
C++11中除了提供了unique_ptr还提供更靠谱的并且支持拷贝的shared_ptr。
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
享。 - 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减
一。 - 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对
象就成野指针了。
实现拷贝构造时采用引用计数的方式,在实现赋值重载时,要注意等号左边的指针指向的内容是否需要被释放。
情况一:
情况二:
shared_ptr具体实现代码:
template<class T>
class shared_ptr {
public://RAII//保存资源shared_ptr(T* ptr):_ptr(ptr),_pcount(new int(1)){}//释放资源~shared_ptr(){Release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){AddCount();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (sp._ptr != _ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;AddCount();}return *this;}void Release(){if (--(*_pcount) == 0){delete _ptr;delete _pcount;}}void AddCount(){++(*_pcount);}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;
};
上述的代码已经可以满足一般的应用场景,但是设计到多线程操作时,上面的shared_ptr存在线程安全的问题,所以我们最好还是实现通过加锁实现一个线程安全的版本。
template<class T>
class shared_ptr {
public://RAII//保存资源shared_ptr(T* ptr):_ptr(ptr),_pcount(new int(1)),_pmtx(new mutex){}//释放资源~shared_ptr(){Release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount),_pmtx(sp._pmtx){AddCount();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (sp._ptr != _ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;_pmtx = sp._pmtx;AddCount();}return *this;}void Release(){_pmtx->lock();if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_pmtx->unlock();}void AddCount(){_pmtx->lock();++(*_pcount);_pmtx->unlock();}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get(){return _ptr;}int use_count(){return *_pcount;}private:T* _ptr;int* _pcount;mutex* _pmtx;
};
weak_ptr
循环引用的情况:
比如上面这个双向链表,我们一般就是这样使用,但是因为抛异常可能导致内存泄漏的问题,于是我们改用智能指针。n1和n2不构成循环时可以正常使用智能指针。
n1和n2构成循环引用时,智能指针无法释放空间,造成内存泄漏。
原因是
- node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
- node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
- node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
- 也就是说_next析构了,node2就释放了。
- 也就是说_prev析构了,node1就释放了。
- 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
weak_ptr原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和
_prev不会增加node1和node2的引用计数。
【注意】
weak_ptr不是常规的智能指针,不支持RAII ,但是它支持像指针一样
专门设计出来,辅助解决shared_ptr的循环引用问题,
weak_ptr可以指向资源,但是他不参与管理,不增加引用计数
template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp) :_ptr(sp.get()){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get(){return _ptr;}private:T* _ptr;};
shared_ptr和weak_ptr配合使用就可以解决循环引用的问题了。
定制删除器
如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这
个问题。
// 仿函数的删除器template<class T>struct FreeFunc {void operator()(T* ptr){cout << "free:" << ptr << endl;free(ptr);}};template<class T>struct DeleteArrayFunc {void operator()(T* ptr){cout << "delete[]" << ptr << endl;delete[] ptr;}};
定制删除器版本shared_ptr
template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)), _pmtx(new mutex){}template<class D>shared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new int(1)), _pmtx(new mutex), _del(del){}~shared_ptr(){Release();}void Release(){_pmtx->lock();bool deleteFlag = false;if (--(*_pcount) == 0){if (_ptr){//cout << "delete:" << _ptr << endl;//delete _ptr;// 删除器进行删除_del(_ptr);}delete _pcount;deleteFlag = true;}_pmtx->unlock();if (deleteFlag){delete _pmtx;}}void AddCount(){_pmtx->lock();++(*_pcount);_pmtx->unlock();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount), _pmtx(sp._pmtx){AddCount();}// sp1 = sp4// sp1 = sp1;// sp1 = sp2;shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;_pmtx = sp._pmtx;AddCount();}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count(){return *_pcount;}private:T* _ptr;int* _pcount;mutex* _pmtx;// 包装器function<void(T*)> _del = [](T* ptr){cout << "lambda delete:" << ptr << endl;delete ptr;};
};
运行结果:
相关文章:

【C++详解】——智能指针
目录 为什么需要智能指针 抛异常引发内存泄漏 内存泄漏 什么是内存泄漏,内存泄漏的危害 内存泄漏分类 检测内存泄漏常用工具 如何避免内存泄漏 智能指针的使用及原理 RAII 智能指针的原理 各类智能指针介绍 auto_ptr unique_ptr shared_ptr weak_ptr …...

Jmeter接口/性能测试,Jmeter使用教程(超细整理)
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、线程组 线程组…...
59,综合案例-演讲比赛流程管理系统
演讲比赛流程管理系统 59.1案例描述59.1.1比赛规则59.1.2程序功能 59.2创建管理类59.3菜单功能59.3.1添加成员函数59.3.2菜单功能实现 59.4退出功能59.4.1提供功能接口59.4.2实现退出功能 59.5演讲比赛功能59.5.1创建选手类59.5.2比赛59.5.2.1成员属性添加59.5.2.2初始化属性59…...

前端JS 展示上传图片缩略图(本地图片读取)
需求: 点击上传图片按钮,选择图片以后,不请求后端接口,直接将图片展示在缩略图中。 解决方案: 使用 FileReader 和 FileReader 中的 readAsDataURL 方法。 第一步 从input[type“file”] (上传文件标签) 里面拿到fil…...

Vue中$route和$router的区别
$router:用来操作路由,$route:用来获取路由信息 $router其实就是VueRouer的实例,对象包括了vue-router使用的实例方法,还有实例属性,我们可以理解为$router有一个设置的含义,比如设置当前的跳转…...

基于多任务学习卷积神经网络的皮肤损伤联合分割与分类
文章目录 Joint segmentation and classification of skin lesions via a multi-task learning convolutional neural network摘要本文方法实验结果 Joint segmentation and classification of skin lesions via a multi-task learning convolutional neural network 摘要 在…...

串口环形缓冲区
文章目录 一、串口环形缓冲区概念二、STC12例程(1)环形串口缓冲区结构体(2)串口环形缓冲区存和取数据(3)完整工程demo 一、串口环形缓冲区概念 串口环形缓冲区应用于嵌入式、物联网开发中处理接收串口数据…...

【腾讯云 Cloud Studio 实战训练营】基于Cloud Studio完成简易通讯录
目录 🔆Cloud Studio 简介 操作步骤 1.登录 2.创建工作空间 3.初始界面 4.开发空间 5.保存自定义模板 🔆简易通讯录 1.实验要求 2.操作环境 3.源代码介绍 3.1 定义通讯录类 3.2 定义通讯录列表 3.3 添加联系人功能 3.4 修改联系人 3.5 …...

【技术积累】Vue.js中的核心知识
Vue的生命周期 Vue中的生命周期是指组件从创建到销毁的整个过程中,会触发一系列的钩子函数 Vue2中的生命周期 Vue2中的生命周期钩子函数是在组件的不同阶段执行的特定函数。这些钩子函数允许开发者在组件的不同生命周期阶段执行自定义的逻辑。 Vue2中的生命周期钩…...
flutter开发实战-显示本地图片网络图片及缓存目录图片
flutter开发实战-显示本地图片网络图片及缓存目录图片 在最近开发中碰到了需要显示缓存目录图片,这里顺便整理一下,显示本地图片、网络图片、缓存目录图片的方法。 一、工程本地图片显示 1 在项目根目录下创建名为 images文件夹,也可以将i…...
面对未来的算法备案法规:企业需要做哪些准备?
在信息时代,算法已经成为我们生活的一部分,涵盖了诸如搜索引擎、社交媒体、电子商务、广告投放等各个方面。然而,随着算法的广泛应用,其带来的问题也日益凸显。这引发了全球范围内的关注,未来的算法备案法规正在酝酿之…...

iptables的备份和还原
iptables的备份和还原 1、写在命令行当中的都是临时设置 2、把规则配置写在服务的文件当中,形成永久有效 备份:把iptables里面所有的配置都保存在/opt/ky30.bak中 iptables-save > /opt/ky30.bak 例: 默认配置文件在/etc/sysconfig/ip…...

easyUI框架学习
文章目录 一、前言二、引入使用easyUI 三、用法3.1 Dialog(对话框窗口)3.1.1 示例13.1.2 示例2 3.2 Layout(布局)3.2.1 示例1——通过标签创建布局3.2.2 示例2—— 创建嵌套布局 3.3 DateBox(日期输入框)3.…...

加入气压计模组,星斗3号将实现快速三维定位
随着信息技术的飞速发展,人们的生活正在发生巨大改变,人们对基于位置服务的需求越来越迫切,尤其是室内位置服务。 室外定位系统中BDS系统、GPS系统等受室内复杂环境条件限制精度大幅下降甚至失效,难以在室内定位中发挥作用,而室内二维定位技术缺乏高程信息,也难以满足人们对室…...

华为HCIP第二节-------------------------ISIS
IS-IS(Intermediate System to Intermediate System,中间系统到中间系统)是ISO (International Organization for Standardization,国际标准化组织)为它的CLNP(ConnectionLessNetwork Protocol&…...

在Mac系统下搭建Selenium环境并驱动Chrome浏览器
本文带领那些使用Mac的童鞋们实现Selenium驱动Chrome浏览器,虽然会有坑,但是我们可以凭借敏捷的身手躲过。下面就开始吧: 安装selenium 打开终端 ->pip安装(安装命令:pip3 install selenium) 安装浏览…...

通过RPM方式安装,升级,卸载,以及配置使用MySQL
通过RPM方式安装,升级,卸载,以及配置使用MySQL 一、下载 MySQL是一种开源的关系数据库管理系统,被广泛应用于各种业务应用中。本文将讲解如何下载和安装MySQL的rpm安装包。 下载rmp安装包有多种方式: 1、官网下载 …...

六边形架构和分层架构的区别?
六边形架构和分层架构是什么? 六边形架构(Hexagonal Architecture)和分层架构(Layered Architecture)是两种常见的软件架构模式。六边形架构强调将核心业务逻辑与外部依赖解耦,通过接口与外部世界进行通信。…...

一封来自Java学姐的信
黑马JavaEE学科学姐想对学弟学妹们说:勤学如春起之苗,不见其增,日有所长。 辍学如磨刀之石,不见其损,日有所亏。 学科 | JavaEE 校区 | 太原 亲爱的学弟学妹们,在学校“混日子”的时间很快就过去了&…...
Mybatis增强版MyBatis-Flex简介
Mybatis增强版:Mybatis-Plus(使用的最多,老牌Mybatis增强框架,2016年开源)、Fluent-MyBatis(阿里云开发的Mybatis增强框架,来自阿里云.云效产品团队)、Mybatis-Flex。 Flex英文单词意思是灵活,Mybatis-Flex官方文档中多…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...