c++面试三 -- 智能指针--7000字

一、智能指针

        C++ 中的智能指针是一种用于管理动态分配的内存的对象,它们可以自动进行内存管理,避免内存泄漏和悬挂指针等问题。

1. 悬挂指针

        悬挂指针(dangling pointer)是指在程序中仍然存在但已经不再指向有效内存地址的指针。悬挂指针通常是由于以下情况引起的:

        1. 释放内存后未将指针置空:

        当使用 deletefree 等方法释放了指针指向的内存后,如果未将指针置空(即将指针设置为 nullptrNULL),则该指针仍然保留之前的内存地址,成为悬挂指针。

int* ptr = new int(42);
delete ptr;
// ptr现在成为悬挂指针,它指向的内存已经被释放
        2. 超出作用域的引用:

        当指针指向的对象超出了其作用域(例如指向了一个局部变量),并且该对象的内存被释放,那么指针就会成为悬挂指针。

int* danglingPtr;
{int value = 42;danglingPtr = &value;
} // value的作用域结束,danglingPtr成为悬挂指针

        3. 指向已经被销毁的对象:

        如果指针指向的对象在其生命周期内被销毁了,那么指针就成为悬挂指针。

int* danglingPtr;
{std::unique_ptr<int> ptr = std::make_unique<int>(42);danglingPtr = ptr.get();
} // ptr超出作用域,其指向的对象被销毁,danglingPtr成为悬挂指针

2.野指针

        野指针是指指针指向的内存地址是随机的、未初始化的,或者指向的内存区域未经过分配。野指针可能是在创建指针后没有给它赋值(即未初始化指针),或者是在释放内存后未将指针置空,但继续使用该指针。

        野指针指向的内存地址通常是随机的,因此访问野指针可能会导致程序崩溃、数据损坏或安全漏洞。

        悬挂指针是指针仍然保留着之前指向的地址,但该地址已经不再有效;而野指针是指针指向的地址是随机的或未经过初始化。在编程中,应该尽量避免出现悬挂指针和野指针。

3.智能指针

C++现代实用教程:智能指针_哔哩哔哩_bilibili

 

3.1 unique_ptr指针 
        3.1.1 特点:

 3.1.2 创建

栈上:出作用域之后会自动调用析构函数

//stack
Cat c1("OK");
c1.cat_info(); 
{Cat c1("OK");c1.cat_info();
}

堆上:需要手动delete释放(不安全)

Cat *c_p1 = new Cat("yy");
int *i_p1 = new int(100);
c_p1->cat_info();
{int *i_p1 =  new int(200); // 重新声明,但是是在局部作用域中,与外部不同,结果为100。需要delete两次Cat *c_p1 new Cat("yy_scope");c_p1->cat_info();delete c_p1; delete i_pi;
}delete c_p1; delete i_pi;
Cat *c_p1 = new Cat("yy");
int *i_p1 = new int(100);
c_p1->cat_info();
{i_p1 =  new int(200); // 不重新声明,只是修改了值,结果为200  ,只用delete一次Cat *c_p1 = new Cat("yy_scope");c_p1->cat_info();delete c_p1; delete i_pi;
}delete c_p1; 

unique_ptr创建的三种方式:

1. 通过原始指针创建

Cat *c_p2 =new Cat("yz");
std::unique_ptr<Cat> u_c_p2(c_p2};// 此时原始指针还能用,需要进行销毁,否则不满足独占指针要求,否则如下
//c_p2->cat_info();
//u_c_p2->cat_info();
//c_p2->set_cat_name("ok");
//u_c_p2->cat_info();// 销毁
c_p2 = nullptr;
c_p2 =nullptr;
u_c_p2->cat_info();

2. 使用new创建

std::unique_ptr<Cat> u_c_p3{new Cat("dd")};
u_c_p3->cat_info();
u_c_p3->set_cat_name("oo");
u_c_p3->cat_info();

 3.使用std::make_unique

std::unique_ptr<Cat> u_c_p4 =make_unique<Cat>();
u_c_p4->cat_info();
u_c_p4->set_cat_name("po");
u_c_p4->cat_info();

4. 移动语义创建

可以通过使用移动语义将一个已有的 std::unique_ptr 赋值给另一个 std::unique_ptr

