力扣 LCR训练计划2(剑指 Offer 22. 链表中倒数第k个节点)-140

news/2024/12/4 21:50:40/

LCR训练计划2(剑指 Offer 22. 链表中倒数第k个节点)-140 

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* trainingPlan(ListNode* head, int cnt) {ListNode* cur = NULL;ListNode* pre = head;while(pre!=NULL){ListNode* t = pre->next;pre->next = cur;cur = pre;pre = t;}/*以上是反转链表操作定义ListNode指针类型的变量temp记录反转后cur的位置,因为后续操作cur的位置会发生变化,此时的cur为反转后链表的头节点,因为后续找到倒数第cnt个节点后要再进行一次反转,因为题目要找的是正常顺序链表的倒数第cnt个节点此时的反转链表只是为了方便找到倒数第cnt的节点位置*/ListNode* temp = cur;//while循环用来找到正数第cnt节点的位置,因为我们已经将链表反转了,从题意倒数变成了正数while(cnt>1){cur=cur->next;cnt--;}//循环过后,cur已经找到了倒数第cur节点的位置,定义ListNode指针类型的变量temphead记录倒数第cur节点的位置ListNode* temphead = cur;/*以下是将反转后的链表再一次反转的操作,再一次反转过后链表变为正常顺序的链表,最后根据题意返回temphead及往后的所有节点*/ListNode* cur1 = NULL;ListNode* pre1 = temp;while(pre1!=NULL){ListNode* t1 = pre1->next;pre1->next = cur1;cur1 = pre1;pre1 = t1;}return temphead;}
};

每日问题

C++循环引用指的是什么,在使用过程当中需要注意什么问题

C++中的循环引用

循环引用(Cyclic Reference)通常指的是两个或多个对象相互引用,形成一个闭环,使得这些对象之间无法被销毁或释放,导致内存泄漏或资源未被及时回收。这种情况最常见于使用指针或智能指针的场景中,特别是在涉及到对象之间相互持有指针或引用时。

循环引用的例子:

考虑一个简单的 C++ 例子,假设我们有两个类 A 和 B,它们通过指针互相引用:

class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr; // A 持有 B 的 shared_ptr
};class B {
public:std::shared_ptr<A> a_ptr; // B 持有 A 的 shared_ptr
};

在这个例子中:

类 A 有一个 shared_ptr 指向类 B 的实例。

类 B 也有一个 shared_ptr 指向类 A 的实例。

如果在程序中创建了 A 和 B 的实例,并且它们通过 shared_ptr 互相引用,当这两个对象超出作用域时,由于 shared_ptr 的引用计数机制,它们互相持有对方,导致它们的引用计数永远不为零。因此,这些对象的析构函数永远不会被调用,从而导致内存泄漏。

循环引用的具体问题

1.内存泄漏:

循环引用的最大问题就是内存泄漏。在智能指针(如 std::shared_ptr)的情况下,引用计数机制会不断增加,导致内存无法被释放。

2.资源无法释放:

除了内存泄漏,循环引用还可能导致其他资源(如文件句柄、数据库连接等)无法及时释放,影响程序性能和稳定性。

3.性能问题:

循环引用会影响垃圾回收或引用计数机制的正常工作,使得它们无法有效地管理资源,可能导致程序资源消耗过多或响应变慢。

避免循环引用的策略

1.弱指针(std::weak_ptr):

如果我们希望避免循环引用,可以使用 std::weak_ptr 替代 std::shared_ptr。std::weak_ptr 不会增加引用计数,也不会阻止对象被销毁。它可以用来观察一个对象,而不持有它。

例如,可以将其中一个指针改为 weak_ptr 来打破循环引用:

class A;class B {
public:std::weak_ptr<A> a_ptr;  // 使用 weak_ptr 避免循环引用
};class A {
public:std::shared_ptr<B> b_ptr;
};

在这种情况下,

B 类持有 A 类的弱引用,这样当 A 被销毁时,不会阻止 B 的析构,同时 A 依然可以安全地持有 B 的 shared_ptr。

2.手动管理资源:

如果循环引用不能避免(例如某些设计模式中确实需要双向引用),可以选择手动管理资源释放,确保在适当的时候解除相互引用。比如在对象销毁前,将智能指针置空或重新设置。

3.避免不必要的双向引用:

在设计时,尽量避免不必要的双向引用。例如,可以考虑将其中一个对象的引用设计为单向引用,而不是双向引用。这样就可以避免引起复杂的引用关系。

4.设计模式:观察者模式:

如果存在双向引用的需求,考虑使用观察者模式来解耦对象间的关系。在这种模式中,一个对象(通常是“主题”)可以有多个观察者(其他对象),而不需要在两个对象之间建立直接的循环引用关系。

5.手动释放资源:

如果确实存在循环引用,且无法使用 weak_ptr 等方法来解决,可以通过手动解除循环引用(例如,在合适的时机将指针重置为 nullptr)来避免资源泄漏。

总结

循环引用 发生在对象通过智能指针(如 shared_ptr)或裸指针互相引用,导致它们无法被销毁,造成内存泄漏。

解决方法 主要是使用 std::weak_ptr 来打破循环引用,或者通过手动解除循环引用来管理资源。

在 C++ 中,避免循环引用是一个重要的设计考虑,特别是在复杂的对象关系和资源管理场景中。

new或malloc在申请空间时,超出可申请的大小就会分配异常,可申请的大小是多少

