首页 rust

语言基础篇

所有权系统

通用概念

堆内存与栈内存

栈内存的数据大小在编译时已知且固定

但是例如字符串类型的数据在编译时大小是未知的,且运行时大小可能发生变化,所以要储存在堆上

在堆上分配内存就是找一个足够大的内存,标记为已使用,而后返回一个执行该空间地址的指针

数据在入栈时速度被堆分配内存快,访问也一样

所以所有权系统就是要减少堆上重复的数据和清理堆是哪个不再使用的数据,确保垃圾数据不会消耗内存空间

值语义与引用语义

值语义就是那个值,引用语义就是一个指针

浅复制是在在栈上复制,深复制就是对栈上和堆上一起复制

复制语义与移动语义

前者就是值语义,后者就是引用语义,前者在栈上进行按位复制,后者在堆上进深复制,

所有权机制

1)每个值都有一个被称为其所有者的变量,也就是每块内存空间都有其所有者,所有者具有这块内存空间的释放和读写权限。
2)每个值在任一时刻有且仅有一个所有者。
3)当所有者(变量)离开作用域,这个变量将被丢弃。

变量绑定

例如let就是一种绑定语义,将变量与存储这个值的内存空间绑定,从而将变量对这块内存空间拥有所有权,并且Rust不允许两个遍历同事执行同一块内存空间

在变量离开组用于时,Rust就会调用drop函数来释放绑定的内存

所有权转移

每个值在任一时刻有且仅有一个所有者

这是为了避免当两个变量拥有同一个值,但是都释放的时候进行了数据的保留,但是数据已经没有和变量关联了,但是数据没有删除这样的内存问题(主要针对堆上数据)

fn main() {
    let s = String::from("hello");
    take_ownership(s);
    // println!("{}", s); // 这里就不能打印了,因为所有权已经交给了函数参数,s已经无效了
    let x = 5;
    make_copy(x);
    println!("{}", x); // 这里就可以调用,因为是栈上数据复制给了函数参数,并没有进行转移
}

fn take_ownership(str: String) {
    println!("{}", str);
}

fn make_copy(int: i32) {
    println!("{}", int);
}

记住:字符串字面量的长度在设置的时候是固定的(&str),但是String是不固定的,所以前者存储在栈上,后者储存在堆上

但是如果参数是一个值而不是指针就不会出现上述情况

use std::collections::HashMap;

fn main() {
    let key = String::from("Favorite");
    let value = String::from("Red");
    
    let mut map = HashMap::new();
    map.insert(&key, &value);
    println!("{}", key);
}

这样就不会出问题了

还有一个解决方法:将所有权作为返回值返回给原本的值

fn main() {
    let s1 = give_ownership();
    let s2 = take_and_give_back(s1);

    // println!("{}", s1); // s1的所有权已经给了函数
    println!("{}", s2);
}

fn give_ownership() -> String {
    let str = String::from("ownership");
    str
}

fn take_and_give_back(name: String) -> String {
    let hello = String::from("hello");
    hello + " " + &name
}

浅复制与深复制

一个元组类型,如果每个元素都实现了Copy trait,那么该元组支持浅复制

但是结构体和枚举不行,需要给标记

#[derive(Debug)]
struct Foo {
    x: i32,
    y: bool,
}

#[derive(Debug, Copy, Clone)]
struct Foo1 {
    x: i32,
    y: bool,
}

fn main() {
    let foo = Foo{ x: 8, y: true };
    let other = foo;
    // println!("{:?}", foo); // 这里就会报错
    let foo1 = Foo1 {x: 8, y: true };
    let other1 = foo1;
    println!("{:?}", foo1);
}

不过就算给标记了,该不能复制的还是不能复制,说的就是String

但是有一个clone的方法可以进行深复制,不过这是创建了一个新的被指向的值

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{}, {}", s1, s2);
}

引用与和借用

就是指针,分为不可变引用与可变引用,区别就是加不加mut

fn main() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec![4, 5, 6];
    let (v1, v2, answer) = sum_vec(vec1, vec2);
    println!("{:?}, {:?}", v1, v2);
}

