rust学习-所有权

news2024/11/16 5:21:40

运行程序必须管理使用内存的方式

(1)一些语言中具有垃圾回收机制,程序运行时不断寻找不再使用的内存
(2)一些语言中,开发者必须亲自分配和释放内存
(3)Rust 通过所有权系统管理内存。编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。

堆和栈的比较

栈中所有数据都必须占用已知且固定的大小
在编译时大小未知或大小可能变化的数据,要改为存储在堆上。
堆上分配内存(allocating on the heap),缺乏组织:当向堆放入数据时,内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。将指向堆内存的指针推入栈中,当需要实际数据时,必须访问栈上的指针。
入栈比在堆上分配内存要快,因为(入栈时)分配器无需为存储新数据去搜索内存空间;其位置总是在栈顶。
堆上分配内存则需要更多的工作,这是因为分配器必须首先找到一块足够存放数据的内存空间,并接着做一些记录为下一次分配做准备。

访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。处理器在处理的数据彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。

所有权系统的功能

跟踪哪部分代码正在使用堆上的哪些数据,最大限度的减少堆上重复数据数量,以及清理堆上不再使用的数据确保不会耗尽空间,管理堆数据
(1)Rust 中的每一个值都有一个被称为 所有者(owner)的变量
(2)值在任一时刻有且只有一个所有者
(3)当所有者(变量)离开作用域,这个值将被丢弃
Rust:内存在拥有它的变量离开作用域后就被自动释放
Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何自动的复制可以被认为对运行时性能影响较小

String类型管理被分配到堆上的数据,所以能够存储在编译时未知大小的文本。下面以String为例来讲解。

变量与数据交互的方式(一):移动

仅拷贝栈上数据
错误代码,let s2 = s1 之后,Rust 认为 s1 不再有效
因此 Rust 不需要在 s1 离开作用域后清理任何东西

    let s1 = String::from("hello");
    let s2 = s1; // s2有了堆内存的管理权,s1丢失,不是简单的浅拷贝
    println!("{}, world!", s1);

在这里插入图片描述

变量与数据交互的方式(二):克隆

拷贝栈上数据+堆上数据

    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("s1 = {}, s2 = {}", s1, s2);

仅栈上数据拷贝

像整型这样在编译时已知大小的类型被整个存储在栈上,所以拷贝实际值很快。没有理由在创建变量 y 后使 x 无效。这里没有深浅拷贝的区别

    let x = 5;
    let y = x;
    println!("x = {}, y = {}", x, y);

示例-所有权和函数

fn main() {
  let s = String::from("hello");  // s 进入作用域

  takes_ownership(s);             // s 的值移动到函数里
  // 这里s不再有效
  // 在调用 takes_ownership 后使用 s 时,Rust 会抛出一个编译时错误
  // 这些静态检查使我们免于犯错
  let x = 5;                      // x 进入作用域

  makes_copy(x);                  // x 应该移动函数里,
  // 但 i32 是 Copy 的,所以在后面可继续使用 x

} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,所以不会有特殊操作

fn takes_ownership(some_string: String) { // some_string 进入作用域
  println!("{}", some_string);
} // some_string 移出作用域并调用 `drop` 方法。占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
  println!("{}", some_integer);
} //some_integer 移出作用域。不会有特殊操作

示例-所有权和返回值

