Rust中的盒子和指针 (zz)

 

 


智能指针是一种数据结构,其行为类似于指针,同时提供内存管理或绑定检查等附加功能。
智能指针可跟踪其指向的内存,还可用于管理其他资源,如Fils句柄和网络连接。
智能指针最初用于C++语言。
引用也是一种指针,但除了引用数据之外,它没有其他功能。引用由&运算符表示。
智能指针提供的功能超出了参考提供的功能。智能指针提供的最常见功能是引用计数智能指针类型此功能能够通过跟踪所有者来拥有多个数据所有者,如果没有所有者,则可以清除数据。
引用是仅借用数据的指针,而智能指针是拥有它们指向的数据的指针。

智能指针的类型:

 

  • Box <T>:Box <T>是一个智能指针,指向在类型为T的堆上分配的数据,其中“T”是数据的类型。它用于将数据存储在堆上而不是堆栈上。
  • Deref <T>:Deref <T>是一个智能指针,用于自定义解除引用运算符(*)的行为。
  • Drop <T>:Drop <T>是一个智能指针,用于在变量超出范围时从堆内存中释放空间。
  • Rc <T>:Rc <T>代表参考计数指针。它是一个智能指针,用于记录存储在堆上的值的引用数。
  • RefCell <T>:RefCell <T>是一个智能指针,允许借用可变数据,即使数据是不可变的。这个过程被称为内部可变性。

原文出自【易百教程】,商业转载请联系作者获得授权,非商业转载请保留原文链接:https://www.yiibai.com/rust/rust-smart-pointers.html

 


