5.所有权
标题
- 一、概念
- 二、规则
- 三、示例
- 3.1 变量作用域
- 3.2 所有权的移交(深拷贝与浅拷贝)
- 3.3 函数与所有权
- 3.4 返回值与作用域
- 3.5 引用的使用
- 四、切片(&str)
一、概念
- 所有权是Rust的核心特性。
- 所有程序在运行时都必须管理它们使用计算机内存的方式。Rust的内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则。
- 在Rust中,一个值是在栈还是堆上对语言的行为和为什么要做某些决定是有更大的影响的。
- Rust通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。
二、规则
- Rust 中的每一个值都有一个被称为其所有者(owner)的变量;
- 值在任一时刻有且只有一个所有者;
- 当所有者(变量)离开作用域,这个值将被丢弃;
- 函数参数的传递也会造成所有权的转移;
- 使用引用可以只使用变量而不转移所有权;
- 一个引用的作用域从声明的地方开始一直持续到最后一次使用为止;
三、示例
3.1 变量作用域
- 下面的变量x超出了{}的作用域范围,打印时报错cannot find value x in this scope
- hello变量自动在堆内存中申请了空间,并且初始化为hello,等出了作用域(倒数第二个“}”号)后自动调用drop函数释放内存。
fn main() { { let x = 3; } println!("x = {}", x); //cannot find value `x` in this scope { let hello = String::from("hello"); println!("hello = {}", hello ); } }
3.2 所有权的移交(深拷贝与浅拷贝)
- 在堆上申请的内存,会在连续赋值的时候进行内存所有权的移交。
- 可以使用clone函数进行堆内存的深拷贝
fn main() { let x = 5; let y = x; //栈内存,没有任何影响 let h = String::from("HelloWorld!"); let l = h; //已经进行了所有权的移交,h已不存在 //let l = h.clone(); //可以使用clon()函数重新申请空间 //println!("h = {}",h); //error: value borrowed here after move(clone除外) println!("l = {}",l); }
3.3 函数与所有权
将值传递给函数在语义上与给变量赋值相似。所有权转移的规则也相同
fn main() { let s = String::from("Hello"); take_ownership(s); //这里s发生了转移 let x = 5; makes_copy(x); //栈上的变量x不受所有权影响 println!("{} {}", x, s); //s的所有权在take_ownership里,因此这里无法打印 } fn take_ownership(src: String){ println!("{}", src); } fn makes_copy(src: i32){ println!("{}", src) }
3.4 返回值与作用域
返回值可以把内存空间的所有权返回
fn main() { let s1 = gives_ownership(); //来源于gives_ownership中的some_string let s2 = String::from("hello"); let s3 = takes_and_gives_back(s2); //通过该函数所有权从s2转移到了s3 println!("{}{}{}", s1, s2, s3); //s2编译报错 } fn gives_ownership() -> String { let src = String::from("hello"); src //返回src的所有权 } fn takes_and_gives_back(a_string: String) -> String{ a_string }
3.5 引用的使用
在参数中使用引用就可以只传递变量而不传递所有权
fn main() { let mut s = String::from("Hello"); alter_string_value(&mut s); //只传递s的值而不转移所有权 println!("{}", s); //s依然有效,输出“Hello,world” } fn alter_string_value(src: &mut String){ //可变引用 src.push_str(",world"); } fn print_string_value(src: &String){ src.push_str(",world"); //不可变引用,不能修改 }
在同一时间,只能有一个对某一特定数据的可变引用,尝试创建两个可变引用的代码将会失败。
fn main() { let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; println!("{}, {}", r1, r2); }
报错信息如下
这个报错说这段代码是无效的,我们不能在同一个作用域内多次将 s 作为可变变量。第一个可变的引用在 r1 中,并且必须持续到在 println! 中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在 r2 中创建另一个可变引用,它引用了与 r1 相同的数据。
这样做是为了避免数据竞争,数据竞争由三个行为造成:
- 两个或更多指针同时访问同一数据。
- 至少有一个指针被用来写入数据。
- 没有同步数据访问的机制。
禁止同时使用可变与不可变引用
fn main() { let mut s = String::from("hello"); let r1 = &s; // 没问题 let r2 = &s; // 没问题 let r3 = &mut s; // 大问题 println!("{}, {}, and {}", r1, r2, r3); }
改成下面这样就行了,依然是作用域的问题。
fn main() { let mut s = String::from("hello"); let r1 = &s; // 没问题 let r2 = &s; // 没问题 println!("{} {}", r1, r2); let r3 = &mut s; // 没问题 println!("{}", r3); }
四、切片(&str)
- 切片(slice)允许引用集合中一段连续的元素序列,而不用引用整个集合;
- 字符串字面量就是切片,因此它是不可变的;
- 可以采用字符串切片&str作为参数类型,因此这样就可以同时接收String和&str类型的参数了;
- 定义函数时使用字符串切片代替字符串引用会使我们的API更加通用,且不会损失任何功能;
切片示例
fn main() { let s = String::from("hello world!"); let hello = &s[0..5]; //hello,取0到4字符, 也可以写成&s[..5] let world = &s[5..]; //world,取6到最后 let whole = &s[..]; //整个字符串 // s.clear(); println!("*{}*",hello); //*hello* println!("*{}*",world); //* world!* println!("{}", whole); //hello world! }
函数示例
fn main() { let s = String::from("hello world!"); let wordIndex = first_world(&s[..]); //使用完整的切片 println!("wordIndex = {}", wordIndex); let my_string_literal = "hello world"; let wordIndex = first_world(&my_string_literal); println!("wordIndex = {}", wordIndex); } //获得第一个单词 fn first_world(s: &str) -> &str { let bytes = s.as_bytes(); //转换为字节序 for (index, &item) in bytes.iter().enumerate(){ if item == b' ' { return &s[..index]; } } &s[..] }
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。