fn main() {
  let s1 = gives_ownership();         // gives_ownership 将返回值移给 s1
  let s2 = String::from("hello");     // s2 进入作用域

  let s3 = takes_and_gives_back(s2);  // s2 被移动到takes_and_gives_back 中
                                      // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,所以什么也不会发生。s1 移出作用域并被丢弃

fn gives_ownership() -> String {           // gives_ownership 将返回值移动给调用它的函数
  let some_string = String::from("yours"); // some_string 进入作用域
  some_string                              // 返回 some_string 并移出给调用的函数
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
  a_string  // 返回 a_string 并移出给调用的函数
}

借用borrowing

函数使用一个值但不获取所有权
如果还要接着使用它的话,每次都传进去再返回来就有点烦人

fn main() {
	// 变量默认不可变,引用也(默认)不允许修改引用的值
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

在这里插入图片描述

可变引用
在同一时间,只能有一个对某一特定数据的可变引用
带来的好处是Rust 可以在编译时避免数据竞争

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

错误代码

    let mut s = String::from("hello");
    let r1 = &mut s;
    let r2 = &mut s;
    println!("{}, {}", r1, r2);

注,数据竞争由三个行为造成:
(1)两个或更多指针同时访问同一数据
(2)至少有一个指针被用来写入数据
(3)没有同步数据访问的机制

Rust避免数据竞争的方式是不会编译存在数据竞争的代码,牛逼
一个引用的作用域从声明的地方开始一直持续到最后一次使用
不能在拥有不可变引用的同时拥有可变引用

    let mut s = String::from("hello");

    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    println!("{} and {}", r1, r2);
    // 此位置之后 r1 和 r2 不再使用

    let r3 = &mut s; // 没问题
    println!("{}", r3);

上述r1r2和r3作用域没有重叠,代码可以编译。
编译器在作用域结束之前判断不再使用的引用的能力被称为非词法作用域生命周期(Non-Lexical Lifetimes,简称 NLL)

错误代码

    let mut s = String::from("hello");

    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    let r3 = &mut s; // 大问题

    println!("{}, {}, and {}", r1, r2, r3);

悬垂引用(Dangling References)

释放内存时保留指向它的指针而错误地生成一个悬垂指针,指向的内存可能已经被分配给其它持有者
Rust编译器确保引用永远也不会变成悬垂状态:当拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String { // dangle 返回一个字符串的引用
    let s = String::from("hello"); // s 是一个新字符串
    &s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
  // 危险!

解决方式

fn no_dangle() -> String {
    let s = String::from("hello");
    s
}

这样就没有任何错。所有权被移出,没有值被释放。

Slice

一个没有所有权的数据类型 slice
slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合
字符串 slice(string slice)是 String 中部分值的引用

示例:接收一个字符串,并返回该字符串中的第一个单词
如果该字符串中并未找到空格,返回整个字符串。

// 将 s 参数的类型改为字符串 slice
fn first_word(s: &str) -> &str {
	// as_bytes 方法将 String 转化为字节数组
    let bytes = s.as_bytes();

	// iter 方法在字节数组上创建一个迭代器
	// enumerate 包装了 iter 的结果,将这些元素作为元组的一部分来返回
	// i 是索引,&item是字符的不可变引用
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

fn main() {
    let my_string = String::from("hello world");
    println!("my_string={}", my_string);

    // `first_word` 接受 `String` 的切片,无论是部分还是全部
    let word = first_word(&my_string[0..6]);
    println!("word={}", word);

    let word = first_word(&my_string[..]);
    println!("word={}", word);

    // `first_word` 也接受 `String` 的引用,
    // 这等同于 `String` 的全部切片
    let word = first_word(&my_string);
    println!("word={}", word);

    let my_string_literal = "hello world";
    println!("my_string_literal={}", my_string);

    // `first_word` 接受字符串字面量的切片,无论是部分还是全部
    let word = first_word(&my_string_literal[0..6]);
    println!("word={}", word);

    let word = first_word(&my_string_literal[..]);
    println!("word={}", word);

    // 因为字符串字面值**就是**字符串 slice,
    // 这样写也可以,即不使用 slice 语法!
    // 如果有一个 String,则可以传递整个 String 的 slice 或对 String 的引用
    let word = first_word(my_string_literal);
    println!("word={}", word);
}

字符串字面量被储存在二进制文件中
字符串字面量就是 slice

let s = "Hello, world!";

s 的类型是 &str:它是一个指向二进制程序特定位置的 slice
字符串字面量不可变;&str 是一个不可变引用

let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];

在这里插入图片描述

总结

任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用
引用必须总是有效

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

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

相关文章

Windows操作系统安全加固

Windows操作系统安全加固 一、安全加固基本思路1.1、安全基线1.2、系统信息审查 二、Windows安全配置加固2.1、漏洞修复——补丁安装2.2、漏洞修复——端口封禁2.2.1、windows高危端口加固实践——封禁135端口对外开放 2.3、安全配置加固——账号口令 一、安全加固基本思路 1.…

10.20UEC++/代理,单播,多播

构建一个无参无返回值类型的函数(也可以有参有返回值类型) 相对应的构建一个无参无返回值类型的代理

【计算机组成与体系结构课程设计】上机考试

1 (1) 针对图中的MIPS处理器数据通路(不考虑I/O),用红色或蓝色描出执行sw指令时的数据通路。(将该图下载到电脑,并用画图完成描线) (2) 写出执行sw指令时,各个元件控制端信号应该置什么值? 2 基于Minisys处理…

Qt保存代码

补全保存代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//字体按钮对应的槽函数 void Widget::on_fontBtn_clicked() {…

使用常见的三个命令分析线程的信息

目录 jps jstack.exe jvisualvm.exe jmc.exe 这三个都在jdk\bin文件夹中!!! 查看线程等待状态与信息可以采用3种常见命令。 本文中针对以下代码进行演示 package ChapterOne.test;public class Run3 {public static void main(String[…

Coggle 30 Days of ML(23年7月)任务四:线性模型训练与预测

Coggle 30 Days of ML(23年7月)任务四:线性模型训练与预测 任务四:使用TFIDF特征和线性模型完成训练和预测 说明:在这个任务中,你需要使用TFIDF特征和线性模型(如逻辑回归)完成训练…

图像处理-比特平面分层和重构

一、比特平面分层 像素是由比特组成的数字。例如在256级灰度图像中,每个像素的灰度是由8比特(一个字节)组成。如下图所示,一幅8比特图像由8个1比特平面组成,其中平面1包含图像中所有像素的最低阶比特,而平…

人工智能(pytorch)搭建模型17-pytorch搭建ReitnNet模型,加载数据进行模型训练与预测

大家好,我是微学AI,今天给大家介绍一下人工智能(pytorch)搭建模型17-pytorch搭建ReitnNet模型,加载数据进行模型训练与预测,RetinaNet 是一种用于目标检测任务的深度学习模型,旨在解决目标检测中存在的困难样本和不平衡…

前端Vue仿京东淘宝我的优惠券列表组件 用于电商我的优惠券列表页面

随着技术的发展,开发的复杂度也越来越高,传统开发方式将一个系统做成了整块应用,经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改,造成牵一发而动全身。 通过组件化开发,可以有效实现…

C++之子类指向父类,父类指向子类总结(一百五十五)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…

nginx配置例子-动静分离实例

动静分离实例 1.准备工作 步骤一:在 根目录/ 下 创建 目录data/www 和 data/image 步骤二:在目录www 下 ,创建a.html文件 步骤三:在目录image下,将图片拖到Xshell客户端,实现图片导入Linux,导…

ITIL 4—发布管理实践

2 基本信息 2.1 目的和描述 关键信息 发布管理实践的目的是使新的和变更的服务及功能均可用。 发布管理实践是为了确保组织及其服务使用者在符合组织政策和协议的前提下,服务可以正常使用而产生的最佳实践。 传统场景下,服务组件(包括基…

QT -20230709

练习&#xff1a; 登录界面增加注册功能(在本地增加用户文件进行比对用户) LoginWindow.h #ifndef LOGINWINDOW_H #define LOGINWINDOW_H#include <QMainWindow> #include <QIcon> #include <QLabel> #include <QLineEdit> #include <QPushButto…

TCP Socket性能优化秘籍:掌握read、recv、readv、write、send、sendv的最佳实践

TCP Socket性能优化秘籍&#xff1a;掌握read、recv、readv、write、send、sendv的最佳实践 博主简介一、引言1.1、TCP Socket在网络通信中的重要性1.2、为什么需要优化TCP Socket的性能&#xff1f; 二、TCP Socket读操作的性能优化2.1、read、recv、readv的功能和用法2.2、提…

有哪些做的问卷调查的工具?

想要洞察市场变化、了解某个特定群体的喜好等情况&#xff0c;使用问卷调查是常见的方法。而互联网的发展&#xff0c;越来越多的人转战网络问卷&#xff0c;而功能各异的问卷工具却让人挑花眼。今天&#xff0c;我们精准针对大家的需求和常见的一些问题&#xff0c;为大家聊一…

ASPICE汽车软件能力如何评估

第一节我们介绍了&#xff1a;什么是ASPICE 上一节我们介绍了&#xff1a;什么是aspice认证 这一节我们看一看&#xff1a;ASPICE汽车软件能力是如何评估 为了使汽车电控系统的研发具有统一的流程和规范的标准&#xff0c;并且使整个开发进度具有可控性和可预测阻借用具有国际…

利用Anaconda完成Python环境安装及配置

1 Anaconda 1.1 配置过程 Anaconda是一个开源的Python和R编程语言的软件包管理器和环境管理器&#xff0c;用于数据科学和机器学习的开发。 进入官网https://www.anaconda.com/下载安装包next->argee进入下列界面&#xff0c;选择Just Me 选择安装路径&#xff0c;点击Ne…

Swagger-Bootstrap-UI

Swagger-Bootstrap-UI 是一个为 Swagger 提供美观、易用的界面展示和增强功能的开源项目。它通过自定义样式和交互&#xff0c;提供了更好的文档展示和交互体验&#xff0c;包括美化的界面、接口测试工具、在线调试、文档导出等功能。 更高阶的有Knife4j,Knife4j是一个集Swagg…

本地部署 ChatPPT

本地部署 ChatPPT 1. 什么是 ChatPPT2. Github 地址3. 安装 Miniconda34. 创建虚拟环境5. 安装 ChatPPT6. 运行 ChatPPT 1. 什么是 ChatPPT ChatPPT由chatgpt提供支持&#xff0c;它可以帮助您生成PPT/幻灯片。支持中英文输出。 2. Github 地址 https://github.com/huimi24/…

CS制作office宏文档钓鱼

前言 书接上文&#xff0c;CobaltStrike_1_部署教程&#xff0c;改篇介绍【CS制作office宏文档钓鱼】。PS&#xff1a;文章仅供学习使用&#xff0c;不做任何非法用途&#xff0c;后果自负&#xff01; 一、CobaltStrike 4.X安装部署 部署安装之前的文章已经介绍过了&#xf…