rust智能指针

一、智能指针是什么

指针是一个存储内存地址的变量。这个地址指向一些其他数据。
智能指针是一类数据结构,它们类似指针,但是拥有额外的功能。智能指针的概念起源于C++。Rust标准库提供了许多智能指针,比如String和Vec<T>,虽然我们并不这么称呼它们,但这些类型都属于智能指针。
智能指针通常使用结构体实现。智能指针与常规结构体的区别在于智能指针实现了Deref和Drop trait。Deref trait使智能指针表现的像引用一样,这样就可以编写既用于引用、又用于智能指针的代码。Drop trait允许我们自定义智能指针离开作用域时的行为。

在Rust中,引用和智能指针的一个区别是引用是一类只借用数据的指针;智能指针则拥有数据的所有权。

二、最常用的一些智能指针

1.Box<T>,用于在堆上分配
2.Rc<T>,一个引用计数类型,其数据可以有多个所有者
3.Ref<T>RefMut<T>,通过RefCell<T> 访问(RefCell<T>是一个在运行时而不是在编译时执行借用规则的类型)

(一)Box指针
Box<T>类型是一个智能指针,因为它实现了Deref trait和Drop trait。
box把值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。除了数据被储存在堆上而不是栈上之外,box没有性能损失。不过也没有很多额外的功能。
1.创建Box
使用new函数创建
例子

fn main() {let var_i32 = 5; // 默认数据保存在 栈 上let b = Box::new(var_i32); // 使用Box后数据会存储在堆上println!("b = {}", b);
}

b离开作用域时,它将自动释放。这个释放包括b本身(位于栈上)和它所指向的数据(位于堆上)。

2.使用box
像使用引用一样使用box。
使用解引用操作符 * 解引用box

fn main() {let x = 5; // 值类型数据let y = Box::new(x); // y是一个智能指针,指向堆上存储的数据5println!("{}",5==x);println!("{}",5==*y); // 为了访问y存储的具体数据,需要解引用
}
编译运行结果如下
true
true
直接使用  5 == y  会返回false

3.使用Box创建递归类型
Rust需要在编译时知道类型占用多少空间。一种无法在编译时知道大小的类型是递归类型,其值的一部分可以是自身类型的另一个值。这种嵌套可以是无限的,所以Rust不知道递归类型需要多少空间。不过box有一个已知的大小,所以通过在递归类型定义中插入box,就可以创建递归类型了。一个常见递归类型就是链表。
实例

enum List {Cons(i32, List),Nil,
}
use crate::List::{Cons, Nil};
fn main() {let list = Cons(1, Cons(2, Cons(3, Nil)));//使用这个list来储存1, 2, 3
}

第一个Cons储存1和另一个List值。这个List是一个Cons值,此cons储存2和下一个List值。这个list又是一个cons,储存3和值为Nil的List。这段代码编译错误。因为这个类型 “有无限的大小”。
因为Box<T> 是一个指针,它的大小是确定的,所以将Box作为Cons的成员,这样List的大小就确定了。

enum List {Cons(i32, Box<List>),Nil,
}
use crate::List::{Cons, Nil};
fn main() {let list = Cons(1,Box::new(Cons(2,Box::new(Cons(3,Box::new(Nil))))));
}

三、两个特性

(一)Deref Trait
1.Deref是由Rust标准库提供的一个特性。
实现Deref之后就能把智能指针当作引用使用,相当于重载解引用运算符*。
Deref中包含deref()方法。
deref()方法用于引用self实例并返回一个指向内部数据的指针。

例子

use std::ops::Deref;
struct DerefExample<T> {value: T
}
impl<T> Deref for DerefExample<T> {type Target = T;fn deref(&self) -> &Self::Target {&self.value}
}
let x = DerefExample { value: 'a' };
assert_eq!('a', *x);

范例

use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {fn new(x:T)-> MyBox<T> {MyBox(x)}
}
impl<T> Deref for MyBox<T> {type Target = T;fn deref(&self) -> &T {&self.0}
}
fn main() {let x = 5;let y = MyBox::new(x); // 调用new() 返回创建一个结构体实例println!("5==x is {}",5==x);println!("5==*y is {}",5==*y); // 解引用yprintln!("x==*y is {}",x==*y); // 解引用y
}
编译运行结果如下
5==x is true
5==*y is true
x==*y is true

每次使用 * 时, * 运算符都被替换成先调用deref方法再使用 * 解引用的操作,且只会发生一次,不会无限递归替换 * 操作符,解引用出i32类型的值就停止了

2.DerefMut trait用于重载可变引用的 * 运算符

3.Deref隐式转换
Deref隐式转换将实现了Deref的类型的引用转换为另一种类型的引用。例如,将&String转换为&str,因为String实现了Deref因此可以返回&str。Deref强制转换是Rust在函数或方法传参上的一种便利操作,并且只能作用于实现了Deref的类型。当这种特定类型的引用作为实参传递给和形参类型不同的函数时将自动转换类型。这时会有一系列的deref方法被调用,把我们提供的类型转换成了形参所需的类型。
Deref隐式转换使Rust程序员在调用函数时无需使用过多& 和 *。这个功能方便我们编写同时作用于引用或智能指针的代码。

