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

Rust简明教程第三章-所有权与借用

观看B站软件工艺师杨旭的rust教程学习记录,有删减有补充

Stack&Heap 栈内存和堆内存

  • stack用于存放函数的参数、局部变量等
  • stack只能从栈顶操作数据,压栈push,出栈pop,后进先出
  • stack存储的数据必须有已知的固定大小
  • stack写入读取数据比stack快得多
  • heap存储编译时大小未知或运行时可能发生变化的数据
  • heap分配空间先标记一块内存空间为在用,并返回一个指向这块地址的指针
  • heap分配内存返回的指针大小已知,heap的指针存储在stack

所有权

  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值同时只能有一个所有者
  • 当所有者超出作用域(scope)时,该值将被删除

字符串字面值,大小已知,存储在stack上

fn main(){//s不可用let s = "Hello";//s可用}//s作用域结束,s不可用

integerarray等大小固定的值存储在stack上,而String类型的值数据存储在heap上,String类型的值是可变的,地址存储在stack

fn main() {let mut s = String::from("Hello");s.push_str(",World");println!("{}", s); //Hello,World
}//s离开作用域调用drop

Move 变量和数据的交互方式

Rust处理资源时,默认行为是move(转移/移动)

对于已知固定大小的值,move的方式是在stack上复制stack上存储的值并绑定到新声明的y变量

fn main() {let x = 5;//x拥有5let y = a;//y拥有copy的5println!("{}", x); //5println!("{}", y); //5
}

对于可变大小的值,move的方式是在stack上复制stack存储的指向heap的指针(、长度、容量),让原来绑定指针的变量失效

fn main() {let s1 = String::from("Hello");let s2 = s1;//s1不再拥有"Hello",所有权move到s2// println!("{}",s1);//s1失效,s1不可用println!("{}",s2);//Hello
}

Shallow copy&Deep copy 浅拷贝和深拷贝

  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存(开销小)
  • 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象,是“值”而不是“引用”(开销大)

Rust对存储在heap上的数据类型不仅复制了指针还让原来的变量失效,所以称作Move(移动),基础类型的值存储在satck中直接复制和复制指针没有太大的性能差距,而存储在heap上的复杂类型直接复制值会消耗大量性能

如果要实现对heap上的数据深拷贝,使用clone()方法,s1和s2所有权没有改变,s2拥有的是被clone的新数据的所有权

fn main() {let s1 = String::from("Hello");let s2 = s1.clone();println!("{}", s1);//Helloprintln!("{}", s2);//Hello
}

Copy trait 复制特质

  • 对于已知固定大小的数据类型,数据完全存储在stack中的类型,rust默认实现了Copy trait
  • 如果一个类型实现了Copy trait,那么变量绑定实际上是复制实际数据,旧变量依然可用
  • 如果一个类型或者该类型的一部分实现了Copy trait,那么Rust不允许再让它实现Copy trait
  • 任何简单标量的组合类型都实现了Copy trait
  • 任何需要分配内存或者某种资源的都不是Copy的

如:

integer:u8i32
bool
char
float:f32f64
Tuple:如果其中所有字段都是固定大小的可以Copy的,这个元组也是可以Copy

所有权与函数

fn main(){let  s = String::from("Hello World");take_ownership(s);//move所有权,s失效,不可用let x= 5;makes_copy(x);//copy, 传入的是copy的值,不改变所有权println!("x:{}",x);//x:5}
fn take_oenership(some_string:String){println!("{}",some_string);//Hello World
}//drop s
fn makes_copy(some_number:i32){println!("{}",some_number);//5
}

返回值与作用域

函数在返回值的过程中也会发生所有权转移

fn main() {let s1 = gives_ownership();//将world所有权move到s1println!("{}",s1);//worldlet s2 = String::from("hello");//变量s2绑定hello的所有权let s3 = takes_and_gives_back(s2);//将函数中返回的所有权move到s3,s2失效println!("{}",s3);//hello
}//drop s1、s3
fn gives_ownership() -> String {let some_string = String::from("world");//some_string绑定world的所有权some_string//返回world所有权
}
fn takes_and_gives_back(a_string: String) -> String {//s2的所有权move到函数中a_string//返回s2的所有权
}
  • 把一个值绑定给其他变量时就会发生move
  • 当一个包含heap数据的变量离开作用域时,它的值会被drop函数清理,除非数据的所有权move到另一个变量上了(包含heap数据的变量被move到另一个变量后原变量会失效,所以不用drop)

引用与借用

函数使用值但不获得所有权

fn main() {let s1 = String::from("hello"); //将hello绑定到s1let len = calculate_length(&s1); //引用s1但不move s1,move的是s1的引用println!("{}的长度为:{}", s1, len); //hello的长度为:5
}
fn calculate_length(s: &String) -> usize {//借用s1不获得所有权s.len() //返回s1的长度
}
  • 把引用作为函数参数叫借用

修改借用的值,借用时&mut

fn main() {let mut s1 = String::from("hello"); //将hello绑定到s1let len = calculate_length(&mut s1); //引用s1但不move s1,move的是s1的引用println!("{}的长度为:{}", s1, len); //hello,world的长度为:11
}
fn calculate_length(s:&mut String) -> usize {//借用s1不获得所有权s.push_str(",world");s.len() //返回s1的长度
}
  • 可变引用在同一作用域只能借用1次(避免数据竞争)
  • 不可变引用在同一作用域可以借用多次
  • 不可以在同一作用域同时借用一个可变引用和一个不可变引用

使用{}创建非同一作用域的可变引用

fn main() {let mut s = String::from("hello");{let s1 = &mut s; //借用可变引用sprint_info(s1); //hello}let s2 = &mut s;//借用可变引用sprint_info(s2); //hello
}
fn print_info(s: &mut String) {println!("{}", s);
}

slice切片

&str 字符串切片(String类型)

  • 字符串切片的范围索引必须发生在有效的UTF-8字符边界内
  • slice是对String类型数据的借用,不获得所有权
fn main() {let s = String::from("hello world");//slicelet hello = &s[..5];let world = &s[6..11]; //或[6..]或[6..s.len()]println!("{},{}", hello, world); //hello,world//整个也是切片let whole = &s[0..s.len()]; //或[..]println!("{}", whole); //hello world
}

fn first_word(s: &str) -> &str{}

  • 使用在字符串切片直接调用该函数
  • 使用String类型,可以借用一个完整的切片调用该函数(函数可以被切片类型复用)
fn main() {let a_string = String::from("hello world");//String类型let wordIndex = first_world(&a_string[..]);//字符串切片println!("{}", wordIndex);//hellolet b_string = "hello rust";//本质是切片&strlet wordIndex = first_world(b_string);println!("{}", wordIndex);//hello
}
//遇到空格切片
fn first_world(s: &str) -> &str {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return &s[..i];}}&s[..]
}

Array slice 数组切片

fn main(){let arry=[1,2,3,4,5,6];let slice = &arry[0..5];println!("{:?}",slice);//[1, 2, 3, 4, 5]
}

还可以对切片切片

fn main() {let arry = [1, 2, 3, 4, 5, 6];let slice = &arry[0..5];println!("{:?}", slice); //[1, 2, 3, 4, 5]let slice_of_slice = &slice[..2];println!("{:?}", slice_of_slice); //[1, 2]
}

Struct 结构体

  • struct是自定义的数据类型
  • 变量绑定顺序和声明的顺序可以不一样
#[derive(Debug)] //派生属性,Debug用来格式化输出值,顾名思义,debug用的
struct User {username: String,email: String,sign_in_count: u64,active: bool,
}
fn main() {let user1 = User {email: String::from("abc@outlook.com"),username: String::from("张三"),active: true,sign_in_count: 455,};println!("{:?}", user1); //User { username: "张三", email: "abc@outlook.com", sign_in_count: 455, active: true }
}
  • 一旦struct的实例是可变的,那么实例中的所有字段都是可变的
#[derive(Debug)] //派生属性,Debug用来格式化输出值,顾名思义,debug用的
struct User {username: String,email: String,sign_in_count: u64,active: bool,
}
fn main() {let mut user1 = User {email: String::from("abc@outlook.com"),username: String::from("张三"),active: true,sign_in_count: 455,};//绑定获得所有权//修改值user1.username = String::from("李四");//变量绑定新值所有权user1.active = false;println!("{:?}", user1); //User { username: "李四", email: "abc@outlook.com", sign_in_count: 455, active: false }
}//drop user1

字段和参数同名可简写

#[derive(Debug)] //派生属性,Debug用来格式化输出值,顾名思义,debug用的
struct User {username: String,email: String,sign_in_count: u64,active: bool,
}
fn main() {let user2 = User {email: String::from("abc@gmail.com"),username: String::from("张三"),active: true,sign_in_count: 000,};println!("{:?}", user2); //User { username: "张三", email: "abc@gmail.com", sign_in_count: 0, active: true }
}
fn build_user(email: String, username: String) -> User {User {email,    //等价于email:email,username, //等价于username:username,active: false,sign_in_count: 455,}
}

struct更新语法,创建部分字段相同的新实例,只需要更新不同的字段

#[derive(Debug)] //派生属性,Debug用来格式化输出值,顾名思义,debug用的
struct User {username: String,email: String,sign_in_count: u64,active: bool,
}
fn main() {let user2 = User {email: String::from("abc@gmail.com"),username: String::from("张三"),active: true,sign_in_count: 000,};println!("{:?}", user2); //User { username: "张三", email: "abc@gmail.com", sign_in_count: 0, active: true }let user3 = User {username: String::from("王五"),..user2};println!("{:?}", user3); //User { username: "王五", email: "abc@gmail.com", sign_in_count: 0, active: true }
}
fn build_user(email: String, username: String) -> User {User {email,    //等价于email:email,username, //等价于username:username,active: false,sign_in_count: 455,}
}

Tuple struct 元组结构体

  • Tuple struct整体有名,字段无名
struct Color_RGB(u8,u8,u8)
fn main(){let blue = Color_RGB(0,111,255);
}

Unit-Like struct 无字段结构体

()单元类型相似

struct Marker;

结构体与所有权

#[derive(Debug)] //派生属性,Debug用来格式化输出值,顾名思义,debug用的
struct Rectangle {width: u32,height: u32,
}
fn main() {let rect = Rectangle {width: 30,height: 50,}; //变量绑定println!("{}", area(&rect)); //1500println!("{:#?}",&rect);//Rectangle { width: 30, height: 50 }
}
fn area(rect: &Rectangle) -> u32 {//借用所有权rect.width * rect.height
}

解构

tuple解构

fn main() {let tuple = (1, "hello", 3.5);// 解构元组let (x, y, z) = tuple;println!("x: {}, y: {}, z: {}", x, y, z); //x: 1, y: hello, z: 3.5
}

struct解构

struct Point {x: i32,y: i32,
}
fn main() {let point = Point { x: 10, y: 20 };// 解构结构体let Point { x, y } = point;println!("x: {}, y: {}", x, y);//x: 10, y: 20
}

enum解构

enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}
fn main() {let msg1 = Message::Move { x: 10, y: 20 };let msg2 = Message::Write(String::from("Hello, Rust!"));let msg3 = Message::ChangeColor(255, 0, 0);let msg4 = Message::Quit;match msg1 {Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),_ => (),//匹配失败返回单元类型}match msg2 {Message::Write(text) => println!("Write message: {}", text),_ => (),}match msg3 {Message::ChangeColor(r, g, b) => println!("Change color to r: {}, g: {}, b: {}", r, g, b),_ => (),}match msg4 {Message::Quit => println!("Quit message received"),_ => (),}
}

方法

  • 方法和函数类似:fn关键字、函数名、参数、返回值
  • 方法在struct(或enum、trait对象)的上下文定义
  • 方法第一个参数是(&self),表示调用方法的实例,也可以是可变借用(&mut self),也可以不使用借用直接获得所有权(self)
  • impl块里定义方法
#[derive(Debug)]
struct Rectangle {width: u32,height: u32,
}
impl Rectangle {fn area(&self) -> u32 {//借用调用该方法的实例的所有权self.width * self.height}
}
fn main() {let rect = Rectangle {width: 30,height: 50,}; //变量绑定println!("{}", rect.area()); //1500println!("{:#?}", &rect); //Rectangle { width: 30, height: 50 }
}

方法签名

  • 在调用方法时,Rust会根据情况自动添加&&mut*(解引用)以便Object匹配方法的签名
struct Point {x: f64,y: f64,
}
fn distance(p1: &Point, p2: &Point) -> f64 {let dx = p2.x - p1.x;let dy = p2.y - p1.y;let squared_distance = dx * dx + dy * dy;squared_distance.sqrt()
}
fn main() {let mut point1 = Point { x: 0.0, y: 0.0 };let mut point2 = Point { x: 3.0, y: 4.0 };let mut dist = distance(&point1, &point2);println!("两点间的距离: {}", dist);//两点间的距离: 5//等价于println!("两点间的距离: {}",&dist);//两点间的距离: 5//等价于println!("两点间的距离: {}",*&dist);//两点间的距离: 5//等价于println!("两点间的距离: {}",&mut dist);//两点间的距离: 5
}

关联函数

  • impl块里定义不把self作为第一个参数的函数叫关联函数,如:String::from()
  • 关联函数常用于构造器
  • 每个struct允许拥有多个impl

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • C++:cv.contourArea()函数解析
  • 前端视角下的Spring-Boot语法学习:打印 hello-world
  • 楼梯导航案例
  • 基于vue的引入登录界面
  • Vue-插值表达式
  • 跟《经济学人》学英文:2024年6月29日这期 A new lab and a new paper reignite an old AI debate
  • 光敏电阻,光敏三极管,光敏二极管的作用与区别
  • 第一章节:HTML 基础
  • 生物素结合金纳米粒子(Bt@Au-NPs ) biotin-conjugated Au-NPs
  • GNU/Linux - 如何编译kernel
  • 为什么要进行垃圾回收器的调优
  • ggplot2绘图点的形状不够用怎么办?
  • django基于个人BMI的健康饮食食谱推荐系统-计算机毕业设计源码26624
  • [Debugger]调试Arm设备
  • nginx配置代理
  • Akka系列(七):Actor持久化之Akka persistence
  • CEF与代理
  • es6要点
  • Fastjson的基本使用方法大全
  • jquery ajax学习笔记
  • MaxCompute访问TableStore(OTS) 数据
  • node和express搭建代理服务器(源码)
  • PHP 程序员也能做的 Java 开发 30分钟使用 netty 轻松打造一个高性能 websocket 服务...
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 记一次删除Git记录中的大文件的过程
  • 理清楚Vue的结构
  • 前言-如何学习区块链
  • 网页视频流m3u8/ts视频下载
  • 一个JAVA程序员成长之路分享
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • 译米田引理
  • 正则学习笔记
  • 7行Python代码的人脸识别
  • ​经​纬​恒​润​二​面​​三​七​互​娱​一​面​​元​象​二​面​
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • # 数据结构
  • #Linux(Source Insight安装及工程建立)
  • #QT(TCP网络编程-服务端)
  • #知识分享#笔记#学习方法
  • $(selector).each()和$.each()的区别
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (SERIES12)DM性能优化
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (强烈推荐)移动端音视频从零到上手(上)
  • (转)母版页和相对路径
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .NET/C#⾯试题汇总系列:⾯向对象
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .NET6实现破解Modbus poll点表配置文件
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题