#include <memory>std::unique_ptr<int> u_c_p5 = std::move(u_c_p4 );
3.1.3  get()和常量类型
std::unique_ptr<int> u_i_p4 =make_unique<int>(200);
cout << "int address" << u_i_p4 .get() << endl; //get 获取原始指针或者地址
cout<< * u_i_p4<<endl;  // 打印值
3.2 unique_ptr和函数调用    (资源的所属权问题)

3.2.1 Passing by value  通过值传递
void do_with_cat_pass_value(std::unique_ptr<Cat> c){c->cat_info();
}int main(){std::unique_ptr<Cat> c1 = make_unique<Cat>("ff");// 1.  使用 std::move 转移资源的所有权给函数,此时c1不再拥有资源的所有权了do_with_cat_pass_value(std::move(c1));// 2. 直接将参数传入make_unique ,将自动转换成movedo_with_cat_pass_value(std::make_unique<Cat>());  // move}
3.2.2 Passing by reference  通过引用传递,可以修改值
void do_with_cat_pass_ref(std::unique_ptr<Cat> &c){c->set_cat_name("oo");c->cat_info();c.reset();   // 释放先前所拥有的对象,不再指向任何对象
}int main(){std::unique_ptr<Cat> c2 = make_unique<Cat>("ff");// 不用使用move,直接c2do_with_cat_pass_value(c2);}
void do_with_cat_pass_ref(const std::unique_ptr<Cat> &c){c->set_cat_name("oo");c->cat_info();// c.reset();   // 释放先前所拥有的对象,不再指向任何对象   不能使用了
}int main(){std::unique_ptr<Cat> c2 = make_unique<Cat>("ff");// 不用使用move,直接c2do_with_cat_pass_value(c2);c2 ->cat->info();}

3.3.3  Return by Value
std::unique_ptr<Cat> get_unique_ptr(){std::unique_ptr<Cat> p_dog = std:: make_unique<Cat>("Local cat");cout <<p_dog.get()<<endl;cout <<&p_dog <<endl;return p_dog;
}
// 链式get_unique_ptr->cat_info();
  1. p_dog.get(): 这个表达式返回指向 std::unique_ptr 管理的对象的原始指针。get() 函数是 std::unique_ptr 类的成员函数,它返回一个指向被管理对象的原始指针。使用 get() 可以获取 std::unique_ptr 所拥有的对象的原始指针,但是请注意,这个原始指针不包含所有权信息,因此需要谨慎使用,特别是不要手动释放内存。

  2. &p_dog: 这个表达式返回的是指向 std::unique_ptr 对象本身的指针,即指向 std::unique_ptr 对象的地址。& 是取地址运算符,它返回变量的地址。std::unique_ptr 是一个对象,因此 &p_dog 返回的是指向 std::unique_ptr 对象的指针

3.3 shared_ptr 计数指针/共享指针

        在实际的 C++ 开发中,我们经常会遇到诸如程序运行中突然崩溃、程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的。比如:

        有些内存资源已经被释放,但指向它的指针并没有改变指向(成为了野指针),并且后续还在使用;
        有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行崩溃);
        没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多。
        智能指针shared_ptr 是存储动态创建对象的指针,其主要功能是管理动态创建对象的销毁,从而帮助彻底消除内存泄漏和悬空指针的问题。

shared_ptr的原理和特点
        基本原理:就是记录对象被引用的次数,当引用次数为 0 的时候,也就是最后一个指向该对象的共享指针析构的时候,共享指针的析构函数就把指向的内存区域释放掉。

        特点:它所指向的资源具有共享性,即多个shared_ptr可以指向同一份资源,并在内部使用引用计数机制来实现这一点。

        共享指针内存:每个 shared_ptr 对象在内部指向两个内存位置:

        指向对象的指针;
用于控制引用计数数据的指针。
        1.当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。

        2.当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。

        shared_ptr像普通指针一样使用,可以将*和->与 shared_ptr 对象一起使用,也可以像其他 shared_ptr 对象一样进行比较;

3.3.1 常量类型
int main(){std::shared_ptr<int> i_p_1 =make_shared<int>(10);// std::shared_ptr<int> i_p_2 =make_shared<int>{new int(10)};// copystd::shared_ptr<int> i_p_2 =i_p_1;cout<< "use cout :" << *i_p_1.use_cout() <<endl;  // 1cout<< "use cout :" << *i_p_2.use_cout() <<endl;  // 1// change   两个指针指向同一个内存*i_p_2 =30;cout<< ":" << *i_p_1 <<endl;  // 30cout<< ":" << *i_p_2 <<endl;  // 30// 将i_p_2置为nullptri_p_2=nullptrcout<< "use cout :" << *i_p_1.use_cout() <<endl;  // 1cout<< "use cout :" << *i_p_2.use_cout() <<endl;  // 0// 将i_p_1置为nullptrstd::shared_ptr<int> i_p_3 =i_p_1;i_p_1=nullptrcout<< "use cout :" << *i_p_1.use_cout() <<endl;  // 0cout<< "use cout :" << *i_p_2.use_cout() <<endl;  // 2cout<< "use cout :" << *i_p_3.use_cout() <<endl;  // 2cout<< "value:" << *i_p_1 <<endl;cout<< "use cout :" << *i_p_1.use_cout() <<endl;return 0;
}
3.3.2 自定义类型
// 自定义类型
std::shared_ptr<Cat> c_p_1 =make_shared<Cat>();cout<< "c_p_1 use cout :" << c_p_1.use_count() << endl;std::shared_ptr<Cat> c_p_2 =c_p_1;
std::shared_ptr<Cat> c_p_3 =c_p_1;cout<< "c_p_1 use cout :" << c_p_1.use_count() << endl;
cout<< "c_p_2 use cout :" << c_p_2 .use_count() << endl;
cout<< "c_p_3 use cout :" << c_p_3 .use_count() << endl;
3.3.3 make_shared的构建方法

      (1).构造函数创建

1.shared_ptr<T> ptr;//ptr 的意义就相当于一个 NULL 指针
2.shared_ptr<T> ptr(new T());//从new操作符的返回值构造
3.shared_ptr<T> ptr2(ptr1);    // 使用拷贝构造函数的方法,会让引用计数加 1//shared_ptr 可以当作函数的参数传递,或者当作函数的返回值返回,这个时候其实也相当于使用拷贝构造函数。
4./*假设B是A的子类*/
shared_ptr<B> ptrb(new B());
shared_ptr<A> ptra( dynamic_pointer_cast<A>(ptrb) );//从 shared_ptr 提供的类型转换 (cast) 函数的返回值构造
5./* shared_ptr 的“赋值”*/
shared_ptr<T> a(new T());
shared_ptr<T> b(new T());
a = b;  // 此后 a 原先所指的对象会被销毁,b 所指的对象引用计数加 1//shared_ptr 也可以直接赋值,但是必须是赋给相同类型的 shared_ptr 对象,而不能是普通的 C 指针或 new 运算符的返回值。//当共享指针 a 被赋值成 b 的时候,如果 a 原来是 NULL, 那么直接让 a 等于 b 并且让它们指向的东西的引用计数加 1;// 如果 a 原来也指向某些东西的时候,如果 a 被赋值成 b, 那么原来 a 指向的东西的引用计数被减 1, 而新指向的对象的引用计数加 1。
6./*已定义的共享指针指向新的new对象————reset()*/
shared_ptr<T> ptr(new T());
ptr.reset(new T()); // 原来所指的对象会被销毁

        (2)make_shared辅助函数创建

std::shared_ptr<int> foo = std::make_shared<int> (10);
3.3.4 shared_ptr 与函数

void cat_by_value( std::shared_ptr<Cat> cat){cout << "cat use coout "<< cat.use_cout() << endl; // 2 
}void cat_by_ref( std::shared_ptr<Cat> &cat){// cat.reset(new Cat());  // 先创建新对象,将原先cat对象内容覆盖,然后resetcout << "cat use coout "<< cat.use_cout() << endl; // 2 
}std::shared_ptr<Cat> get_shared_ptr(){std::shared_ptr<Cat> cat_p= std::make_shared<Cat>("dd);
}int main(){std::shared_ptr<Cat> c1 =make_shared<Cat>("dd");cat_by_value(c1);cout << "c1 use coout "<< c1.use_cout() << endl; // 1cat_by_ref(c1);std::shared_ptr<Cat> c_p =get_shared_ptr();get_shared_ptr->cat_info();  
}
3.4 shared_ptr 和 unique_ptr转换

3.5 weak_ptr  弱引用指针

  std::weak_ptr 是 C++11 引入的一个智能指针类,用于解决 std::shared_ptr 的循环引用问题。它是一个弱引用指针,不会增加指向对象的引用计数,也不会拥有对象的所有权,因此不会影响对象的生命周期。

std::weak_ptr 主要用于解决以下两个问题:

  1. 循环引用问题:当两个或多个对象相互持有对方的 std::shared_ptr,就会形成循环引用,导致对象无法被正确释放,从而产生内存泄漏。使用 std::weak_ptr 可以打破循环引用,避免内存泄漏的发生。

  2. 避免悬挂指针:当对象的 std::shared_ptr 被释放后,指向该对象的 std::weak_ptr 仍然可以继续存在,但是无法访问对象。因此,使用 std::weak_ptr 可以避免悬挂指针的出现,从而提高程序的稳定性。

        使用 std::weak_ptr 需要配合 std::shared_ptr 使用,通过 std::shared_ptr 对象的 weak_ptr 方法来创建 std::weak_ptr 对象。std::weak_ptr 可以通过 lock 方法获取一个有效的 std::shared_ptr 对象,用于访问所指向的对象,但是需要注意,获取的 std::shared_ptr 可能为空,需要进行有效性检查。

// 产生循环依赖问题 

 

// 使用weak_ptr 

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

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

相关文章

IO多路复用 poll模型

poll 是一种在 Linux 系统中进行 I/O 多路复用的模型&#xff0c;它与 select 类似&#xff0c;但具有一些不同之处。poll 允许监视的文件描述符数量不受限制&#xff0c;而不像 select 有一定的限制。 基本概念&#xff1a; poll 函数&#xff1a; 通过 poll 函数&#xff0c…

队列的结构概念和实现

文章目录 一、队列的结构和概念二、队列的实现三、队列的实现函数四、队列的思维导图 一、队列的结构和概念 什么是队列&#xff1f; 队列就是只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出 如上图所示&#x…

【比较mybatis、lazy、sqltoy、mybatis-flex操作数据】操作批量新增、分页查询(二)

orm框架使用性能比较 环境&#xff1a; idea jdk17 spring boot 3.0.7 mysql 8.0比较mybatis、lazy、sqltoy、mybatis-flex操作数据 测试条件常规对象 orm 框架是否支持xml是否支持 Lambda对比版本mybatis☑️☑️3.5.4sqltoy☑️☑️5.2.98lazy✖️☑️1.2.4-JDK17-SNAPS…

Vue+SpringBoot打造天然气工程运维系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程 12.2.2 流程 22.3 各角色功能2.3.1 系统管理员功能2.3.2 用户服务部功能2.3.3 分公司&#xff08;施工单位&#xff09;功能2.3.3.1 技术员角色功能2.3.3.2 材料员角色功能 2.3.4 安…

Zabbix企业运维监控工具

Zabbix企业级监控方案 常见监控软件介绍 Cacti Cacti是一套基于 PHP、MySQL、SNMP 及 RRD Tool 开发的监测图形分析工具&#xff0c;Cacti 是使用轮询的方式由主服务器向设备发送数据请求来获取设备上状态数据信息的,如果设备不断增多,这个轮询的过程就非常的耗时&#xff0…

sql注入less46作业三

采用报错注入 updatexml(XML_document,XPath_string,new_value) 一共可以接收三个参数&#xff0c;报错位置在第二个参数。 ?sort1 and updatexml(1,concat(0x7e,database(),0x7e),1)-- #查询库名 ?sort1 and updatexml(1,concat(0x7e,(select group_concat(table_name) fr…

[每周一更]-(第89期):开源许可证介绍

开源代码本就是一种共享精神&#xff0c;一种大无畏行为&#xff0c;为了发扬代码的魅力&#xff0c;创造更多的价值&#xff0c;让爱传递四方&#xff0c;让知识惠及更多人&#xff1b; 写文章也是一种共享精神&#xff0c;让知识传播出去。 介绍下开源中不同许可证的内容限…

初学Vue总结

0 Vue概述 问题&#xff1a;我们已经学过了htmlCssjavascript,可以开发前端页面了&#xff0c;但会发现&#xff0c;效率太低了。那么&#xff0c;有没有什么工具可以提高我们的开发效率&#xff0c;加快开发速度呢&#xff1f; 他来了&#xff0c;他来了&#xff0c;他大佬似…

Spring注解之json 数据处理

目录 1. 过滤 json 数据 2. 格式化 json 数据 3. 扁平化对象 1. 过滤 json 数据 JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析。 //生成json时将userRoles属性过滤 JsonIgnoreProperties({"userRoles"}) public class User { ​private S…

线性规划在多种问题形式下的应用

线性规划的用处非常的广泛&#xff0c;这主要是因为很多类型的问题是可以通过转化的方式转化为线性规划的问题。例如需要再图论中寻找起始点到给定的点的最短路径问题&#xff1a; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 假设要计算从节点0到节点…

springboot配置多数据源以及事务问题

一、背景以及为什么需要学习 在高并发的项目中,单数据库已无法承载大数据量的访问,因此需要使用多个数据库进行对数据的读写分离,此外就是在微服化的今天,我们在项目中可能采用各种不同存储,因此也需要连接不同的数据库,居于这样的背景,这里简单分享实现的思路以及实现…

点亮城市名片丨计讯物联智慧灯杆系统在通讯基地的成功应用

项目背景 在国家新型城镇化大背景下&#xff0c;十四五规划纲要强调“加快数字化发展&#xff0c;建设数字中国”&#xff0c;明确提出“以数字化助推城乡发展和治理模式创新”&#xff0c;全面提高城市的运行效率和宜居程度。 项目概况 为满足灯杆灯光亮度的远程智能管理、对…

记录 android studio 通过安装NDK 编译C文件,得到需要的so文件

只怪自己太健忘&#xff0c;每次网上查了一圈&#xff0c;搞定后&#xff0c;再遇到又发现不会操作了&#xff0c;特此记下 不废话直接上步骤 &#xff08;1&#xff09; 进入AS的settinging如下界面 &#xff08;2&#xff09;选中图片箭头两个文件 进行下载 &#xff08;…

web学习笔记(二十一)

目录 1.构造函数创建对象 1.1规则 1.2 new关键字调用构造函数时&#xff0c;函数内部做了什么事情&#xff1f; 1.3总结 2.混合模式创建对象 3.JavaScript 继承---借助构造函数 4.原型链 4.1原型链实现方法继承 5.完美的组合继承 6.call方法的使用 1.构造函数创建对象…

React之数据绑定以及表单处理

一、表单元素 像<input>、<textarea>、<option>这样的表单元素不同于其他元素&#xff0c;因为他们可以通过用户交互发生变化。这些元素提供的界面使响应用户交互的表单数据处理更加容易 交互属性&#xff0c;用户对一下元素交互时通过onChange回调函数来监听…

回溯例题(leetcode17/37)

文章目录 leetcode37leetcode17 回溯跟枚举差不多。要注意“回溯”&#xff0c;别忘记“回”之前把之前的改动都复原。 leetcode37 leetcode37是解数独问题。本题保证有且仅有唯一解。 思路&#xff1a;先把空格子的位置存下来&#xff0c;然后对每一个空位置挨个枚举1-9。枚…

Excel常用公式总结非常实用

16个最实用的Excel万能公式 1、多条件判断 IF(And(条件1,条件2..条件N),条件成立返回值) IF(or(条件1,条件2..条件N),条件成立返回值) 2、多条件查找 Lookup(1,0/((条件1*条件2*...条件N)),返回值区域&#xff09; 3、多条件求和 Sumifs(值区域,判断区域1,条件1,判断区域2,条…

2024最新精华版Java面试题之spring篇

目录 一、Java面试题之spring篇 1、什么是spring? 2、你们项目中为什么使用Spring框架&#xff1f; 3、 Autowired和Resource关键字的区别&#xff1f; 4、依赖注入的方式有几种&#xff0c;各是什么? 5、讲一下什么是Spring容器&#xff1f; 6、说说你对Spring MVC的理…

Java毕业设计-基于springboot开发的私人健身与教练预约系统-毕业论文+答辩PPT(有源代码)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1.开发说明2.需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、后台功能模块2.1管理员功能2.2用户功能2.3教练功能 四、毕设内容和源代码获取总结 [Java毕业设计-基于springboot…

简单数据类型和复杂数据类型

1. 简单数据类型 null是个特例: 2. 复杂数据类型 3. 堆和栈 注意&#xff1a; JavaScript 中是没有堆和栈的概念的&#xff0c;通过堆栈的概念可以更好的理解代码的一些执行方式&#xff0c;便于将来学习其他语言。 4. 简单数据类型传参 总结&#xff1a;简单数据类型传参传…