【百例RUST - 005】所有权和切片

一、所有权的基础

第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)。

相关推荐
2301_765703141 天前
C++代码复杂度控制
开发语言·c++·算法
m0_708830961 天前
C++中的享元模式实战
开发语言·c++·算法
naruto_lnq1 天前
分布式计算C++库
开发语言·c++·算法
惊讶的猫1 天前
多线程同步问题及解决
java·开发语言·jvm
wfsm1 天前
工厂模式创建动态代理实现类
java·开发语言
好好研究1 天前
总结SSM设置欢迎页的方式
xml·java·后端·mvc
m0_706653231 天前
模板编译期排序算法
开发语言·c++·算法
历程里程碑1 天前
Linxu14 进程一
linux·c语言·开发语言·数据结构·c++·笔记·算法
不当菜虚困1 天前
windows下HSDB导出class文件报错【java.io.IOException : 系统找不到指定的路径。】
java·开发语言
lsx2024061 天前
Vue.js 循环语句
开发语言