Rust学习Day4: 所有权、引用和切片介绍
所有权
什么是所有权: 所有权是Rust 用于如何管理内存的一组规则。所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时有规律地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序的运行
**堆:**堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 "分配"(allocating)
**栈:**栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出
作用域:
说明: rust的作用域只在{}内有效,在离开作用域后,会自动调用drop函数来清理变量的堆内存
示例:离开作用域后,就会失效
rust
fn main(){
let s1 = String::form("hello");
let s2 = s1; // 到这里,s1就已经释放了,因为被s2使用更改了,后面将无法使用
//println!("{s1}, world"); // 会报错
println!("{s2},world"); // 可以正常使用
}
赋值
说明: 当你给一个已有的变量赋一个全新的值时,也是可以的,rust会立即调用drop并释放原始值的内存
示例:
rust
fn main(){
let mut s = String::form("hello");
// 重新赋值
s = String::form("world"); // 旧的就会被丢失,然后重新赋值
println!("{s}, world"); // 最终将会输出 world, world
}
深复制(与python的深拷贝一致)
语法: xxx.clone();
rust
fn main(){
let a1 = String::from("hello");
let a2 = a1.clone(); // 克隆a1的值到a2
println!("a1={a1}, a2={a2};"); // 输出 a1=hello, a2=hello; 说明复制成功了
}
部分类型,无需.clone() 也可
rust
fn main(){
let x = 5; // x是一个整数,存储在栈上
let y = x; // y是x的一个副本,x和y都有效
let z = x.clone(); // z也是x的一个副本,x和z都有效
println!("x = {x}, y = {y}, z = {z}"); // 使用x和y和z 都可以正常输出
}
copy的类型:
- 所有整数类型, 如
u32 - 布尔类型,
bool,值是true,false - 所有浮点类型, 比如
f64 - 字符类型,
char - 元组, 当且仅当其包含的类型也都实现
copy的时候,比如 (i32,i32)实现了copy,但(i32,String) 就没有
所有权与函数
说明: 向函数传递值可能会被移动或者复制
示例:
rust
fn main() {
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s 的值移动到函数里 ...
// ... 所以到这里不再有效
let x = 5; // x 进入作用域
makes_copy(x); // x 应该移动函数里,
// 但 i32 是 Copy 的,
println!("{}", x); // 所以在后面可继续使用 x
} // 这里,x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
// 没有特殊之处
fn takes_ownership(some_string: String) { // some_string 进入作用域
println!("{some_string}");
} // 这里,some_string 移出作用域并调用 `drop` 方法。
// 占用的内存被释放
fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{some_integer}");
} // 这里,some_integer 移出作用域。没有特殊之处
返回值与作用域
说明: 返回值也可以被转移所有权
rust
fn main() {
let s1 = gives_ownership(); // gives_ownership 将它的返回值传递给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被传入 takes_and_gives_back,
// 它的返回值又传递给 s3
} // 此处,s3 移出作用域并被丢弃。s2 被 move,所以无事发生
// s1 移出作用域并被丢弃
fn gives_ownership() -> String { // gives_ownership 将会把返回值传入
// 调用它的函数
let some_string = String::from("yours"); // some_string 进入作用域
some_string // 返回 some_string 并将其移至调用函数
}
// 该函数将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String {
// a_string 进入作用域
a_string // 返回 a_string 并移出给调用的函数
}
引用与借用
定义: 引用(reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。与指针不同,引用在其生命周期内保证指向某个特定类型的有效值。 语法关键词&xxx
示例:
rust
fn main(){
let s5 = String::from("hello");
let s5_len = calculate_length_ref(&s5);
println!("s5 = {s5}, s5_len = {s5_len}"); // 输出s5=hello, s5_len=5;
}
fn calculate_length_ref(s: &String) -> unsize {
s.len() // 返回长度
}
可变引用
示例:
rust
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("s = {s}"); // 最终输出将是 hello, world
}
fn change(some_string: &mut String) {
some_string.push_str(", world"); // 可以改变s
}
注意实现:
- 如下,不可同时引用
rust
fn main(){
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2); // 会报错
}
- 如下,可被固定引用
rust
fn main(){
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("r1 = {r1}, r2 = {r2}"); // 可以正确输出
let rs = &mut s; // 也没问题,可以正常使用,不能同时出现 &mut s
}
悬垂引用
示例:悬垂引用,会被rust报错提示
rust
fn main(){
let reference_to_nothing = dangle(); // 试图获取一个悬吊指针,编译器会报错,因为dangle函数返回了一个指向已经离开作用域的值的引用
println!("reference_to_nothing = {reference_to_nothing}"); // 使用reference_to_nothing
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
如果想要解决上面的问题,可以如下修改:
rust
fn main(){
let reference_to_nothing = dangle(); // 试图获取一个悬吊指针,编译器会报错,因为dangle函数返回了一个指向已经离开作用域的值的引用
println!("reference_to_nothing = {reference_to_nothing}"); // 使用reference_to_nothing
}
fn dangle() -> String { // 修改返回jike
let s = String::from("hello");
s
}
Slice类型
定义: 切片(slice)允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一种引用,所以它不拥有所有权。
字符串类型:
示例:
rust
fn main(){
let str_char = String::from("hello world"); // str_char进入作用域
// 字符串切换
let hello = &str_char[0..5]; // hello是str_char的一个字符串切片,包含从索引0到索引5(不包括5)的字符
let world = &str_char[6..11]; // world是str_char的一个
println!("hello = {hello}, world = {world}"); // 使用hello和world
// slice 如果是从0开始也可以不传递,如下,两个输出一样
let slice = &str_char[0..5];
println!("slice = {slice}"); // 使用slice
let slice = &str_char[..5]; // 从索引0开始到索引5(不包括5)
println!("slice = {slice}"); // 使用slice
// 同理,如果是到字符串末尾,也可以不传递,如下,两个输出一样
let slice = &str_char[6..11];
println!("slice = {slice}"); // 使用slice
let slice = &str_char[6..]; // 从索引6开始到字符串末尾
println!("slice = {slice}"); // 使用slice
// 同理,取完整的
let slice = &str_char[0..11];
println!("slice = {slice}"); // 使用slice
let slice = &str_char[..]; // 从索引0开始到字符串末尾
println!("slice = {slice}"); // 使用slice
//
let my_string = String::from("hello world"); // my_string进入作用域
// first_word函数返回my_string的第一个单词的切片
let word = first_word(&my_string[0..6]);
println!("The first word is: {word}"); // 使用word
let word = first_word(&my_string[..]); // 直接传递整个字符串的切片
println!("The first word is: {word}"); // 使用word
let word = first_word(&my_string); // 直接传递整个字符串,函数会自动将其转换为切片
println!("The first word is: {word}"); // 使用word
}
// first_word函数返回字符串的第一个单词的切片
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes(); // 将字符串转换为字节数组
for (i, &item) in bytes.iter().enumerate() { // 枚举字节数组,获取索引和字节值
if item == b' ' { // 如果字节值是空格
return &s[0..i]; // 返回从索引0到索引i(不包括i)的字符串切片
}
}
&s[..] // 如果没有找到空格,返回整个字符串的切片
}
其他类型:
示例:数字类型
rust
fn main(){
// 其他类型的 slice
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // slice是数组a的一个切片,包含从索引1到索引3(不包括3)的元素
println!("slice = {:?}", slice); // 使用slice,使用{:?}来打印切
}
本人正在参考官方文档学习这门语言,如果有描述错误的,欢迎各位RUST大神指出;
