Skip to content

Latest commit

 

History

History
166 lines (108 loc) · 11.2 KB

Arrays and Vectors - Chinese.md

File metadata and controls

166 lines (108 loc) · 11.2 KB

原文: https://github.com/nrc/r4cppp/blob/master/arrays.md 翻译者: Scott Huang
日期: August 22,2015 于 厦门

数组和向量 Arrays and Vectors

Rust数组和C数组很不同。开胃菜是数组有静态的和动态尺寸的口味。 这些都是比较常见的固定长度的数组Array和切片Slice。正如我们所看到的,前者是一种坏名声,因为两种类型的数组Array拥有固定的(和可增长相反)长度。对于可变长的数组array,Rust提供向量Vec集合。

固定长度的数组 Fixed length arrays

固定长度的数组Array有已知的静态长度和类型。例如: [i32; 4]是指类型为i32的长度为4的数组。

数组字面量和访问语法和C语言一致:

let a: [i32; 4] = [1, 2, 3, 4];     // 和往常一样,类型注释是可选的
println!("The second element is {}", a[1]);

你会注意到数组的索引以0开始,就像C一样。

然而,和C/C++不一样,Rust数组的索引动作会检查边界。实际上数组的所有访问都会进行边界检查,这也从另一方面说明Rust是一个安全的语言。

如果你试图a[4],你会得到一个运行时恐慌panic。不幸的是,Rust编译器还没有足够聪明到给你编译时错误,即使有时候错误看起来显而易见(就如本例)。

如果你喜欢危险的生活,或者必须榨出你程序的最后一点性能,你仍然可以访问数组而不检查边界。为此,使用数组的get_unchecked方法。不检查数组边界的语句必须包在unsafe的语句边界内。 你应该只在极少数情况下使用这种方法。

就像Rust其他数据结构一样,数组缺省是不可变的,易变性是遗传的。突变也是通过索引语法来完成:

let mut a = [1, 2, 3, 4];
a[3] = 5;
println!("{:?}", a);

就像其它数据一样,你可以通过获取引用来借用数组的使用权:

fn foo(a: &[i32; 4]) {
    println!("First: {}; last: {}", a[0], a[3]);
}

fn main() {
    foo(&[1, 2, 3, 4]);
}

注意,在一个借来的数组中索引仍然有效。

