• Rust认识所有权(4)


    1.认识所有权

    • 所有权(系统)是Rust最为与众不同的特性,它让Rust无需垃圾回收器,即可以保证内存安全。

    2.什么是所有权?

    2.1程序运行管理运行的方式

    • 一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存
    • 一些语言中,开发者必须亲自分配释放内存
    • Rust中,通过所有权系统管理内存编译器在编译时会更加一些列规则进行检查;在运行时,所有权系统的任何功能都不会减慢程序

    2.2栈(Stack)和堆(Heap)

    • 栈和堆都是代码在运行时可供使用的内存
    1.栈(Stack)
    • 栈以放入值的顺序存储值并以宪法顺序取出值,也称为先进后出
    • 栈中的所有数据都必须占用已知固定的大小
    • 入栈比堆上分配内存要快,入栈时分配器无需为存储新数据去搜索内存空间,其位置总是在栈顶
    2.堆(Heap)
    • 在编译时大小未知或大小可能变化的数据,要改为存储在堆上
    • 堆时缺乏组织的,当向堆放入数据时,需要请求一定大小的空间
    • 内存分配器在堆的某处知道到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针
    • 访问堆上的数据比访问栈上的数据慢,必须通过指针来访问

    2.3所有权规则

    • Rust中的每一个值都有一个被称为其所有者的变量
    • 值在任意时刻有且只有一个所有者
    • 当所有者离开作用域,这个值将被丢弃

    2.4变量作用域

    • 作用域是一个项(item)在程序中的有效范围
    fn main(){						//str在这里无效,它尚未声明
    	let str = "生活很美好";		//从此处起,str开始有效
    	
    }								//此作用域已经结束,str不再有效
    
    • 1
    • 2
    • 3
    • 4

    2.4String类型

    • let str = "hello"let str = String::from("hello")的区别
      • let str = "hello"
        • 这种方式创建的字符串是静态分配的,存储在程序可执行文件中,并且在运行时不可变
        • 优势
          • 性能高效:因为字符串时静态分配的,不需要额外的内存分配和释放操作
          • 编译时检查:编译器可以在编译时检查字符串的有效性,避免一些运行时错误
      • let str = String::from("hello")
        • 优势
          • 动态大小: 可以根据需要动态增加或减少字符串的长度
          • 可变性: 可以修改字符串的内容
          • 动态分配: 适用于需要在运行时构建字符串的情况
    fn main(){
    	let str = "hello";
    	
    	let mut str1 = String::from("hello");
    	str1.push_str(",Tom");
    	println!("{}",str1);// 输出 hello,Tom
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.5内存与分配

    • 针对字符串字面量,在编译时就知道内容,文本被直接硬编码进最终的可执行文件中,快速且高效,但是字符串字面量不可变
    • 对于String类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容
      • 必须在运行时向内存分配器请求内存
      • 需要一个当我们处理完String时将内存返回给分配器的方法
    1.以String类型为参考
    • 当调用String::from(" "),它的实现请求其所需的内存
    • 释放内存
      • 有垃圾回收机制的语言中,GC记录并清除不再使用的内存
      • 没有GC的话,识别除不再使用的内存并调用代码显式释放
      • Rust采用了一个不同的策略: 内存在拥有它的变量离开作用域后,就被自动释放
      /*
      	当变量离开作用域,rust为我们调用一个特殊函数,这函数叫做drop自动释放内存
      */
      fn main(){
      	let str = String::from("hello");	//从此起,str开始有效
      	//使用str
      	
      }										//此作用域已结束,str不在有效
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    2.变量与数据交互的方式(一): 移动
    • 在rust中,多个变量可以采取不同的方式与同一数据进行交互
    let x = 20;
    let y = x;
    
    • 1
    • 2
    • 将20绑定到x,接着生成一个值x的拷贝并绑定到y,变量 x 和 y 都等于20
    • 整数是已知固定大小的简单值,两个20都被放入栈中
    2.1String版本
    let str = String::from("hello");
    let str1 = str;
    
    • 1
    • 2

    在这里插入图片描述

    • 一个指向存放字符串内容内存的指针一个长度一个容量,这一组数据存放在栈上
    • 右侧则是堆上存放的内容的内存部分
    • 并且str赋值给str1,只拷贝了它的指针、长度和容量,并没复制指针指向的堆上数据
    fn main() {
        let str = String::from("自强不息");
        let str1 = str;
        // println!("{}", str);// error
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 两个数据指针指向同一个位置,有一个内存安全问题
      • strstr1 离开作用域,它们都会尝试释放相同的内存,两次释放相同内存会导致内存污染
      • 为了确保内存安全,在执行let str1 = str;之后,Rust认为str不再有效
    3.变量与数据交互的方式:克隆
    • 需要深度复制String中堆上的数据,而不仅仅是栈上的数据
    • 需要使用到一个clone的通用函数
    fn main() {
        let str = String::from("自强不息");
        let str1 = str.clone();
        println!("str = {},str1 = {}", str, str1); // str = 自强不息,str1 = 自强不息
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 非必要时,不要轻易使用这种方式,会消耗代码的资源
    4.只在栈上的数据: 拷贝
    • rust中,存放在栈中的数据,拷贝其实际的值是最快速的。

    2.5所有权与函数

    fn main() {
        let str = String::from("自强不息"); //str进入作用域
        takes_ownership(s); // str的值移动到函数里面,所以这里不再有效
    
        let num = 20; // num进入作用域
        makes_copy(20); // num移动函数里面,但i32是拷贝,所以在后面可继续使用x
    } // num移除作用域,str的值已被移走
    fn takes_ownership(some_string: String) {
        // some_thing 进入作用域
        println!("{}", some_string);
    } // some_thing移除作用域并调用 drop 方法
    fn makes_copy(some_interger: i32) {
        // some_integer进入作用域
        println!("{}", some_interger);
    } // some_integer移除作用域
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.6返回值与作用域

    fn main() {
        let str = String::from("自强不息");
        let (str1, len) = calculate_length(str);
        println!("The length of {} is {}", str1, len); // The length of 自强不息 is 12
    }
    fn calculate_length(str: String) -> (String, usize) {
        let length = str.len();
        (str, length)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.引用与借用

    • 引用像一个指针,它是一个地址
    • 访问储存于该地址的属于其他变量的数据,与指针不同,引用确保指向某个特定类型的有效值
      在这里插入图片描述
    fn main() {
        let str = String::from("自强不息");
        let len = calculate_length(&str);
        println!("The length of {} is {}", str, len); // The length of 自强不息 is 12
    }
    fn calculate_length(str: &String) -> usize {
        let length = str.len();
        length
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • &str 语法让我们创建一个指向值str的引用,但是并不拥有它,所以当引用停止使用时,它所指向的值也不会被丢弃

    • 创建一个引用的行为称为借用,只有使用权限,没有拥有权限借用变量不可修改

    3.1可变引用

    • 允许修改一个借用的值,就是可变引用
    fn main() {
        let mut str = String::from("天行健,君子以");
        change(&mut str);
        println!("{}", str)
    }
    fn change(some_thing: &mut String) {
        some_thing.push_str("自强不息!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1.可变引用的限制
    • 创建一个可变引用,我们就不能再创建对变量的引用
    fn main() {
        let mut str = String::from("天行健,君子以");
        let str1 = &mut str;
        // let str2 = &mut str; //error
        // println!("{},{}", str1, str2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 防止同一时间对同一数据存在多个可变引用,限制的好处就是Rust可以再编译时就避免数据竞争,数据竞争类似于竞态条件,它由以下行为造成
      • 两个或更多指针同时访问同一数据
      • 至少有一个指针被用来写入数据
      • 没有同步数据访问的机制
    1.1允许多个可变引用-作用域
    fn main(){
    	let mut str = String::from("自强不息");
    	{
    		let str1 = &mut str;
    	}
    	let str2 = &mut str;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1.2不可变引用和可变引用
    • 不能在拥有不可变引用的同时拥有可变引用
    • 多个不可变引用是可以的
    • 不可变引用和可变引用同时存在的场景
      • 不可变引用str1str2的作用域在println!最后一次使用之后结束
      • 可变引用str3创建和str1 & str2作用域没重叠
    fn main(){
    	let mut str = String::from("自强不息");
    	let str1 = & str;
    	let str2 = & str;
    	println!("{},{}",str1,str2);// 自强不息,自强不息
    	let str3 = &mut str;
    	println!("{}",str3);//自强不息
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.2悬垂引用

    • 具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个垂悬指针
    • 垂悬指针是其指向的内存可能已经被分配给其他持有者
    • 在rust中,编译器确保引用永远不会变成垂悬指针,编译器确保数据不会在其引用之前离开作用域
    fn main(){
    	let reference_to_nothing = dangle();
    
    }
    
    /*
     // error
    fn dangle() -> &String{
    	let str = String::from("自强不息");
    	&str //返回字符串str的引用
    }// 这里str离开作用域并被丢弃,其内存释放
    
    */
    
    fn dangle() ->String{
    	let str = Stirng::from("自强不息");
    	str
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.Slice类型

    fn main() {
        let mut str = String::from("hello world");
        let word = first_word(&str);
        println!("{}", word); // 5
        str.clear();
    }
    fn first_word(str: &String) -> usize {
        let bytes = str.as_bytes();
        for (i, &item) in bytes.iter().enumerate() {
            // b' ',表示字节字面量,它表示空格字符
            if item == b' ' {
                return i;
            }
        }
        str.len()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.1字符串slice

    • 字符串sliceString中一部分值的引用
    let str = String::from("hello world");
    let hello = &str[0..5];
    let world = &str[6..11];
    println!("{}",world);//world
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    fn main() {
        let mut str = String::from("hello world");
        let hello = first_word(&str[..]);
        //str.clear();// clear需要清空String,尝试获取一个可变引用,而hello使用了不可变引用
        println!("The value of is {}", hello);
    }
    fn first_word(str: &str) -> &str {
        let bytes = str.as_bytes();
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return &str[0..i];
            }
        }
        &str[..]
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    4.1字符串字面值就是slice
    • 字符串字面值被存储在二进制文件中
    let str = "hello world";
    
    • 1
    • 这里的str类型就是&str,它指向二进制程序特定位置的slice
    4.2字符串slice作为参数
    fn first_world(st: &str) -> &str{}
    
    • 1

    4.2其他类型的slice

    let arr = [1,2,5,1,6,8];
    let slice = &arr[1..3];
    assert_eq!(slice,&[2,5]);
    
    
    • 1
    • 2
    • 3
    • 4
    • slice的类型是&[i32]
  • 相关阅读:
    【HarmonyOS】eTS开发是否支持在data/ethernet/创建文件
    CSS之复合选择器与伪类选择器
    谈谈HTTP协议的方法里,GET和POST的区别?我来教你如何回答~
    基于JAVA天津城建大学校友录管理系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署
    setState同步异步场景
    于51单片机的安全带(拉力,高度,紧急)原理图、流程图、物料清单、仿真图、源代码
    一文读懂MP4封装格式
    MySQL—一条查询SQL语句的完整执行流程
    Visual Studio复制、拷贝C++项目与第三方库配置信息到新的项目中
    120G课程内容!龙讯旷腾为您的课题组打造专属空间
  • 原文地址:https://blog.csdn.net/qq_39656068/article/details/132836578