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不再有效

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

}

2.5内存与分配

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

在这里插入图片描述

  • 一个指向存放字符串内容内存的指针一个长度一个容量,这一组数据存放在栈上
  • 右侧则是堆上存放的内容的内存部分
  • 并且str赋值给str1,只拷贝了它的指针、长度和容量,并没复制指针指向的堆上数据
fn main() {
    let str = String::from("自强不息");
    let str1 = str;
    // println!("{}", str);// error
}

  • 两个数据指针指向同一个位置,有一个内存安全问题
    • 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 = 自强不息
}
  • 非必要时,不要轻易使用这种方式,会消耗代码的资源
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移除作用域

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)
}

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
}

  • &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.可变引用的限制
  • 创建一个可变引用,我们就不能再创建对变量的引用
fn main() {
    let mut str = String::from("天行健,君子以");
    let str1 = &mut str;
    // let str2 = &mut str; //error
    // println!("{},{}", str1, str2);
}
  • 防止同一时间对同一数据存在多个可变引用,限制的好处就是Rust可以再编译时就避免数据竞争,数据竞争类似于竞态条件,它由以下行为造成
    • 两个或更多指针同时访问同一数据
    • 至少有一个指针被用来写入数据
    • 没有同步数据访问的机制
1.1允许多个可变引用-作用域
fn main(){
	let mut str = String::from("自强不息");
	{
		let str1 = &mut str;
	}
	let str2 = &mut str;

}

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);//自强不息
}

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
}

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()
}

4.1字符串slice

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

在这里插入图片描述

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[..]
}

4.1字符串字面值就是slice
  • 字符串字面值被存储在二进制文件中
let str = "hello world";
  • 这里的str类型就是&str,它指向二进制程序特定位置的slice
4.2字符串slice作为参数
fn first_world(st: &str) -> &str{}

4.2其他类型的slice

let arr = [1,2,5,1,6,8];
let slice = &arr[1..3];
assert_eq!(slice,&[2,5]);

  • slice的类型是&[i32]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Rust所有权系统是其最显著的特性之一,它通过一系列规则和机制确保内存安全、避免数据竞争和资源泄漏。下面是对 Rust 所有权的详细解释: 1. 所有权规则: - Rust 中的每个值都有一个被称为“所有者”的变量。 - 任何时候,一个值只能有一个所有者。 - 当所有者超出其作用域时,该值将被释放。 2. 移动语义: - 当将一个值赋值给另一个变量时,它的所有权会被移动。 - 移动所有权意味着原始变量将无效,不能再使用它。 - 这样可以防止多个变量同时访问同一个内存,避免了数据竞争。 3. 借用语义: - 如果需要在不移动所有权的情况下访问值,可以使用“借用”机制。 - 借用通过引用来实现,引用允许临时地借用值的所有权。 - 在借用的作用域结束后,原始所有者仍然保持对值的所有权。 4. 可变性: - 默认情况下,变量是不可变的,不能修改其所指向的值。 - 如果需要修改一个值,必须使用 `mut` 关键字声明可变变量。 - 可变引用和不可变引用不能同时存在,以避免数据竞争。 5. 生命周期: - 生命周期是 Rust 的一个重要概念,用于跟踪引用的有效性。 - 生命周期注解可以标识引用的生命周期,以确保引用在其所指向的值仍然有效时使用。 - 生命周期注解在函数签名和结构体定义中使用,以确保引用的正确使用。 通过这些所有权规则和机制,Rust 在编译时静态地检查内存安全性,并防止常见的错误,如空指针引用、野指针、使用已释放的内存等。这使得 Rust 成为一种非常安全且高效的编程语言,特别适合开发系统级软件和并发应用程序。 需要注意的是,所有权系统对于初学者可能会有一定的学习曲线,但一旦掌握了所有权的概念和规则,编写安全且高效的 Rust 代码将变得更加容易。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值