这是一个很好的时间来谈谈Rust数组中一些最让C++程序员(他们的代表)感兴趣的方面。Rust数组是值类型的:它们在栈Stack分配内存,和其他值一样,数组对象是一系列的值,并不是一个指针指向这些值(C是这么干的)。所以从我们前面的例子, let a = [1_i32, 2, 3, 4];将会从栈分配16字节并执行let b= a;将会复制16字节。 如果你喜欢一个C风格的数组,你必须明确将指针指向数组,这将给你一个指向第一个元素的指针。`

Rust和C++数组差异的最后一点是,Rust数组可以实现特性Traits,然后拥有方法。比如你可以用a.len()来查出数组的长度。

切片 Slices

一个切片在Rust看来仅仅是一个在编译期时长度未知的数组Array。类型的语法就像是长度固定的数组,但没有长度。

例如,[i32]是一个32位整数的切片(没有静态已知长度)

slices要注意的一点:由于Rust编译器必须知道所有对象的长度,而slice的长度未知,因此我们从来不能有值的切片类型。 如果你尝试写fn foo(x: [i32]),编译器就会报错。

因此,你必须总有指针指向切片slices(这条规定有一些非常技术性的例外,以便您可以实现你自己的智能指针,不过,现在你可以安全的忽略它们)。你必须这样写fn foo(x: &[i32]) (一个借用来的引用指向切片slice) 或者 fn foo(x: *mut [i32]) (一个可写的原始指针来指向切片slice), 等等。

创建切片slice的最简单的方法是通过强制。Rust比C++更少采用隐式转换。其中一种强制转换可以把固定长度的数组转为切片slices。由于切片必须指向值,这是一种有效率的值和指针之间的强制转换。举例,我们可以转换 &[i32; 4]&[i32], 例如,

let a: &[i32] = &[1, 2, 3, 4];

这里,右边是一个从栈分配的固定长度为4的数组,然后取一个引用指向它(type &[i32; 4])。那个引用强制转换类型为&[i32],并且用let声明取名为a

再次强调,访问和C一样(用[...]),并且访问会进行边界检查。你还可以用len()来检查自己的长度。所以,很明显,数组的长度是在某处已知的。实际上Rust的所有类型的数组都有已知长度,因为这是边界检查的必要条件,而这是保证内存安全的必须部分。大小已知是动态变化的(和静态的长度固定的数组相对立),并且我们说切片slice类型是动态大小类型(DSTs,还有其他种动态大小的类型,它们会在其它地方提到)。

由于切片slice仅仅只是一个系列的值,大小不能存为切片的一部分。取而代之,它被存为指针的一部分(记得切片必须总是以指针类型存在)。一个指向切片slice的指针(像所有DSTs指针)是一个胖指针 - 有两个字words宽,而不仅一个字宽,并且指向数据加一个有效负荷。这里,负荷指的是切片的长度。 所以,上述例子,指针a将有128 bits宽(在64位系统中)。第一个64 bits将存序列[1,2,3,4]1的地址。通常,作为Rust程序员,这些胖指针可以仅看待为一个普通指针。但应该知道它的原理(比如它可以影响casts转换等等)

切片符号和范围 slicing notation and ranges

一个切片可以看作是数组的视图(借来的)。到现在为止,我们仅仅看到整个数组的切片,但我们也可以取数组的部分作为切片。这里有一个特殊的符号,就像索引语法,但获取一个范围而不是一个简单的整数。比如,a[0..4],就是取a的前面4个元素。注意,范围是在起始位置是包含的,而在结尾部分是排除的(译者:原文有错,这边直接改正了)。比如:

let a: [i32; 4] = [1, 2, 3, 4];
let b: &[i32] = &a;   // Slice of the whole array. 整个数组的切片
let c = &a[0..4];     // Another slice of the whole array, also has type &[i32]. 另外一种整个数组的切片,并且带有类型 &[i32]
let c = &a[1..3];     // The middle two elements, &[i32]. 中间两个元素
let c = &a[1..];      // The last three elements. 最后三个
let c = &a[..3];      // The first three element. 前面三个
let c = &a[..];       // The whole array, again. 所有
let c = &b[1..3];     // We can also slice a slice. 可以取切片的切片

请注意,在最后一个例子里,我们需要借用切片动作的结果。这个切片动作的语法产生一个没有借用的切片(类型:[i32]),我们必须接着借用(得到 a &[i32]),以至于我们在一个切片基础上继续切片。

在切片语法外,还可以使用范围语法。 a..b产生一个迭代器从ab-1。这可以和其他迭代器结合在一起,通常,可以用在for循环中:

// Print all numbers from 1 to 10. 打印所有的数据,从1到10
for i in 1..11 {
    println!("{}", i);
}

向量 Vecs

一个向量vector从堆heap中分配内存并且有自己的引用。因此(就像Box<_>),它带有移动语义。我们可以想象一个固定长度的数组类似一个值,一个切片来借用引用。 同样的,想象Rust中一个向量vector类似一个Box<_>指针。 把Vec<_>想象为某种智能指针而不是一个值本身,就像Box<_>。和切片slice类似,长度是存在指针pointer里,这种情况下pointer就是向量Vec的值。

向量i32s拥有类型Vec<i32>。没有向量字变量,但我们可以用vec!宏取得同样效果。我们也可以用Vec::new()来创建空的向量。

let v = vec![1, 2, 3, 4];      // A Vec<i32> with length 4.  长度为4的Vec<i32>
let v: Vec<i32> = Vec::new();  // An empty vector of i32s. i32s的空向量

在上述情况,类型注释是必不可少的,这样编译器就知道向量是一个装啥的向量了。如果向量有内容,那么类型注解则不是必须的。

就像数组arrays和切片slices,我们可以用索引符号来从向量vector中取得一个值(比如v[2])。 再次,这些都会做边界检查。我们也可以用切片符号来从向量获取切片(比如,&v[1..3])。

向量vectors的额外特色是它们的容量大小可以改变 - 它们可以按需变长或者变短。 例如,v.push(5)将把元素5加入向量vector的末尾(这将要求v是可变的)。注意,改变向量会导致重新分配内存,对于大的向量来说这意味着一堆拷贝动作。为了避免这个动作,你可以用with_capacity预先为向量分配空间,请参考Vec docs

索引特质 The Index traits

读者请注意: 本节有很多资料,我没有适当的覆盖到。如果你是按照培训资料顺序阅读的话,你可以跳过这一节,底下是一些高级话题。

相同的索引indexing语法被同时用给数组和向量,以及其他一些集合collections,比如HashMaps。并且你可以用在你自己的集合类。你可以选择性的用索引(和切片slicing)语法来实现Index特质。这是一个好例证来说明Rust可以如何用好的语法来同时服务内置的和用户的类型(Deref用来为智能指针解引用,就像Add以及其他各种各样的特质都用相似的方法起作用)

Index特质看起来像:

pub trait Index<Idx: ?Sized> {
    type Output: ?Sized;

    fn index(&self, index: Idx) -> &Self::Output;
}

Idx是用来索引的类型。对于大多数的索引indexing,这是usize类型。对于切片来说,这是一个std::ops::Range类型。Output是一种用来返回索引的类型,因集合不同而不同。 对于切片动作slicing来说,它将返回切片,而不是一个单一元素类型。注意,参考和方法返回集合的是引用的元素,且具有相同的使用期。

让我们研究Vec的实现,来看一看一个实现长啥样:

impl<T> Index<usize> for Vec<T> {
    type Output = T;

    fn index(&self, index: usize) -> &T {
        &(**self)[index]
    }
}

正如我们上面说的,索引indexing采用usize。对于Vec<T>,索引动作将返回一个类型为T的单一元素,就是Output的值。index的实现看起来有点怪异 - (**self)获取整个向量的切片视图,然后我们用切片索引动作来获取元素,最后取得指向它的一个引用。

如果你有你自己的集合,你可以用类似的方法实现Index

初始化语法 Initialiser syntax

和Rust里面的所有数据一样,数组和向量必须恰当的初始化。你经常想要一个初始值都是0的数组,如果用数组字面量语法的将写的很痛苦。所以Rust给你一点语法糖来初始化给定值的数组:[value; len]。所以,比如要创建一个含100个0,你可以用[0; 100]

类似的,vec![42; 100]将创建一个含100个元素的向量,并且初始值都是42。

初始值不限于整数,它可以是任何表达式。对于数组的初始化,长度必须是整数常量表达式。对于vec!,它可以使任何返回usize的表达式。