一、所有权的基础
第01节 基础概念
所有权系统有三个基础核心规则
1、 每个值都有一个被称为 其所有者的变量
2、 同一时间只能有一个所有者
3、 当所有者离开作用域时, 这个值将会被丢弃
在 rust 当中, 赋值操作默认会转移所有权
rust
fn main(){
let s1 = String::from("hello"); // s1 拥有了字符串 "hello" 的所有权
println!("{}", s1); // 输出s1的内容
let s2 = s1; // s1 将字符串 "hello" 的所有权, 交付给了 s2
println!("{}", s2); // 输出s2的内容
// println!("{}", s1); // 编译错误! s1 不在有效
}
// 作用域外面, 数据值 "hello" 将会被丢弃
生活案例:
生活中的案例,理解
所有权的概念1、张三拥有一套
hello的房子,目前房产证上印着 张三的名字。 张三是hello房子的所有者2、在某个时间点,张三 将
hello的房子,售卖给了 李四。3、在此之后,房产证上印着的是 李四的名字。 李四 是
hello房子的所有者4、如果张三在已经售卖给李四房子之后,还想作为房子的所有者,进行操作
hello房子,是不被允许的。5、站在
hello房子的角度来看,同一个时间,只能属于其中某个人。 同一个时间点,房子要么属于张三,要么属于李四。不可能同时属于张三和李四6、不管是张三还是李四,只有在自己国家境内,才能有效。在大括号作用域外面,
hello房子不会被认可。
第02节 概念介绍
所有权(Ownership)
1、在 Rust 中,每个值都有一个被称为其 所有者 的变量。
2、一次只能有一个所有者。
3、当所有者离开作用域时,该值将被丢弃。
变量作用域(Variable Scope)
1、作用域是一个变量在程序中有效的范围。
2、当变量的作用域结束时,相关的资源(如内存)会自动被释放。
移动(Move)
1、Rust 默认使用移动语义而不是克隆任何数据。
2、当一个变量赋值给另一个变量时,原始数据的所有权会被移动到新变量。原始变量将不再有效,以防止双重释放内存的风险。
克隆(Clone)
1、如果数据确实需要被多个变量共享,则可以使用数据的 .clone() 方法显式创建数据的副本。
2、这样做会消耗更多的内存因为数据被复制了。
借用(Borrowing)
1、Rust 允许通过引用来借用变量,这不会获取所有权。
2、引用必须遵守 Rust 的借用规则,包括:
A. 不可变借用(&T):允许多个不可变借用,但在借用期间不能修改数据。
B. 可变借用(&mut T):只能有一个可变借用,可以修改数据。
生命周期(Lifetimes)
生命周期是一个引用有效的作用域。
Rust 需要明确指定引用的生命周期以确保引用在使用期间始终有效。
生命周期注解看起来像这样:'a,用于帮助编译器理解引用间的关系和有效性。
第03节 克隆深拷贝
上面的直接赋值的方式,实际上是转移了 所有权。
如果想要深度拷贝数据,而不是转移所有权
rust
fn main(){
let s1 = String::from("hello");
let s2 = s1.clone(); // 创建数据的完整拷贝
println!("s1 = {}, s2 = {}", s1, s2); // 两者都有效
}
生活案例:
原封不动的复制了一套
hello的房子
二、所有权与函数
第01节 所有权函数参数
rust
fn main(){
let s = String::from("hello");
takes_ownership(s); // s的所有权转移到函数内
// println!("{}", s); // 编译错误!s不再有效
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
}
第02节 所有权函数返回
rust
fn main(){
let s = gives_ownership(); // 现在s拥有这个字符串
println!("{}", s);
}
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string // 所有权被转移出来
}
三、借用所有权
第01节 快速理解
案例
rust
// 假设你有一个函数,它需要读取一个数字的值,但并不需要获取这个数字的所有权。这种情况下,你可以使用借用
fn main(){
let number = 10;
print_number(&number);
// 注意: 这里仍然可以使用 number 因为 print_number 函数只是借用了 number
println!("I still have my number: {}", number);
}
fn print_number(x: &i32) {
println!("The number is : {}", x);
}
在这个例子中:
print_number函数接受一个对i32类型的引用(&i32),这意味着它借用了一个i32类型的值。- 在
main函数中,我们创建了一个整数num,然后将num的引用传递给print_number函数。这里使用&num表示对num的借用。- 因为
num被借用而不是被移动,所以在print_number调用之后,我们仍然可以安全地使用num。
第02节 可变借用
案例
rust
// 如果我想要在函数中修改传入的值, 我需要使用可变借用。
fn main(){
let mut number = 10;
increment_number(&mut number);
println!("The incremented number is {}", number);
}
fn increment_number(x: &mut i32){
*x = *x + 1;
}
在这个例子中:
increment_number函数接受一个对i32类型的可变引用(&mut i32),这允许函数修改借用的值。- 在
main函数中,我们创建了一个可变的整数num,然后通过&mut num将其可变借用给increment_number函数。- 使用
*x = *x + 1;语句来增加x指向的值,其中*x是解引用操作符,用于访问引用指向的实际值。
通过这两个例子,你可以看到 Rust 的借用系统如何工作。不可变借用允许你在多个地方安全地读取数据,而不会引起数据竞争。
可变借用则允许你在一个地方修改数据,但在此期间,原始数据不能被其他地方访问或修改,这保证了修改的安全性。
四、切片
第01节 基础概念
基本概念:
Rust 中的切片(slice)是一个引用一个数组的部分元素的数据结构。
1、切片允许你访问数组的一部分而不是整个数组,这在处理数组或字符串的子序列时非常有用。
2、切片是非所有权类型,它们没有拥有它们引用的数据。
相关介绍:
1、切片类型:切片类型表示为 &[T],其中 T 是元素的类型。对于字符串切片,类型是 &str。
2、创建切片:通常通过借用数组的一部分来创建切片。
3、使用场景:切片常用于函数参数,允许函数处理部分数组而不是整个数组。
为什么使用切片?
切片提供了一种高效访问数组或字符串部分内容的方式,而不需要复制数据。
通过使用切片,你可以写出更灵活和内存效率更高的代码。
在 Rust 中,切片的使用还有助于保证代码的安全性,因为切片的操作都会在编译时检查边界,避免运行时错误。
第02节 数组切片
案例代码
rust
// 数组切片的使用
fn main(){
// 定义数组
let array = [11,22,33,44,55];
// 创建一个包含了整个数组的切片
let full_slice = &array[..];
// 输出结果
println!("Full slice: {:?}" , full_slice);
// 创建一个包含数组从 1到3的切片 (不包含索引3)
let partial_slice = &array[1..3];
// 输出结果
println!("Partial slice: {:?}" , partial_slice);
}
在这个例子中:
full_slice是指向整个数组的切片,而partial_slice只引用数组的一部分,从索引1到索引2(不包括3)。1、切片使用范围语法
start..end,其中start是包含的起始索引,end是不包含的结束索引。2、如果省略
start,则默认从0开始;如果省略end,则默认到数组的末尾。
第03节 字符串切片
案例代码
rust
// 字符串切片的使用
fn main(){
// 定义字符串
let str = String::from("Hello world");
// 创建一个包含了整个字符串的切片
let full_slice = &str[..];
// 输出结果
println!("Full slice: {:?}" , full_slice);
// 创建一个包含字符串从 0到5的切片 (不包含索引5)
let partial_slice = &str[0..5];
// 输出结果
println!("Partial slice: {:?}" , partial_slice);
}
在这个例子中:
full_slice是整个字符串的切片,而partial_slice是原字符串的一部分,从索引0到4(不包括5)。