Rust 入门笔记【二】

哪些变量实现了copy?

  • 所有整形
  • 所有浮点型
  • 布尔类型
  • 字符型
  • 元组,当且仅当元组内所有类型都可以copy

函数与所有权

函数的传参和赋值类似,也可能会发生移动或复制两种,且规则一样.

同样的,函数返回值也可以返回所有权.

引用

看下面的例子

fn main(){
    let s = String::from("hello");
    let len = cnt(s);
    println!("{s} length is {len}");
}

fn cnt(s : String) -> usize {
    return s.len();
}

这个例子是无法正常的运行的。很好理解因为s的所有权被转移到了cnt函数中,所以我们可以在用函数的返回值返回所有权。

fn main(){
    let s = String::from("hello");
    let (s, len) = cnt(s);
    println!("{s} length is {len}");
}

fn cnt(s : String) -> (String, usize) {
    let len = s.len();
    return (s, len);
}

但是如果频繁的改变所有权,不仅麻烦而且效率不高,因此我们这里引出了一个引用的概念。

fn main() {
    let s = String::from("hello");
    let len = cnt(&s);
    println!("{s} length is {len}");
}

fn cnt(s: &String) -> usize {
    s.len()
}

但是这个引用是无法进行修改的。如果确实需要修改操作,我们可以使用可变引用。

fn main() {
    let s = String::from("hello");
    let len = cnt(&s);
    println!("{s} length is {len}");
}

fn cnt(s: &String) -> usize {
    s.len()
}

普通的引用是无法修改的,如果需要修改我们就要使用可变引用。

fn main() {
    let mut s = String::from("hello");
    let len = cnt(&mut s);
    println!("{s} length is {len}");
}

fn cnt(s: &mut String) -> usize {
    s.push_str(", world");
    s.len()
}

注意可变引用有一个很强的限制是只能对一个变量创建一个可变引用,但创建一个可变引用后甚至不能再创建一个普通引用。下面的代码x2,x3都会报错。

fn test() {
    let mut x = 5;
    let x1 = &mut x;
    let x2 = &mut x;
    let x3 = & x;
}

这样设计是为了防止数据竞争,因此如果再两个不同的作用域中,则可以分别创建一个可变引用。换句话来说,不允许同时创建两个可变引用。

悬垂引用

如果一个指针指向的对象被内存释放了,就会造成悬垂引用。看下面的代码

fn main() {
    let ref_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");
    &s  
}

dangle中返回了s的引用,也就是说s的所有权还在s,当dangle函数结束时s就会被drop掉。这样ref_to_nothing就会悬空引用。但是,这样的代码其实时无法通过编译的。因此再回到我们说的,如果一个rust通过编译了,则大概率是正确的。

slice

想实现一个函数,给一个由若干各单词组成的字符串,单词之间用空格分隔,找到一个单词。

该怎么实现?有一个比较显然的是第一个单词的开头下标一定是0,相当于我们只要找到第一个空格,并返回,可以得到一个左闭右开的区间表示第一个单词。

fn main() {
    let s = String::from("Hello world");
    let p = first_word(&s);
    println!("p = {}", p);
}

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }
    return s.len();
}

这样确实可以找到第一个单词,但是带了一个问题,如果s发生修改的话,无法判断p是否失效。比如

fn main() {
    let mut s = String::from("Hello world");
    let p = first_word(&s);
    println!("p = {}", p);
    s.clear();
    println!("p = {}", p);
}

实际上此时字符串已经为空了,但是p还是5。因此我们可以引出一个概念字符串 slice,看起来是这样的。

fn main() {
    let mut s = String::from("Hello world");
    let hello =  &s[0..5];
    let world =  &s[6..11];
    println!("{},{}!", hello, world);
}

并且slice的截取和Python的数组的截取很类似,也可省略端点。字符串slice 的类型声明是&str我们可以写出

fn main() {
    let mut s = String::from("Hello world");
    let p = first_word(&s);
    println!("p = {}", p);
}

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    return &s[..];
}

除了字符串slice外,还有需要其他类型的slice。

posted @ 2025-05-05 17:41  PHarr  阅读(19)  评论(0)    收藏  举报