Rust学习笔记(12)——Box智能指针

        在rust编程语言的官方介绍中,对于Box智能指针介绍比较简单,只说其可以包裹一个变量,将变量存在堆上,Box中包含堆上的地址。同时Box指针实现了自动解引用trait。在实际操作中,也确实类似,对其操作与对普通类型操作类似,支持连续引用和解引用,在编译器不能自动解引用时,需要手动解引用。

Box指针看似简单,实际上在复杂的应用场景中也有很多情况,我们从简单变量的指针开始。

一、简单变量的指针操作

对于简单变量,操作可以如下:(例子-1)

fn main() {
    let mut a = 5;
    let b = &mut a;

    *b += 10;

    println!("b = {:?}", b);
    println!("a = {:?}", a);
}

也可以连续引用或可变引用:(例子-2)

fn main() {
    let mut a = 5;
    let mut b = &mut a;
    let c = &mut b;

    **c += 10;

    println!("c = {:?}", c);
    println!("b = {:?}", b);
    println!("a = {:?}", a);
}

多次引用时,如上面中间一行 “**c + 10”,对c运算,需要手动解引用多次。但下面println!不需要,因为编译器可以自动识别并进行多次解引用。

二、Box指针的引用操作——多重引用和改变Box或其中的值

2.1 改变Box本身的值

那么对于Box指针的操作呢?也类似,可以通过引用或多次引用使用或改变原始变量的值(需要手动多次解引用),如下代码:

fn main() {
    let mut a = Box::new(5);
    let b = &mut a;
    // let mut b = &mut a;
    // let c = &mut b;

    // **c = Box::new(10);
    *b = Box::new(12);

    // println!("c = {:?}", c);
    println!("b = {:?}", b);
    println!("a = {:?}", a);
}

看起来和普通变量好像没什么区别。

2.2 改变Box内包含的变量的值

但等一下,上面的代码,改变的都是整个Box本身。如果改变Box里面的值呢?可以吗?又怎么操作呢?答案是可以的,如下代码:

fn main() {
    let mut a = Box::new(5);
    *a += 12;

    println!("a = {:?}", a);
}

这也确实和Box文档介绍的一样,它包含了地址,其实可以视为是一个指向堆数据的指针。上面代码和 例子-1 中的代码有点类似了。

这种改变也支持多重连续引用,下面代码有点类似于 例子-2:

fn main() {
    let mut a = Box::new(5);
    let b = &mut a;
    **b += 10;
    // *a += 12;
    println!("b = {:?}", b);
    println!("a = {:?}", a);
}

如果其中包含的是复合类型,也可以通过解引用改变其中的值,该操作支持多重引用,如下:(代码中要注意a.x的类型及操作与a.z的差异

fn main() {
    let mut a = Box::new(Point::new(5, 5, 9));
    *a.x += 10; // because a.x's type is Box<i32>, it needs to deref.
    a.z += 10; // a.z's type is i32, it doesn't need to deref.
    println!("a = {:?}", a);

    let b = &mut a;
    *b.x += 10;

    println!("b = {:?}", b);
    println!("a = {:?}", a);
}

#[derive(Debug)]
struct Point {
    x: Box<i32>,
    y: Box<i32>,
    z: i32,
}

impl Point {
    fn new(x: i32, y: i32, z: i32) -> Point {
        Point {
            x: Box::new(x),
            y: Box::new(y),
            z,
        }
    }
}

上面代码中,编译器首先对a和b进行了自动解引用,然后才能操作被包含变量的值,输出:

a = Point { x: 15, y: 5, z: 19 }
b = Point { x: 25, y: 5, z: 19 }
a = Point { x: 25, y: 5, z: 19 }

 上面一系列的操作还是有点令人眼花缭乱的:)

三、Box指针的解引用

既然Box只是一个指针,那么可以对其进行解引用(*)操作吧?

3.1 简单类型

我们先看看简单类型:

fn main() {
    let mut a = Box::new(5);
    let b = *a; // copy '*a' to b.
    
    *a += 12;
    println!("b = {:?}", b);
    println!("a = {:?}", a);
}

答案是可以的,对于简单类型来说,其值是copy的。如上面有注释的行。同时变量a仍然可用。下方通过 *a += 12 改变a的值,可以看出b的值没有变化。输出为:

b = 5
a = 17

对于简单类型来说,引用情况下,允许对引用进行解引用吗?答案是不允许的。下方代码无法编译:

    let a = Box::new(5);
    let b = &a;

    let c = *b; // Error! move occurs because `*b` has type `std::boxed::Box<i32>`, which does not implement the `Copy` trait

提示错误:

cannot move out of `*b` which is behind a shared reference
move occurs because `*b` has type `std::boxed::Box<i32>`, which does not implement the `Copy` trait

