美文网首页Rust 学习笔记
Rust基础学习-06-所有权、作用域

Rust基础学习-06-所有权、作用域

作者: 一个游戏开发者 | 来源:发表于2019-10-10 13:05 被阅读0次

    本篇博客我们来解释几个名词,作用域所有权所有权移动

    栈是在代码运行时,可供使用的一块内存。它的存取数据方式是先进后出,或者说后进先出。想象有一个箱子,你往里放本子,最先放入的本子,是在箱子底下,当你要使用本子时,总是从顶上取一个使用,也就是取最后放入的一个本子。
    因为这种存取数据时总是在栈顶操作,而不需要去内存中寻找一个位置,所以栈的操作是时分迅速的。
    还有一个点是,存在栈里的数据,都是以知的固定大小。这一点的意思是,例如要让用户输入一个名字,因为不知道用户会输入多少字符,所以这个数据就无法放在栈中,因为无法事先知道明确的大小。

    在编译时大小未知或大小可能变化的数据,要改为存储在堆上。堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 分配(allocating)

    作用域

    作用域可以理解为一个东西在程序中的有效范围。对于Rust来说,当一个变量出了作用域后,对应的内存就会自动被释放掉,变量变为无效状态。

    {                      // s 在这里无效, 它尚未声明
        let s = "hello";   // 从此处起,s 是有效的
        // 使用 s
    }                      // 此作用域已结束,s 不再有效
    

    字符串类型 String

    之前在数据类型一节,没有讲到 String,是因为牵扯到堆栈的问题,所以放在这里讲。

    fn main() {
        // 像这种直接硬编码在代码里的字符串,是放在栈上的,并且不可改变
        let name = "Jack";
        
        // 使用String::from创建的,是在堆上分配内存,并且是可以改变的
        let mut my_name = String::from("Jack");
        my_name.push_str(", My name is Jack");
        
        // 输出 Jack, My name is Jack
        println!("{}", my_name);
    }
    

    当调用 String::from 时,它的实现 (implementation) 请求其所需的内存。这在编程语言中是非常通用的。

    所有权

    1. Rust 中每一个值都有一个被称为所有者的变量
    2. 值,有且只有一个所有者
    3. 当所有者(变量)离开作用域时,这个值被丢弃,内存被释放

    移动

    先看下面一段代码

    fn main() {
        let x = 10;
        let y = x;
        println!("x: {}, y:{}", x, y);
        
        let name1 = "Fred";
        let name2 = name1;
        println!("name1: {}, name2: {}", name1, name2);
    }
    

    很正常,最后输出了 x: 10, y:10name1: Fred, name2: Fred

    再看下面这段代码

    fn main() {
        let name1 = String::from("Fred");
        println!("name1: {}", name1);
    
        let name2 = name1;
        println!("name2: {}", name2);
    
        // 编译出错,这句会出错
        println!("name1 again: {}", name1);
    }
    

    为什么加了最后一句会编译出错呢,这里涉及到一个概念,移动。首先 name1 指向的值是分配在堆上的。当将 name1 赋值 给 name2后,在有一些编程语言,两个变量会指向同一块堆内存区域,但是对于Rust来说,不是这样的,Rust在这里会直接让 name1 失效,避免两个指针指向同一块堆内存。因为 Rust 会自动释放内存,这样可以避免当两个变量超出作用域时,导致重复的内存释放问题。将 name1 赋值给 name2,这个操作叫做移动,name1移动到了name2,移动后,name1自动失效,所以最后一句访问 name1 会编译出错。

    更详细的内容 官方文档

    这里要记住,对于那些固定大小的数据类型,i32, f32, boolchar 等不会存在移动的问题。但是对于存储在上的数据,不管是String还是后面自定义的数据类型,这样的操作都会触发移动

    有没有办法将指上堆内存的变量赋值给另一个变量不触发移动呢?有!方法就是克隆,看下面的代码。

    fn main() {
        let name1 = String::from("Fred");
        println!("name1: {}", name1);
    
        let name2 = name1.clone();
        println!("name2: {}", name2);
    
        println!("name1 again: {}", name1);
    }
    

    和之前的代码只有第5行变了,当调用了 clone()函数后,会导致 name1 指向的堆上的内存复制一份。所以这里就没有移动。String内部实现了 clone(),当我们自定义数据结构时,如果要有克隆功能,需要自己实现 clone()方法。这个后面会讲到。

    移动与函数

    说完了移动,就需要说一下移动和函数相关的东西。如果将一个值作为参数,去调用一个函数,如果这个值是在栈上,那么不会发生什么,但是如果这个值是分配在堆上,那么它会移动到函数内部。

    看下面的代码(注意看代码的注释)

    fn main() {
        let name1 = String::from("Fred");
        println!("name1: {}", name1);
    
        // name1 的值移动了函数里
        takes_ownership((name1));
    
        // name1 已经无效,这里再使用就会编译出错
        // println!("name1 again: {}", name1);
    
    }
    
    fn takes_ownership(str: String) {
        println!("i have ownership: {}", str);
    }
    

    下面的代码,函数在结束时将 所有权 返回

    fn main() {
        let name1 = String::from("Fred");
        println!("name1: {}", name1);
    
        // 因为name1不是mut的,所以这里的name1相当于创建了一个
        // 新的变量name1, 本质上并不是之前的
        let name1 = takes_and_gives_back(name1);
        println!("name1 again: {}", name1);
    }
    
    fn takes_and_gives_back(str: String) -> String {
        println!("i have ownership: {}", str);
        
        // 这里将值返回,所有权移出函数
        str
    }
    

    使用函数时每次都要转移所有权很繁琐,所下一节将介绍引用,不用每次将所有权转来转去

    相关文章

      网友评论

        本文标题:Rust基础学习-06-所有权、作用域

        本文链接:https://www.haomeiwen.com/subject/rswopctx.html