Rust编程基础核心之所有权(下)

1.变量与数据交互方式之二: 克隆

在上一节中, 我们讨论了变量与数据交互的第一种方式: 移动, 本节将介绍第二种方式:克隆。

如果我们 确实 需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的通用函数。

看下面的代码:

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

这段代码能正常运行, 并且堆上的数据现在可以被复制了。

我们在代码中下个断点, 使用调试器观察下s1的内容,如图:

结合上一章节的分析, 此时s1变量中保存的指针ptr指向的内存保存了内容:"hello"。

现在执行语句: let s2 = s1.clone(); 单不执行一下看下s2的内容,如图:

可以看到, clone()函数的确将字符串内容复制到变量s2中。

注意:当出现 clone 调用时,我们心里要清楚一些特定的代码被执行而且这些代码可能相当消耗资源。很容易能察觉到一些不寻常的事情正在发生。

下面再看一段代码:

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

执行这段代码, 结果如下:

这段代码似乎与我们刚刚学到的内容相矛盾:没有调用 clone,不过 x 依然有效且没有被移动到 y 中。

原因是像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 y 后使 x 无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 clone 并不会与通常的浅拷贝有什么不同,我们可以不用管它。

Rust 有一个叫做 Copy trait 的特殊注解,可以用在类似整型这样的存储在栈上的类型上, 如果一个类型实现了 Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。

Rust 不允许自身或其任何部分实现了 Drop trait 的类型使用 Copy trait。如果我们对其值离开作用域时需要特殊处理的类型使用 Copy 注解,将会出现一个编译时错误。

那么哪些类型实现了 Copy trait 呢?可以查看给定类型的文档来确认,不过作为一个通用的规则,任何一组简单标量值的组合都可以实现 Copy,任何不需要分配内存或某种形式资源的类型都可以实现 Copy 。如下是一些 Copy 的类型:

  • 所有整数类型,比如 u32

  • 布尔类型,bool,它的值是 truefalse

  • 所有浮点数类型,比如 f64

  • 字符类型,char

  • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。

2.所有权和函数

将值传递给函数与给变量赋值的原理相似。向函数传递值可能会移动或者复制,就像赋值语句一样。

看一下下面的代码:

fn main() {let s = String::from("hello");  // s 进入作用域
​takes_ownership(s);             // s 的值移动到函数里 ...// ... 所以到这里不再有效
​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 移出作用域。没有特殊之处

当尝试在调用 takes_ownership 后使用 s 时,Rust 会抛出一个编译时错误。这些静态检查使我们免于犯错。

3.返回值与作用域

返回值也可以转移所有权, 看下面的代码:

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 并移出给调用的函数
}

变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。

虽然这样是可以的,但是在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都传进去再返回来就有点烦人了,除此之外,我们也可能想返回函数体中产生的一些数据。

我们可以使用元组来返回多个值, 看下面的代码:

fn main() {let s1 = String::from("hello");
​let (s2, len) = calculate_length(s1);
​println!("The length of '{}' is {}.", s2, len);
}
​
fn calculate_length(s: String) -> (String, usize) {let length = s.len(); // len() 返回字符串的长度
​(s, length)
}

但是这未免有些形式主义,而且这种场景应该很常见。幸运的是,Rust 对此提供了一个不用获取所有权就可以使用值的功能,叫做 引用references)。

咱们下一章将揭开引用与借用的神秘面纱。

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

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

相关文章

1.用递归求一个正整数的逆序数

#include<stdio.h>void f(int n){if(0!n){ //n是0的时候&#xff0c;退出循环 printf("%d ",n%10);f(n/10);} } int main(){f(12345);return 0; } /*void(12345) 12345不等于0 12345%105 输出 5 12345/101234void(1234) 1234不等于0 1234%104 输出 4 1…

DDD领域模式的模块层级及其依赖关系