实例

//还是上面的MyBox<T>
fn hello(name: &str) {println!("Hello, {name}!");
}
let m = MyBox::new(String::from("Rust"));
hello(&m);

因为Deref隐式转换,使用MyBox<String>的引用作为参数是可行的。
因为MyBox<T>实现了Deref,Rust可以通过deref将&MyBox<String>变为&String。而String也实现了Deref,Rust再次调用deref将&String变为&str,这就符合hello函数的定义了。

如果没有Deref强制转换,要把&MyBox<String>类型的值传给hello函数,则不得不编写如下代码

let m = MyBox::new(String::from("Rust"));
hello(&(*m)[..]);

(*m)MyBox<String>解引用为String,接着&和[..]将String转换成&str。
没有Deref强制转换的话,所有这些符号混在一起将难以读写和理解。Deref强制转换会自动执行这些转换。这些转换发生在编译时,所以没有运行时损耗!

Deref隐式转换有三种情形:
(1)当T实现Deref Target=U 时从 &T到 &U。
(2)当T实现DerefMut Target=U 时从 &mut T到 &mut U。
(3)当T实现Deref Target=U 时从 &mut T到 &U。
第一种情况表明如果有一个 &T,而T实现了返回U类型的Deref,则可以直接得到 &U。
第二种情况表明可变引用也有着相同的行为。
第三个情况将可变引用强转为不可变引用。但是反过来是不行的,不可变引用永远也不能强转为可变引用。
这三种情况下,T类型都自动实现了U类型的所有方法。

(二)Drop Trait
Rust中的析构函数是由Drop trait提供的drop()方法。
Drop Trait只有一个方法drop() 。
实现了Drop特质的结构体在离开了它的作用域时会调用drop()方法。
例子

use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {fn new(x:T)->MyBox<T>{MyBox(x)}
}
impl<T> Deref for MyBox<T> {type Target = T;fn deref(&self) -< &T {&self.0}
}
impl<T> Drop for MyBox<T>{fn drop(&mut self){println!("dropping MyBox object from memory ");}
}
fn main() {let x = 50;MyBox::new(x);MyBox::new("Hello");
}
编译运行结果如下
dropping MyBox object from memory
dropping MyBox object from memory

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【AI视野·今日Sound 声学论文速览 第十七期】Tue, 3 Oct 2023

AI视野今日CS.Sound 声学论文速览 Tue, 3 Oct 2023 Totally 15 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers DiffAR: Denoising Diffusion Autoregressive Model for Raw Speech Waveform Generation Authors Roi Benita, Michael Elad, Joseph Kes…

大数据-玩转数据-Flink 海量数据实时去重

一、海量数据实时去重说明 借助redis的Set&#xff0c;需要频繁连接Redis&#xff0c;如果数据量过大, 对redis的内存也是一种压力&#xff1b;使用Flink的MapState&#xff0c;如果数据量过大, 状态后端最好选择 RocksDBStateBackend&#xff1b; 使用布隆过滤器&#xff0c;…

结构型设计模式——桥接模式

摘要 桥接模式(Bridge pattern): 使用桥接模式通过将实现和抽象放在两个不同的类层次中而使它们可以独立改变。 一、桥接模式的意图 将抽象与实现分离开来&#xff0c;使它们可以独立变化。 二、桥接模式的类图 Abstraction: 定义抽象类的接口Implementor: 定义实现类接口 …

【Pytorch笔记】4.梯度计算

深度之眼官方账号 - 01-04-mp4-计算图与动态图机制 前置知识&#xff1a;计算图 可以参考我的笔记&#xff1a; 【学习笔记】计算机视觉与深度学习(2.全连接神经网络) 计算图 以这棵计算图为例。这个计算图中&#xff0c;叶子节点为x和w。 import torchw torch.tensor([1.]…

使用关键字interface来声明使用接口-PHP8知识详解

继承特性简化了对象、类的创建&#xff0c;增加了代码的可重用性。但是php8只支持单继承&#xff0c;如果想实现多继承&#xff0c;就需要使用接口。PHP8可以实现多个接口。 接口类通过关键字interface来声明&#xff0c;接口中不能声明变量&#xff0c;只能使用关键字const声明…

机器人中的数值优化|【六】线性共轭梯度法,牛顿共轭梯度法

机器人中的数值优化|【六】线性共轭梯度法&#xff0c;牛顿共轭梯度法 往期回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法&#xff0c;可行牛顿法的python实现&#xff0c;以Rosenbrock function为例 机器人中的数值优化|【三】无约束优化…

stm32 - 中断

stm32 - 中断 概念中断向量表NVIC 嵌套中断向量控制器优先级 中断EXTI概念基本结构例子- 对射式红外传感器计次例子 - 旋转编码器 概念 stm32 支持的中断资源&#xff08;都属于外设&#xff09; EXTITIMADCUSARtSPII2C stm32支持的中断 内核中断 外设中断 中断通道与优先级 一…