Rust中的盒子和指针 
根据Rust 0.5tutorial整理,指针这部分内容应该不会变化了。 
(豆瓣的帖子没法排版。排版好的请参考http://corwindong.blogspot.com/2013/01/rust_7.html

大多数现代语言对于聚合类型(如class, struct, enum)都采用一种“uniform representation”方式表示,将这些类型缺省表示为分配在堆上的内存的指针。Rust则不同,类似于CC++Rust直接表示这些类型。也就是说聚合数据在Rust中是未打包的(unboxed)。这意味着当你写下let x = Point {x: 1f, y: 1f};时,你是在栈上创建了一个struct。如果你把它拷贝到一个数据结构中,你拷贝的是整个struct,而不是指针。 

对于Point这样的小型结构来说,直接拷贝要比先分配内存后再通过指针访问高效。但是对于大型结构,或者那些有可变数据域的结构,只在栈上或堆上保留一份数据,然后通过指针引用会更好一些。 

Rust支持好几种指针(三种)。
安全指针@T代表分配在本地堆上的managed boxes;指针~T代表分配在交换堆上的uniquely-owned boxes;而指针&T,代表 borrowed pointers,这种指针可以指向任何内存,同时它们的生命周期由调用栈管理。 

所有的指针类型都由一元运算符来解除引用。 

1 Managed Boxes 
Managed Boxes
是一种指针,指向堆上分配,具有GC的内存。将一元运算符应用于表达式就建立了一个managed box。生成的盒子包含了表达式的结果。拷贝一个managed box,只是拷贝了指针,而不是盒子中的内容。 

let x: @int = @10; // New box 
let y = x; // Copy of a pointer to the same box 

// x and y both refer to the same allocation. When both go out of scope 
// then the allocation will be freed.  


一个managed type要么形如@T,要么是任何包含了managed boxes或者其他managed types的类型。 

// A linked list node 
struct Node { 
mut next: MaybeNode, 
mut prev: MaybeNode, 
payload: int 


enum MaybeNode { 
SomeNode(@Node), 
NoNode 


let node1 = @Node { next: NoNode, prev: NoNode, payload: 1 }; 
let node2 = @Node { next: NoNode, prev: NoNode, payload: 2 }; 
let node3 = @Node { next: NoNode, prev: NoNode, payload: 3 }; 

// Link the three list nodes together 
node1.next = SomeNode(node2); 
node2.prev = SomeNode(node1); 
node2.next = SomeNode(node3); 
node3.prev = SomeNode(node2); 

Managed Boxes
绝不跨越task边界。 

2 Owned Boxes 
managed boxes不同的是,owned boxes具有内存独享性,因此两个owned boxes不会指向同一内存。所有跨越全部tasks边界的owned boxes都在一个exchange heap上分配。而它们的唯一所有权特性使得tasks可以高效地交换它们。 

由于owned boxes的唯一所有特性,拷贝它们则需要分配一个新的owned box然后复制内容。然而,owned boxes默认使用moved,交换内存所有权,反初始化前一个owning变量。任何企图在变量被move后访问该变量的操作都将触发编译错误。 

let x = ~10; 
// Move x to y, deinitializing x 
let y = x; 


如果你打算拷贝owned box,你必须明确指出: 

let x = ~10; 
let y = copy x; 

let z = *x + *y; 
assert z == 20; 


owned boxes不包含任何managed boxes时,可以发送给其他task。发送方task将交出box的所有权,发送后将再不能访问它。接收方task将成为box的唯一所有者。 

3 Borrowed Pointers 
Rust
borrowed pointers是通用的引用/指针类型,类似C++的引用类型,但是保证指向有效内存。和owned boxes不同的是,borrowed pointers从不隐含内存所有权。指针可以从任意类型借来,同时保证指针不会比它指向的值生存得更久。 

举个例子,比如一个简单的结构类型:Point 

struct Point { 
x: float, y: float 


我们使用这个简单的类型来展示指针的多种分配方式。例如,在下面的代码中,三个局部变量都包含一个point,但是分配在不同的位置上。 

let on_the_stack : Point = Point {x: 3.0, y: 4.0}; 
let managed_box : @Point = @Point {x: 5.0, y: 1.0}; 
let owned_box : ~Point = ~Point {x: 7.0, y: 9.0}; 


假如我们想要写一个计算两个点距离的函数,要求不管两个点存储在什么位置都能用。例如,我们可能计算on_the_stackmanaged_box之间的距离,或者managed_boxowned_box之间的距离。一种方法是定义两个参数都是Point类型的函数,该函数使用point的值。这就会造成我们调用函数时拷贝point的值。对于points而言,问题还不严重,但是一般情况下拷贝都是代价高昂的,甚至更糟的是,存在可变数据域,这些将改变程序的语意。所以我们一般定义接受指向points的指针的函数。我们使用borrowed_pointers来达成目标: 

fn compute_distance(p1: &Point, p2: &Point) -> float { 
let x_d = p1.x - p2.x; 
let y_d = p1.y - p2.y; 
sqrt(x_d * x_d + y_d * y_d) 


现在我们可以以各种方式调用compute_distance() 

compute_distance(&on_the_stack, managed_box); 
compute_distance(managed_box, owned_box); 


这里的&运算符用来取得变量on_the_stack的地址;我们称这个为borrowing局部变量on_the_stack,因为我们创建了一个别名:也就是访问同一数据的另一种方式。 

而对于像managed_boxowned_box这样的盒子,则不需要什么操作。编译器能自动将类似@point~point这样的盒子转换为类似&point这样的borrowed pointer。这是另一种形式的borrowing;这种情况下,managed/owned box的内容被借出。 

不论一个值何时被借出,对于原始值得操作都存在一些限制。例如,如果一个变量的内容已经被借出,那么我们不能将该变量发送给别的task,也不能执行任何会使得借出的值被释放,或者类型改变的操作。请牢记这条原则:你必须等借出的值被归还后(也就是等borrowed pointer离开作用域),才能再次对它为所欲为。 

对于borrowed pointers更进一步的解释,请参考borrowed pointer tutorial 

4 Dereferencing pointers 
Rust
使用一元运算符来获取box或者pointer的值,这点和C类似。 

let managed = @10; 
let owned = ~20; 
let borrowed = &30; 

let sum = *managed + *owned + *borrowed; 


对可变指针解引用可以出现在赋值式的左边。这样的赋值将修改指针指向的值。 

let managed = @mut 10; 
let owned = ~mut 20; 

let mut value = 30; 
let borrowed = &mut value; 

*managed = *owned + 10; 
*owned = *borrowed + 100; 
*borrowed = *managed + 1000; 


指针相关操作符具有高优先级,但是比取数据域和调用方法的点操作优先级低。 

let start = @Point { x: 10f, y: 20f }; 
let end = ~Point { x: (*start).x + 100f, y: (*start).y + 100f }; 
let rect = &Rectangle(*start, *end); 
let area = (*rect).area(); 


为了消除上面那丑陋的星号括号,点操作符对接受者(点号的左边)自动解引用,所以大多数情况下,不需要对接受者显式解引用。 

let start = @Point { x: 10f, y: 20f }; 
let end = ~Point { x: start.x + 100f, y: start.y + 100f }; 
let rect = &Rectangle(*start, *end); 
let area = rect.area(); 


来个极端的例子,让编译器对任意重指针自动解引用: 

let point = &@~Point { x: 10f, y: 20f }; 
io::println(fmt!("%f", point.x)); 


索引操作符([ ])也具有自动解引用的特性。

 

原文地址:https://www.cnblogs.com/cx2016/p/12926204.html