在 C++ 中,new 或 C 中的 malloc 等内存分配函数,如果申请的内存超过系统能分配的最大内存大小,通常会出现异常或错误。这个最大可分配的内存大小取决于多个因素:

1.操作系统和架构的限制:

在 32 位系统上,通常最多能分配的内存是 4GB(不过,由于操作系统通常会把一些内存空间保留给内核,用户空间可能会少于 4GB),每个进程的最大虚拟内存空间通常在 2GB 到 3GB 之间。

在 64 位系统上,理论上支持的内存大小可以达到几 EB(Exabyte,亿亿字节),但实际上,系统能分配的内存通常受硬件(物理内存)、操作系统和其他限制的影响,通常是几 TB 或者更多。

2.可用物理内存和虚拟内存:

malloc 或 new 在分配内存时,操作系统会尝试为进程分配一个足够大的虚拟内存块。如果虚拟内存不足,或者物理内存不足以满足请求,分配会失败。

3.片化:

内存碎片化也会影响内存分配的成功与否。即使总的可用虚拟内存很大,如果内存已经被分割成许多小块,可能无法分配一个大的连续内存块。

错误和异常处理:

        C++:

                如果 new 无法分配内存,会抛出 std::bad_alloc 异常。你可以使用 try-catch 块捕获这个异常。

try {int* arr = new int[10000000000]; // 试图申请一个非常大的数组
} catch (const std::bad_alloc& e) {std::cerr << "内存分配失败: " << e.what() << std::endl;
}
        C:

                如果 malloc 或 calloc 无法分配内存,它们会返回 NULL,并且通常会设置 errno 为 ENOMEM。

实际可申请的大小:

        这个大小通常依赖于:

                系统的总物理内存和虚拟内存

                系统的内存分配策略

                操作系统的配置(例如,最大进程内存限制)

                程序本身的内存使用情况

在大多数现代操作系统中,单个进程的虚拟地址空间在 64 位系统上非常大,但要注意物理内存的限制和操作系统的配置可能会导致超出物理内存时出现分配失败或严重的性能下降。

总结:

对于 new 和 malloc,具体能分配多少内存受到操作系统、硬件架构、物理内存、虚拟内存空间以及内存碎片等多方面因素的限制。

在 64 位操作系统上,通常可分配的内存大小非常大,然而,最终是否能成功分配内存也取决于具体的系统和当前的内存使用状况。


http://www.ppmy.cn/news/1552384.html

相关文章

JavaScript实现tab栏切换

JavaScript实现tab栏切换 代码功能概述 这段代码实现了一个简单的选项卡&#xff08;Tab&#xff09;切换功能。它通过操作 HTML 元素的类名&#xff08;class&#xff09;来控制哪些选项卡&#xff08;Tab&#xff09;和对应的内容板块显示&#xff0c;哪些隐藏。基本思路是先…

解决`-bash: ./configure:/bin/sh^M:解释器错误: 没有那个文件或目录`的问题

解决`-bash: ./configure:/bin/sh^M:解释器错误: 没有那个文件或目录`的问题 一、错误原因分析二、解决方法方法一:使用`dos2unix`工具方法二:使用`sed`命令方法三:使用`tr`命令方法四:在文本编辑器中转换方法五:在Windows系统中使用适当的工具三、预防措施四、总结在使…

深度学习基础03_BP算法(下)过拟合和欠拟合

目录 一、BP算法(下) 0、反向传播代码回顾 写法一&#xff1a; 写法二(更常用)&#xff1a; 1、BP中的梯度下降 1.数学描述 2.传统下降方式 3.优化梯度下降方式 指数加权平均 Momentum AdaGrad RMSProp Adam(常用) 总结 二、过拟合和欠拟合 1、概念 1.过拟合 …

第 44 章 - Go语言 团队协作

在第44章中&#xff0c;我们将探讨团队协作的关键方面&#xff0c;包括版本控制系统、代码仓库管理和团队沟通与协作。为了具体化这些概念&#xff0c;我们将结合实际案例&#xff0c;并使用Go语言作为示例语言来演示如何有效地进行团队开发。 1. 版本控制系统 (Version Contr…

Go快速入门

一、环境安装 1、源码包下载 https://golang.org/dl/ https://golang.google.cn/dl/ https://studygolang.com/dl/ 2、下载解压至/usr/local tar -zxvf go1.14.4.linux-amd64.tar.gz -c /usr/local 3、cd /usr/local/go src 源码 bin go指令 gofmt指令 4、配置bin到环境…

【机器学习】支持向量机SVR、SVC分析简明教程

关于使用SVM进行回归分析的介绍很少&#xff0c;在这里&#xff0c;我们讨论一下SVR的理论知识&#xff0c;并对该方法有一个简明的理解。 1. SVC简单介绍 SVR全称是support vector regression&#xff0c;是SVM&#xff08;支持向量机support vector machine&#xff09;对回…

计算机毕业设计Spark+SpringBoot旅游推荐系统 旅游景点推荐 旅游可视化 旅游爬虫 景区客流量预测 旅游大数据 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

基于FPGA的FM调制(载波频率、频偏、峰值、DAC输出)-带仿真文件-上板验证正确

基于FPGA的FM调制-带仿真文件-上板验证正确 前言一、FM调制储备知识载波频率频偏峰值个人理解 二、代码分析1.模块分析2.波形分析 总结 前言 FM、AM等调制是学习FPGA信号处理一个比较好的小项目&#xff0c;通过学习FM调制过程熟悉信号处理的一个简单流程&#xff0c;进而熟悉…