C# 读取Execl文件3种方法

方法 1&#xff0c;使用OLEDB可以对excel文件进行读取 1.1C#提供的数据连接有哪些 对于不同的.net数据提供者&#xff0c;ADO.NET采用不同的Connection对象连接数据库。这些Connection对我们屏蔽了具体的实现细节&#xff0c;并提供了一种统一的实现方法。 Connection类有四…

【Linux】线程池

目录 一、线程池1.什么是线程池2.线程池图解3.实现代码 二、单例模式1.单例模式的概念2.饿汉方式实现单例模式3.懒汉方式实现单例模式4.懒汉方式实现单例模式的线程池 一、线程池 1.什么是线程池 线程虽然比进程轻量了很多&#xff0c;但是每创建一个线程时&#xff0c;需要向…

UCOS的任务创建和删除

一、任务创建和删除的API函数 1、任务创建和删除本质就是调用uC/OS的函数 API函数 描述 OSTaskCreate() 创建任务 OSTaskDel() 删除任务 注意&#xff1a; 1&#xff0c;使用OSTaskCreate() 创建任务&#xff0c;任务的任务控制块以及任务栈空间所需的内存&#xff0c…

算法——买卖股票问题

309. 买卖股票的最佳时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; 一、 究其就是个动态规划的问题 算法实现图 初始化 由于有三个阶段&#xff0c;买入&#xff0c;可交易&#xff0c;冷冻期&#xff0c;那么用dp表表示现在为止的最大利润&#xff0c;则有 dp[0][…

asp.net core 远程调试

大概说下过程&#xff1a; 1、站点发布使用Debug模式 2、拷贝到远程服务器&#xff0c;以及iis创建站点。 3、本地的VS2022的安装目录&#xff1a;C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE下找Remote Debugger 你的服务器是64位就拷贝x64的目…

详解Linux的系统调用fork()函数

在Linux系统中&#xff0c;fork()是一个非常重要的系统调用&#xff0c;它的作用是创建一个新的进程。具体来说&#xff0c;fork()函数会在当前进程的地址空间中复制一份子进程&#xff0c;并且这个子进程几乎完全与父进程相同&#xff0c;包括进程代码、数据、堆栈以及打开的文…

WebSocket实战之四WSS配置

一、前言 上一篇文章WebSocket实战之三遇上PAC &#xff0c;碰到的问题只能上安全的WebSocket&#xff08;WSS&#xff09;才能解决&#xff0c;配置证书还是挺麻烦的&#xff0c;主要是每年都需要重新更新证书&#xff0c;我配置过的证书最长有效期也只有两年&#xff0c;搞不…

ElasticSearch第四讲:ES详解:ElasticSearch和Kibana安装

ElasticSearch第四讲&#xff1a;ES详解&#xff1a;ElasticSearch和Kibana安装 本文是ElasticSearch第四讲&#xff1a;ElasticSearch和Kibana安装&#xff0c;主要介绍ElasticSearch和Kibana的安装。了解完ElasticSearch基础和Elastic Stack生态后&#xff0c;我们便可以开始…

ctfshow—1024系列练习

1024 柏拉图 有点像rce远程执行&#xff0c;有四个按钮&#xff0c;分别对应四份php文件&#xff0c;开始搞一下。一开始&#xff0c;先要试探出 文件上传到哪里&#xff1f; 怎么读取上传的文件&#xff1f; 第一步&#xff1a;试探上传文件位置 直接用burp抓包&#xff0c;…

力扣练习——链表在线OJ

目录 提示&#xff1a; 一、移除链表元素 题目&#xff1a; 解答&#xff1a; 二、反转链表 题目&#xff1a; 解答&#xff1a; 三、找到链表的中间结点 题目&#xff1a; 解答&#xff1a; 四、合并两个有序链表&#xff08;经典&#xff09; 题目&#xff1a; 解…

【数据结构---排序】很详细的哦

本篇文章介绍数据结构中的几种排序哦~ 文章目录 前言一、排序是什么&#xff1f;二、排序的分类 1.直接插入排序2.希尔排序3.选择排序4.冒泡排序5.快速排序6.归并排序总结 前言 排序在我们的生活当中无处不在&#xff0c;当然&#xff0c;它在计算机程序当中也是一种很重要的操…

聊聊常见的IO模型 BIO/NIO/AIO 、DIO、多路复用等IO模型

聊聊常见的IO模型 BIO/NIO/AIO/DIO、IO多路复用等IO模型 文章目录 一、前言1. 什么是IO模型2. 为什么需要IO模型 二、常见的IO模型1. 同步阻塞IO&#xff08;Blocking IO&#xff0c;BIO&#xff09;2. 同步非阻塞IO&#xff08;Non-blocking IO&#xff0c;NIO&#xff09;3.…

排序算法之【希尔排序】

&#x1f4d9;作者简介&#xff1a; 清水加冰&#xff0c;目前大二在读&#xff0c;正在学习C/C、Python、操作系统、数据库等。 &#x1f4d8;相关专栏&#xff1a;C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。 欢迎点赞 &#x1f44d…