Rust入门-05-所有权

所有权(ownership

栈内存(Stack)与堆内存(Heap

  • 按值的接收顺序来存储,按相反的谁徐将他们移除(后进先出)(last in, first out
    • 添加数据叫(压)入栈pushing onto the stack
    • 移出数据叫(弹)出栈popping off the stack
  • 储存在栈内的数据必须拥有已知的固定大小
    • 指针也有固定大小,也可以存放在栈内
  • 向堆放入数据时,要请求一定大小的空间,将该空间标记为已使用,并返回一个表示该位置地址的指针。这个过程叫做在堆上分配内存allocating on the heap),简称为 “分配”(allocating)
  1. 栈和堆的比较
  • 入栈比分配要快
    • 新数据一直在栈的顶端,操作系统不需要寻找新的空间来储存新数据
  • 访问堆上的数据比访问栈上的数据慢
    • 访问堆上的数据时要通过指针访问(间接访问)

所有权规则

  • 每一个值都有一个变量,该变量叫做所有者owner
  • 每个值同时只能有一个所有者
  • 当所有者离开作用域时,这个值将被删除

String 类型

  • 字符串字面量,是不可变的

  • String是第二种字符串类型

    • 上分配

创建String类型

  • 使用from函数 基于字符串字面量来创建 String

    let s = String::from("hello");
    
    • ::)表示fromstring类型下的函数
    • 变量s是可以修改的
    fn main() {
        let mut s = String::from("hello");
        
        s.push_str(", world!"); // push_str() 在字符串后追加字面值    
        
        println!("{}", s); //输出结果是 hello, world!
                           //"{}"是 替换占位符
                           //占位符就是先占住一个固定的位置,等着你再往里面添加内容的符号   
    }
    

    但字符串字面量是不可以修改的

内存与分配

  • 字符串字面值,被直接硬编码进最终的可执行文件中

    • 不可变
    • 快速且高效
  • String类型,为了其可变性,要在堆上分配未知大小的内存

    • 操作系统必须在运行时来请求内存
      • 这步通过String::from来实现
    • 使用String之后,要将其内存返回给操作系统
      • 在有 垃圾回收garbage collectorGC)的语言中, GC 记录并清除不再使用的内存
      • 但没有GC的语言中,需要我们去识别不再使用的内存并调用代码将其返回(显式释放
        • 忘记回收,会浪费内存
        • 过早回收,会出现无效变量(变量非法
        • 重复回收,会出现bug
  • Rust的内存分配:对于某个值来说,当拥有其的变量离开作用域后就会被自动释放

    {
        let s = String::from("hello"); // 从此处起,s 开始有效
    
            // 使用 s
    }                                  // 此作用域已结束,
                                       // s 不再有效
    
    • 当变量s离开作用域时,Rust会自动调用drop函数
变量与数据交互的方式(一):移动(Move

​ 多个变量能够以不同的方式与同一数据交互。

  • 普通例子

    let x = 5;
    let y = x;
    
    • 变量xy都与整数5发生了交互
    • 整数是有已知固定大小的简单值,所以这两个 5 被放入了
  • String 版本

    let s1 = String::from("hello");
    let s2 = s1;
                  //即  let s2 <- s1;
    
    • 将值 "hello" 绑定给 s1String 在内存中的表现形式

      • String由三部分组成(这一组数据存放在

        • 存放字符串内容的内存的指针
        • 长度(len存放字符串内容所需的字节数)(即实际存的内存字节数
        • 容量(capacity是指String从操作系统总共获得内存的总字节数)(最多能存的内存字节数
      • 存放字符串内容的内存部分(也为String的一部分)在

    (云传)

    • 当把s1赋给s2时,String的数据复制了一份

      • 上复制了一份指针、长度、容量
      • 并没有复制指针所指向堆上的数据

      (云传)

    • 当变量离开作用域时,Rust会自动调用drop函数,将变量使用的堆内存释放

      s1s2离开作用域时,它们会尝试释放相同的内存

      • 二次释放double free)bug(可能会是某些正在使用的数据发生损坏)
    • 为了确保内存安全,在 let s2 = s1 之后,Rust 认为 s1 不再有效

    let s1 = String::from("hello");
    let s2 = s1;    //此时 s1 已经失效
                    //
    println!("{}, world!", s1);
    

    ​ 该代码不能运行

    • 相当于将s1的值移动给了s2
      • 之后s2离开作用域时,会释放自己的内存

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

​ 确实需要复制String上的数据,可以使用clone通用函数的方法(clone方法

  • eg

    let s1 = String::from("hello");
    let s2 = s1.clone();                   //堆上的数据也被复制了
    
    println!("s1 = {}, s2 = {}", s1, s2);  //s1没有失效
    

    该代码能正常运行

  • 对于在栈上的数据:拷贝(Copy)

    let x = 5;           //x为整数类型(整形),编译时就确定了自己的大小,并储存在栈上
    let y = x;           //所以拷贝其的值是快速的,也不会使x失效
                         //此时调用clone函数与浅拷贝没有什么不同
    println!("x = {}, y = {}", x, y);
    

    该代码能正常运行

  • Copy trait (接口)可以用在类似整型这样的存储在栈上的类型上

    • 如果一个类型实现了 Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用
    • 如果一个类型或该类型的一部分实现了Dorp trait ,那么Rust不允许让其再去实现Copy trait(会报错
  • 可以实现Copy trait 的类型

    • 任何简单标量的组合类型可以实现
    • 任何需要分配内存或某种资源的不可以
      • 所有整数类型,比如 u32
      • 布尔类型,bool,它的值是 truefalse
      • 所有浮点数类型,比如 f64
      • 字符类型,char
      • 元组,当其中所包含的字段都可以Copy
        • 比如,(i32, i32) 实现了 Copy
        • (i32, String) 就没有

所有权和函数(所有权的转移

  • 将值传递给函数在语义上与给变量赋值相似。

    • 向函数传递值可能会移动或者复制,就像赋值语句一样。
    fn main() {
      let s = String::from("hello");  // s 进入作用域
    
      takes_ownership(s);             // 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 移出作用域。不会有特殊操作
    

    即所有权发生了转移,从main函数转移到了子函数,此时由子函数负责释放

返回值与作用域

  • 返回值也可以转移所有权

    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 移出作用域,s3无效
      // s2 也移出作用域,但其所有权当作返回值被移动给了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 并移出给调用的函数
    }
    

    即 s2 进入main函数作用域 —> 移动到 takes_and_gives_back()函数 —> a_string 作为函数返回值 —> 移动给 s3

    即 s2 的所有权被移走又移进来

    其 s2 的所有权移动到了 s3

    所以 s2 离开作用域时,因为其所有权移动到了 s3 ,且 s3 先移出了作用域并使 s3 失效,所以 s2离开时,s2 没有发生事情

  • 变量的所有权总是遵循相同的模式:

    • 将值赋给另一个变量时移动其所有权
    • 当持有中数据值的变量离开作用域时,其值将通过 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)
    
    }
    

    即将 s1 传递为函数参数,再作为函数结果进行了返回,s1 的所有权传递给了 s2

    即 calculate_length(s: String) 函数只取得了 s1 的字符长度,没有获得 s1 的所有权(将其所有权交给了 s2

posted @ 2022-04-29 20:10  ragworm  阅读(63)  评论(0)    收藏  举报