fn sum_vec(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
    let sum1: i32 = v1.iter().sum();
    let sum2: i32 = v2.iter().sum();

    (v1, v2, sum1 + sum2)
}

可以进行这个所有权进去再回来

也可以给一个指针

fn main() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec![4, 5, 6];
    let answer = sum_vec(&vec1, &vec2);
    println!("{:?}, {:?}", vec1, vec2);
}

fn sum_vec(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
    let sum1: i32 = v1.iter().sum();
    let sum2: i32 = v2.iter().sum();

    sum1 + sum2
}

这里的引用默认是只读的如果是&mut是可变的的

但是要保证变量本身是可变的,函数参数是&mut,传递的实参也是可变的

fn main() {
    let mut vec = Vec::new();
    push_vec(&mut vec, 1);
}

fn push_vec(v: &mut Vec<i32>, value: i32) {
    v.push(value);
}

还有*的使用:

fn main() {
    let mut vec = Vec::new();
    push_vec(&mut vec, 1);
}

fn push_vec(v: &mut Vec<i32>, value: i32) {
    v.push(value);
}

引用规则

对于同一个资源的借用,同一个作用于只能有一个可变引用,或者多个不可变引用,两者不能同时存在

在可变引用是否前不能访问资源所有者

任何引用的作用于必须小于资源所有者的作用于,并在离开作用于后自动释放

就像一个读写锁

fn main() {
    let mut x = 6;
    let y = &mut x;
    *y += 1;
    let z = &x;
    println!("{}, {}", *y, *z);
}

这个就会出错

fn main() {
    let mut x = 6;
    let y = &mut x;
    *y += 1;
    let z = x;
}

这个也会出错,y还没有释放就再次访问了x

切片

就是[]中选择调用多说

例如:[3..5]从3到4全开

切片也可以作为函数参数

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];
    let vec_slice = &mut vec[2..];
    vec_slice[0] = 4;
    println!("{:?}", vec);
}

转移所有权的迭代器:Intolter

fn main() {
    let vec = vec!["JAVA", "RUST", "PYTHON"];
    for str in vec.into_iter() {
        match str {
            "RUST" => println!("HELLO"),
            _ => println!("{}", str),
        }
    }
    // println!("{:?}", vec);
}

不可借用Iter

不发生所有权的转移

可变借用:IterMut

iter是只读的,IterMut是可变的

fn main() {
    let mut vec = vec!["JAVA", "RUST", "PYTHON"];
    for str in vec.iter_mut() {
        match str {
            &mut "RUST" => {
                *str = "Hello";
                println!("{}", str);
            },
            _ => println!("{}", str),
        }
    }
}

生命周期

fn foo(x: &str) -> &str {
    x
}

fn xoo<'a>(x: &'a str) -> &'a str {
    x
}

后面的是前面函数经过编译器推导的代码

fn long_str(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
// 这个函数就会出现问题,所以应该显式使用生命周期
fn long_str<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
// 这样就不会出现问题了,这里生命周期用的是同样的,系统就会返回那个更小的生命周期

生命周期的作用就是防止出现悬垂引用

就是在内存中的数据已经被释放了还被再次使用,

fn main() {
    let str1 = String::from("abcd");
    let result;
    {
        let str2 = String::from("hello");
        result = long_str(str1.as_str(), str2.as_str());
    }
    // println!("{}", result);
}
fn long_str<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

这里就不能输入,因为从生命周期来看,result选择了那个更小的生命周期,所以在那个{}之后其实是没有result的了

结构体与生命周期

如果结构体中有引用,就会出现问题

正确写法:

struct Foo<'a, 'b> {
    x: &'a i32,
    y: &'b i32,
}

impl<'a, 'b> Foo<'a, 'b> {
    fn get_x(&self) -> &i32 { self.x }
    fn max_x(&'a self, f: &'a Foo) -> &'a i32 {
        if self.x > f.x {
            self.x
        } else {
            f.x
        }
    }
}
fn main() {
    let f1 = Foo { x: &3, y: &5 };
    let f2 = Foo { x: &7, y: &9 };
    println!("{}", f1.max_x(&f2));
}

豆瓣链接:https://book.douban.com/subject/35447165/




文章评论

目录