DDD领域模型设计是一种常用的软件设计模式,它强调将业务逻辑和数据模型放在最核心的位置,以便更好地满足业务需求。在DDD领域模型设计中,应用程序被分为四个层次:用户界面层、应用服务层、领域模型层和基础设施层。 层次 用户界面层(Presentation Layer) 作为用户和应…

【Unity】2D角色跳跃控制器

最近加了学校的Nova独游社&#xff0c;本文是社团出的二面题&#xff0c;后续有时间优化下可能会做成一个二维冒险小游戏。本文主要涉及相关代码&#xff0c;参考教程&#xff1a;《勇士传说》横版动作类游戏开发教程 效果演示 【Unity】2D角色跳跃模拟器 主要实现功能&#xf…

模版方法模式-定义算法的框架

在生活中&#xff0c;很多事需要通过几个步骤才能完成。例如找工作&#xff0c;一般都包含投递简历、面试、等待结果等几个步骤。投递简历何等待结果这两个步骤大同小异&#xff0c;最大的区别在于面试这个步骤。 在软件开发中&#xff0c;有时也会遇到类似情况。某个方法的实…

SpringBoot+AOP+自定义注解,优雅实现日志记录

文章目录 前言准备阶段1、数据库日志表2、自定义注解编写3、AOP切面类编写4、业务层4.1、Service 层&#xff1a;4.2 Service 实现层&#xff1a; 5、测试 前言 首先我们看下传统记录日志的方式是什么样的&#xff1a; DeleteMapping("/deleteUserById/{userId}") …

Juniper Networks Junos OS EX远程命令执行漏洞(CVE-2023-36845)

Juniper Networks Junos OS EX远程命令执行漏洞&#xff08;CVE-2023-36845&#xff09; 免责声明漏洞描述漏洞影响漏洞危害网络测绘Fofa: body"J-web" || title"Juniper Web Device Manager" 漏洞复现1. 构造poc2. 查看文件3. 执行命令 免责声明 仅用于技…

AI系统ChatGPT程序源码+AI绘画系统源码+支持GPT4.0+Midjourney绘画

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

[ Linux Busybox ] flash_eraseall 命令解析

文章目录 相关结构体flash_eraseall 函数实现flash_eraseall 实现流程图 文件路径&#xff1a;busybox-1.20.2/miscutils/flash_eraseall.c 相关结构体 MTD 相关信息结构体 struct mtd_info_user {__u8 type; // MTD 设备类型__u32 flags; // MTD设…

VSCode修改主题为Eclipse 绿色护眼模式

前言 从参加开发以来&#xff0c;一直使用eclipse进行开发&#xff0c;基本官方出新版本&#xff0c;我都会更新。后来出来很多其他的IDE工具&#xff0c;我也尝试了&#xff0c;但他们的主题都把我劝退了&#xff0c;黑色主题是谁想出来&#xff1f;&#x1f602; 字体小的时…

docker安装达梦数据库镜像并初始化实例设置数据库大小写不敏感

全文参考文章Docker - 通过容器安装部署达梦数据库教程 以及docker安装达梦V8数据库 UTF-8及大小写敏感问题 一、安装部署达梦数据库 下载完成之后使用FileZilla上传到服务器上面 启动命令 创建dm8的容器并设置大小写不敏感运行 docker run -d -p 5236:5236 \ --restartalwa…

移远EC600U-CN开发板 day02

1.QuecPythonLVGL显示图片 由于官方提供的显示图片函数使用失败&#xff0c;为了能在屏幕上显示图片&#xff0c;通过对出厂脚本的分析&#xff0c;成功使用LVGL显示图片 (1)代码 import lvgl as lv from tp import gt9xx from machine import LCD from machine import Pin …

【网络协议】聊聊HTTPDNS如何工作的

传统 DNS 存在哪些问题&#xff1f; 域名缓存问题 我们知道CND会进行域名解析&#xff0c;但是由于本地会进行缓存对应的域名-ip地址&#xff0c;所以可能出现过期数据的情况。 域名转发问题 出口 NAT 问题 域名更新问题 解析延迟问题 因为在解析DNS的时候&#xff0c;需要进行…