当前位置: 首页 > news >正文

21.智能指针(上)

目录

  • 一、概念
  • 二、Box\<T\>
    • 2.1 概念与应用场景
    • 2.2 简单应用
    • 2.3 递归类型的创建
  • 三、通过Deref trait将智能指针当作常规引用处理
    • 3.1 常规引用
    • 3.2 像引用一样使用Box\<T\>
    • 3.3 自定义智能指针
    • 3.4 函数和方法的隐式解引用强制转换
    • 3.5 解引用强制转换与可变性交互
  • 四、使用Drop Trait清理代码
    • 4.1 自动运行
    • 4.2 手动丢弃

一、概念

  • 在Rust中,引用是只是借用数据的指针,智能指针拥有它们所指向的数据的所有权;
  • 智能指针通常使用结构体实现;
  • 智能指针实现了Deref trait,值可以被当作引用对待;
  • 智能指针实现了Drop trait,当值离开作用域时,其所指向的堆数据也去被清除;
  • 常用的智能指针见下表
指针功能说明
Box<T>用于在堆上分配值,允许在编译时执行不可变或可变借用检查
Rc<T>一个引用计数类型,相同数据可以有多个所有者,仅允许在编译时执行不可变借用检查
RefCell<T>允许在运行时执行不可变或可变借用检查;可以在即使RefCell<T> 自身是不可变的情况下修改其内部的值
Ref<T>RefMut<T>通过RefCell<T> 访问
  • 内部可变性模式:在不可变值内部改变值;

二、Box<T>

2.1 概念与应用场景

  • box是最简单最直接的智能指针,其类型是box<T>
  • box主要应用于以下场景:
    • 编译时未知大小的类型,但使用时却需要知道它的确切大小;
    • 大量数据且希望在确保数据不被拷贝的情况下转移所有权;
    • 只关心值的类型是否实现了特定 trait;

2.2 简单应用

fn main(){let b = Box::new(5);println!("b = {}", b);
}
  • 变量b指向了分配在上的值为5的Box;
  • b拥有这块内存的所有权,离开作用域后堆内存被自动释放;

2.3 递归类型的创建

  • Rust需要在编译时知道类型占用的空间大小;
  • box的已知大小,让其可以在循环类型定义中插入box,就可以创建递归类型;
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))))));
}
  • Cons成员将需要一个i32类型的空间大小以及box指针数据的空间;
  • Nil成员不存储值,因此它比Cons成员需要更少的空间;
  • 看起来像这样

在这里插入图片描述

  • 如果不用Box定义递归,写成下面这样

enum List{Cons(i32, List),Nil,
}use crate::List::{Cons, Nil};
fn main() {let list = Cons(1, Cons(2, Cons(3, Nil)));
}
  • 则编译报错,表明类型占用的空间无限大

在这里插入图片描述

  • 其空间排布类型于

在这里插入图片描述

三、通过Deref trait将智能指针当作常规引用处理

  • 实现Deref trait可以让使用者重载解引用运算符(dereference operator) *
  • 这种方式实现Deref trait的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针;

3.1 常规引用

  • 常规引用是一种指针类型;
fn main() {let x = 5;let y = &x;assert_eq!(5, x);assert_eq!(5, *y);
}
  • y等于x的引用,使用*y访问x的值;

3.2 像引用一样使用Box<T>

    let x = 5;let y = Box::new(x);assert_eq!(5, x);assert_eq!(5, *y);
  • 代码可正常运行不报错;

3.3 自定义智能指针

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);assert_eq!(5, x);assert_eq!(5, *y);
}
  • MyBox<T> 被定义为包含一个元素的元组结构体;
  • new函数获取一个T类型的参数并返回一个存入传入值的实例;
  • 为MyBox实现Deref trait才能启动*运算符的解引用功能;
  • impl<T> Deref for MyBox<T>中的type Target = T 定义了此trait的关联类型;
  • deref方法返回了一个值的引用,如果直接返回值,则值的选择权将被移出self;
  • 当使用*y时,底层运行了代码*(y.deref())

3.4 函数和方法的隐式解引用强制转换

  • 解引用强制转换只能工作在实现了Dereftrait 的类型上;
  • 解引用强制转换是将一种类型隐式转换为另外一种类型的引用;
  • 前一种类型实现了Dereftrait,并且其关联类型是后一种类型;

例如,解引用强制转换可以将 &String 转换为 &str,因为类型 String 实现了 Deref trait 并且其关联类型是 str;

#[stable(feature = "rust1", since = "1.0.0")]
impl ops::Deref for String {type Target = str;#[inline]fn deref(&self) -> &str {unsafe { str::from_utf8_unchecked(&self.vec) }}
}
  • 将特定类型的值的引用传递给函数且与函数定义的参数类型不匹配时,会发生解引用强制转换
  • 此时有一系列的deref方法被调用,将我们提供的参数类型转换成函数或方法需要的参数类型;
  • 解引用强制转换功能可以让开发者编写函数和方法调用时无需增加过多显式使用&和*引用和解引用。
  • 解引用强制转换功能也使得开发者可以编写更多同时作用于引用或智能指针的代码;
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 hello(name: &str){println!("Hello, {}", name);
}fn main() {let m = MyBox::new(String::from("Rust"));hello(&m);
}
  • main函数中的m为MyBox<String>值的引用;
  • MyBox<T>上实现了Dereftrait,Rust可以通过deref调用将&MyBox<String>变为&String
  • 再次调用deref将&String 变为 &str;
  • 如果没有实现解引用强制转换,为了使用&MyBox<String>类型的值调用hello函数,应该这样写