这里注意,b为a的引用,其类型为&Box<i32>,对b进行解引用的话,*b的类型才为 Box<i32>,如上错误提示,*b 未实现Copy trait, 并且*b也不拥有其指向变量a的所有权,所以无法移动到变量c中,违反所有权规则,报错。可变引用情况下也是如此。

在这种情况下,可以一直解引用到其包含的值,并进行操作,但此时的值是copy的,可以看出,对c的操作不改变a和b,是可以的,如下代码:

fn main() {
    let mut a = Box::new(5);
    let b = &mut a;
    let mut c = **b;
    c += 20;
    **b += 10;

    println!("c = {:?}", c);
    println!("b = {:?}", b);
    println!("a = {:?}", a);
}

输出:

c = 25
b = 15
a = 15

3.2 复合类型

那么如果Box中包含的是复合数据类型呢?我们先定义一个复合类型:

#[derive(Debug)]
struct Point {
    x: Box<i32>,
    y: Box<i32>,
}

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point {
            x: Box::new(x),
            y: Box::new(y),
        }
    }
}

尝试:

fn main() {
    let a = Box::new(Point::new(5, 5));
    let b = *a;

    println!("b = {:?}", b);
    println!("a = {:?}", a);
}

最后一行报错:

borrow of moved value: `a`
value borrowed here after move
note: move occurs because `*a` has type `Point`, which does not implement the `Copy` traitrustc(E0382)

如果注释掉最后一行,则可编译运行,可以看出,对a进行解引用后,将a包含的值的所有权转移到了变量b中(因为Point未实现Copy trait),同时a变得无效,不可再次使用。此时,可以对b进行各种操作,如下:

fn main() {
    let a = Box::new(Point::new(5, 5, 9));
    let mut b = *a;
    b.z += 10;
    *b.x += 10;

    println!("b = {:?}", b);
    // println!("a = {:?}", a);
}

输出:

b = Point { x: 15, y: 5, z: 19 }

那么,引用情况下,允许对引用进行解引用吗?答案也是不允许的。如下代码无法编译:

    let a = Box::new(Point::new(5, 5));
    let b = &a;
    let c = *b;

提示错误:

cannot move out of `*b` which is behind a shared reference
move occurs because `*b` has type `std::boxed::Box<Point>`, which does not implement the `Copy` trait

这和包含简单类型时对引用的解引用错误类似,也和笔记(10)中对struct的深入分析类似。

那么此时可以像简单类型那样,一直解引用到其包含的值,并进行move操作吗?经过实验,是不行的。下面代码无法编译:

fn main() {
    let a = Box::new(Point::new(5, 5, 9));
    let b = &a;
    let c = **b;

    println!("b = {:?}", b);
    // println!("a = {:?}", a);
}

提示如下错误:

cannot move out of `**b` which is behind a shared reference

move occurs because `**b` has type `Point`, which does not implement the `Copy` trait

如果要改变其中的值,请参考2.2小节。

四、Box包含struct成员的所有权的转移

之前有学习到,struct支持成员的所有权转移,那么当struct被Box包裹时,还支持吗?我们先定义一个结构体及其方法:

#[derive(Debug)]
struct Node {
    val: i32,
    next: Option<Box<Node>>, // 'next' has the ownership of the next node.
}

impl Node {
    fn new(val: i32) -> Box<Node> {
        Box::new(Node { val, next: None })
    }

    fn link(&mut self, node: Box<Node>) {
        self.next = Some(node);
    }
}

尝试一下:

fn main() {
    let mut a = Node::new(1);
    let b = Node::new(2);
    a.link(b);

    let c = a.next;
    println!("{:?}", c);
    // println!("{:?}", a);
}

a的类型为 Box<Node>,但上面代码仍然可以将a.next 的所有权 move 到 变量 c 中。这相当于下面这行:

let c = (*a).next;

对a进行解引用,获取被包含的Node的所有权,然后对struct成员进行所有权转移。但编译器已经识别到我们的意图了。Box指针不愧是智能指针。

不信的话可以将最后一行打印a的注释去掉,会提示如下错误:

borrow of partially moved value: `a`

说明a的部分成员所有权确实被转移了。

如果对上面的代码加一层引用,则会报错:

    let mut a = Node::new(1);
    let b = Node::new(2);
    a.link(b);

    let d = &a;

    let c = d.next; // Error!

最后一行会报错:

cannot move out of `d.next` which is behind a shared reference

仔细分析一下,Box智能指针已经自动解引用了,这里报错的原因是d是Node的引用,无权转移其成员的所有权。这和前面学习struct的引用的所有权限制是一致的。

简单吗?一点都不简单。

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值