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

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

Stack&Heap 栈内存和堆内存

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

所有权

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

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

rust 复制代码
fn main(){
    //s不可用
    let s = "Hello";//s可用

}//s作用域结束,s不可用

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

rust 复制代码
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变量

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

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

rust 复制代码
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的新数据的所有权

rust 复制代码
fn main() {
    let s1 = String::from("Hello");
    let s2 = s1.clone();
    println!("{}", s1);//Hello
    println!("{}", s2);//Hello
}

Copy trait 复制特质

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

如:

rust 复制代码
integer:u8、i32
bool
char
float:f32、f64
Tuple:如果其中所有字段都是固定大小的可以Copy的,这个元组也是可以Copy的

所有权与函数

rust 复制代码
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
}

返回值与作用域

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

rust 复制代码
fn main() {
    let s1 = gives_ownership();//将world所有权move到s1
    println!("{}",s1);//world
    let 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)

引用与借用

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

rust 复制代码
fn main() {
    let s1 = String::from("hello"); //将hello绑定到s1
    let 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

rust 复制代码
fn main() {
    let mut s1 = String::from("hello"); //将hello绑定到s1
    let 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次(避免数据竞争)
  • 不可变引用在同一作用域可以借用多次
  • 不可以在同一作用域同时借用一个可变引用和一个不可变引用

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

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

slice切片

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

  • 字符串切片的范围索引必须发生在有效的UTF-8字符边界内
  • slice是对String类型数据的借用,不获得所有权
rust 复制代码
fn main() {
    let s = String::from("hello world");
    //slice
    let 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类型,可以借用一个完整的切片调用该函数(函数可以被切片类型复用)
rust 复制代码
fn main() {
    let a_string = String::from("hello world");//String类型
    let wordIndex = first_world(&a_string[..]);//字符串切片
    println!("{}", wordIndex);//hello
    let b_string = "hello rust";//本质是切片&str
    let 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 数组切片

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

还可以对切片切片

rust 复制代码
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是自定义的数据类型
  • 变量绑定顺序和声明的顺序可以不一样
rust 复制代码
#[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的实例是可变的,那么实例中的所有字段都是可变的
rust 复制代码
#[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

字段和参数同名可简写

rust 复制代码
#[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更新语法,创建部分字段相同的新实例,只需要更新不同的字段

rust 复制代码
#[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整体有名,字段无名
rust 复制代码
struct Color_RGB(u8,u8,u8)
fn main(){
    let blue = Color_RGB(0,111,255);
}
rust 复制代码

Unit-Like struct 无字段结构体

()单元类型相似

rust 复制代码
struct Marker;

结构体与所有权

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

解构

tuple解构

rust 复制代码
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解构

rust 复制代码
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解构

rust 复制代码
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块里定义方法
rust 复制代码
#[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()); //1500
    println!("{:#?}", &rect); //Rectangle { width: 30, height: 50 }
}

方法签名

  • 在调用方法时,Rust会根据情况自动添加&&mut*(解引用)以便Object匹配方法的签名
rust 复制代码
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
相关推荐
小冷coding20 小时前
【MySQL】MySQL 插入一条数据的完整流程(InnoDB 引擎)
数据库·mysql
Elias不吃糖21 小时前
Java Lambda 表达式
java·开发语言·学习
guygg8821 小时前
一级倒立摆MATLAB仿真程序
开发语言·matlab
情缘晓梦.21 小时前
C语言指针进阶
java·开发语言·算法
鲨莎分不晴21 小时前
Redis 基本指令与命令详解
数据库·redis·缓存
专注echarts研发20年1 天前
工业级 Qt 业务窗体标杆实现・ResearchForm 类深度解析
数据库·qt·系统架构
世转神风-1 天前
qt-字符串版本与数值版本互转
开发语言·qt
极客代码1 天前
深入解析C语言中的函数指针:原理、规则与实践
c语言·开发语言·指针·状态机·函数·函数指针
w-w0w-w1 天前
C++模板参数与特化全解析
开发语言·c++