fn main() {let m = MyBox::new(String::from("Rust"));hello(&(*m)[..]);
}
  • (*m) 将 MyBox<String> 解引用为 String;
  • &和[…] 获取了整个 String 的字符串 slice 来匹配 hello 函数的参数;
  • 没有解引用强制转换所有这些符号混在一起将更难以读写和理解;
  • Rust的解引用强制转换发生在编译,因此在运行时没有损耗!

3.5 解引用强制转换与可变性交互

  • 类似于使用 Deref trait 重载不可变引用的*运算符,Rust提供了DerefMut trait用于重载可变引用的*运算符;
  • Rust 在发现类型和 trait 的实现满足以下三种情况时会进行解引用强制转换;
    1. 当 T: Deref<Target=U> :从 &T 到 &U
      如果有一个&T,而T实现了返回U类型的Deref,则可以直接得到&U
    2. 当 T: DerefMut<Target=U> :从 &mut T 到 &mut U
      对于可变引用有着与第一种相同的行为;
    3. 当 T: Deref<Target=U> :从 &mut T 到 &U
      Rust也会将可变引用强转为不可变引用,但是反之是不可能的,因为不可变引用永远也不能强转为可变引用;

四、使用Drop Trait清理代码

4.1 自动运行

  • 通过实现Droptrait指定变量离开作用域时被执行的代码;
  • 可以理解为析构函数;
struct CustomSmartPointer {data: String,
}impl Drop for CustomSmartPointer {fn drop(&mut self) {println!("Dropping CustomSmartPointer with data `{}`!", self.data);}
}fn main() {{let c = CustomSmartPointer { data: String::from("stuff c") };}let d = CustomSmartPointer { data: String::from("stuff d") };let e = CustomSmartPointer { data: String::from("stuff e") };println!("CustomSmartPointers created.");
}
  • main函数中离开最内层的大括号后,变量c首先离开作用域,自动调用drop方法;
  • 然后打印CustomSmartPointers created.
  • 变量d、e最后离开作用域,再自动调用对应的drop方法;
  • d、e的输出结果显示,以先进后出的方式调用drop方法;

在这里插入图片描述

4.2 手动丢弃

  • 不能显式的调用drop方法;
  • 如果要在作用域结构之前强制释放变量,使用drop(x)实现;
fn main() {{let c = CustomSmartPointer { data: String::from("stuff c") };}let d = CustomSmartPointer { data: String::from("stuff d") };drop(d);let e = CustomSmartPointer { data: String::from("stuff e") };println!("CustomSmartPointers created.");
}

运行代码,可以发现d被提前析构;
在这里插入图片描述

http://www.lryc.cn/news/384179.html

相关文章:

  • Jenkins+gitee流水线部署springboot项目
  • python--os.walk()函数使用(超详细)
  • 基础名词概念
  • ArkTS开发系列之Web组件的学习(2.9)
  • postman接口工具的详细使用教程
  • C语言经典例题-17
  • 鸿蒙学习(-)
  • 【TB作品】MSP430G2553,单片机,口袋板, 烘箱温度控制器
  • PCM、WAV,立体声,单声道,正弦波等音频素材
  • 基于深度学习的图像去雾
  • 中国电子学会青少年编程等级考试真题下载
  • PostMan动态设置全局变量
  • ACL 2023事件相关(事件抽取、事件关系抽取、事件预测等)论文汇总
  • 力扣:59. 螺旋矩阵 II(Java,模拟)
  • 记录SpringBoot启动报错解决
  • 微软代码页标识符 (Code Page Identifiers)
  • 刷题——二叉树的后续遍历
  • 用友U8 Cloud smartweb2.showRPCLoadingTip.d XXE漏洞复现
  • React中的事件绑定的四种方式
  • 小文件过多的解决方法(不同阶段下的治理手段,SQL端、存储端以及计算端)
  • SGPT论文阅读笔记
  • 虚拟机与主机的网络桥接
  • urfread刷算法题day1|LeetCode2748.美丽下标的数目
  • 面向对象修炼手册(四)(多态与空间分配)(Java宝典)
  • 基于UDP的网络聊天室(多线程实现收和发消息)
  • 【脚本工具库】随机抽取数据 - 图像和标签对应(附源码)
  • 【python】eval函数
  • 实战|记一次java协同办公OA系统源码审计
  • 浅浅谈谈如何利用Javase+多线程+计算机网络的知识做一个爬CSDN阅读量总访问量的程序
  • Vscode 中launch.json与tasks.json文件