Rust标准库包含了一系列非常有用的被称为集合(collections)的数据结构。大部分的数据结构都代表着某个特定的值,但集合却可以包含多个值。与内置的数组与元组类型不同,这些集合将自己持有的数据存储在堆上。不同的集合类型有着不同的性能特性与开销。

  • 动态数组(Vector):可以让你连续地存储任意多个值
  • 字符串(String):是字符的集合。
  • 哈希映射(Hash map):可以让你将值关联到一个特定的键上,它是另外一个数据结构——映射(map)的特殊表现。

一、动态数组(Vector)

动态数组(Vector)是一个集合类型表现为Vec<T>。动态数组允许你在单个数据结构中存储多个相同类型的值,这些值会彼此相邻地存储在内存中。动态数组非常适合在需要存储一系列相同类型值地场景中使用。

1、创建动态数据

fn main() {
    let v: Vec<i32> = Vec::new();
}

因为Vector是用泛型实现地,所以我们需要告诉Rust变量v绑定的Vec<T>会持有i32类型的元素。
Rust为了方便提供了vec!宏,这个宏会根据我们提供的值来创建一个新的Vector,并根据上下文判断变量v是存储那种类型的键值。

fn main() {
    let v = vec![1, 2, 3];
}

2、更新Vector

fn main() {
    let mut v = Vec::new();

    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
}

对一个新创建的Vector并向其增加新元素,可以使用push方法。但是必须在该变量v前使用mut关键字使其可变并且放入其中的所有值都是i32类型,而且Rust也根据数据做出判断。

3、丢弃Vector

fn main() {
    {
        let v = vec![1, 2, 3, 4];

        // 处理变量 v
    } // <- 这里 v 离开作用域并被丢弃
}

动态数组中的所有内容都会随着动态数组的销毁而销毁,其持有的整数将被自动清理干净。

4、读取Vector元素

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let third: &i32 = &v[2];
    println!("The third element is {}", third);

    match v.get(2) {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element."),
    }
}

读取Vector元素中的内容:

  1. 使用&[]返回一个引用;
  2. 使用get方法以索引作为参数返回一个一个Option<&T>
    Rust提供了两种引用元素的方法的原因是当尝试使用现有元素之外的索引值时可以选择让程序如何运行。
fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let does_not_exist = &v[100];
    let does_not_exist = v.get(100);
}


当运行这段代码时,[]方法会因为索引指向了不存在的元素而导致程序触发panic。当你尝试越界访问元素时使程序直接崩溃,那么[]方法很合适;
get方法会在检测到索引越越界时简单返回None,而不是使程序直接崩溃。当偶尔越界访问动态数据组中的元素是一个正常行为时,你应该使用这种get方法;(例如:索引可能来自一个用户输入的数字。当这个数字意外地超出边界时,程序就会得到一个None值。而我们也应该将这一信息反馈给用户,告诉他们当前动态数组的元素数量,并再度请求用户输入有效的值。这就比因为输入错误而使程序崩溃要友好得多!)
一旦程序获得了一个有效的引用,借用检查器就会执行所有权规则和借用规则,来保证这个引用及其他任何指向这个动态数组的引用始终有效。

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    let first = &v[0];

    v.push(6);

    println!("The first element is : {}", first);

}

不能在相同作用域中同时存在可变和不可变引用的规则。当我们获取了vector的第一个元素的不可变引用并尝试在vector末尾增加一个元素的时候。

此处错误是由动态数组的工作原理导致的:当动态数组张的元素时连续存储的,插入新的元素后也许会没有足够多的空间将所有元素依次相邻地放下,这就需要分配新的内存空间,并将旧的元素移动到新的空间上。

5、遍历动态数组中的值

通过for循环来获取动态数组中每一个i32元素的不可变引用。

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{}", i);
    }
}

我们也可以遍历可变的动态数组,并获得元素的可变引用,并修改其中的值。

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

为了使用+=运算符来修改可变引用指向的值,我们首先需要使用解引用运算符*来获取i的绑定值。

6、使用枚举来存储多个类型的值

当需要在动态数组中存储不同类型的元素时,可以定义并使用枚举来应对这种情况,因为枚举中的所有变体都被定义为了同一种枚举类型。

fn main() {
    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
}

二、使用字符串存储UTF-8编码的文本

在Rust中使用字符串时出现错误,可能时由3个因素共同造成的:

  1. Rust倾向于暴露可能的错误;
  2. 字符串时一个复杂的数据结构;
  3. Rust中的字符串使用了UTF-8编码;
    字符串本身就是基于字节的集合,并通过功能性的方法将字节解析为文本。

1、字符串是什么?

Rust在语言核心部分只有一种字符串类型,那就是字符串切片str,它通常以借用的形式(&str)出现。字符串切片是一些指向存储在别处的UTF-8编码字符串的引用。
String类型被定义在了Rust标准库中而没有被内置在语言的核心部分。当Rust开发者提到”字符串“时,他们通常指的是String类型与&str字符切片。

2、创建一个新的字符串

fn main() {
    let mut s = String::new(); 
}

这行代码创建了一个叫做s的空字符串,我们可以将数据填入字符串。通常字符串会有初始数据。对于这种情况我们可以对实现了Display trait的类型调用to_string方法。

fn main() {
    let data = "initial contents";

    let s = data.to_string();

    // 该方法也可直接用于字符串字面值:
    let s = "initial contents".to_string();
}

这段代码所创建的字符串会拥有”initial contents”作为内容。
记住,字符串是基于UTF-8编码的,我们可以将任何合法的数据编码进字符串中。

3、更新字符串

String的大小可以增减,其中的内容也可以修改,正如我们将数据推入其中时Vec<T>内部数据所发生的变化一样。此外,还可以使用+运算符或format!宏拼接String

1.使用push_strpush向字符串添加内容

使用push_str方法向String中添加一段字符串切片:

fn main() {
    let mut s = String::from("foo");
    s.push_str("bar");
}

上面代码执行完成后,s中的字符串将被更新为foobar。由于我们并不需要取得参数的所有权,所以这里的push_str方法只需要接收一个字符串切片作为参数。
在将字符串切片附加至String后继续使用变量s2

fn main() {
    let mut s1 = String::from("foo");
    let s2 = "bar";
    s1.push_str(s2);
    println!("s2 is {}", s2);
}

push方法接收单个字符作为参数,并将它添加到String中。

fn main() {
    let mut s = String::from("lo");
    s.push('l');
    println!("{}", s);
}

2.使用+运算符或format!宏拼接字符串

使用+运算符将两个String之合并到一个新的String值中:

fn main() {
    let s1 = String::from("Hello, ");
    let s2 = String::from("World!");
    let s3 = s1 + &s2;

    println!("{}", s3);
}

执行完这段代码后,字符串s3中的内容会变为Hello, World!。值得我们注意的是,我们在加法操作中仅对s2采用了引用,而s1在加法操作之后则不再有效。产生这一现象的原因与使用+运算符时所调用的方法签名有关。

fn add(self, s: &str) -> String {

标准库中,add函数使用了泛型来进行定义。此处展示的add函数将泛型替换为了具体的类型,这是我们使用String值调用add时使用的签名。
首先,代码中的s2使用了&符号,实际上是将第二个字符串的引用与第一个字符串相加,但是只能将&str与String相加,而不能将两个String相加。我们能够使用&s2来调用add函数的原因在于:编译器可以自动将&String类型的参数强制转换为&str类型。当我们调用add函数时,Rust使用了一种被称作解引用强制转换的技术,将&2转换为了&s2[]..
其次,我们可以看到add函数签名中的self并没有&标记,所以add函数会取得self的所有权。这一意味着s1将被移动到add函数中调用,并在调用后失效。所以,即便let s3 = s1 + &s2;看起来像是复制两个字符串并创建一个新的字符串,但实际上这条语句会取得s1的所有权,再将s2中的内容复制到其中,最后再将s1的所有权作为结果返回。

4、字符串索引

Rust不允许我们通过索引来获得String中的字符。在Rust中,我们实际上可以通过3中不同的方式来看待字符串中的数据:字节、标量值和字形簇。

5、字符串切片

为了明确表明需要一个字符串切片,你需要在索引的[]中填写范围来指定所需的字节内容,而不是在[]中使用单个数字进行索引:

fn main() {
    let hello = "Здравствуйте";
    
    let s = &hello[0..4];
    println!("{}", s);
    }

s 会是一个 &str,它包含字符串的头四个字节。早些时候,我们提到了这些字母都占据两个字节,所以这意味着 s 将会是 “Зд”。

6、遍历字符串的方法

如果你想要对每一个Unicode标量值都进行处理,那么最好的办法就是使用chars方法。

fn main() {
    for c in "नमस्ते".chars() {
        println!("{}", c);
    }
}

另外使用bytes方法返回每一个原始字节,这可能会适合你的场景:

fn main() {
    for b in "नमस्ते".bytes() {
        println!("{}", b);
    }
}

三、使用Hash Map存储键值对

HashMap<K,V>存储了从K类型键到V类型值之间的映射关系。哈希映射在内部实现中使用了哈希函数,这同时决定了它在内存中存储键值对的方式。

1、创建一个新的哈希映射

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
}

注意,我们首先需要使用useHashMap从标准库的集合部分引入当前作用域。由于哈希映射没有被包含在预导入模块内,所以标准库对哈希映射的支持也不知另外两个集合。和动态数组一样,哈希映射也将数据存储在堆上。
另外一个构建哈希映射的方法是,在一个由键值对组成的元组动态数组上使用collect方法。这里的collect方法可以将数据收集到很多数据结构中,这些数据结构也包括HashMap

fn main() {
    use std::collections::HashMap;

    let teams = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];

    let mut scores: HashMap<_,_> =
        teams.into_iter().zip(initial_scores.into_iter()).collect();
}

2、哈希映射与所有权

对于那些实现了Copy trait的类型,它们的值会被简单地复制到哈希映射中,而对于String这种持有所有权地值,其值将会转移且所有权会转移给哈希映射。

fn main() {
    use std::collections::HashMap;

    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // 这里 field_name 和 field_value 不再有效,
    // 尝试使用它们看看会出现什么编译错误!
}

在调用insert方法后,field_namefield_value变量被移动到哈希映射中,我们没有办法使用这两个变量了。

3、访问哈希映射中的值

我们可以通过将键传入get方法来获得哈希映射中的值。

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    let team_name = String::from("Blue");
    let score = scores.get(&team_name);
}

上面的代码中的score将会是与蓝队相关联的值,也就是Some(&10)。因为get返回的是一个Option<&V>,所以这里的结果将被封装到了Some中;假如这个哈希映射中没有键所对应的值,那么get就会返回None。
类似于动态数组,我们同样可以使用一个for循环来遍历哈希映射中所有的键值对:

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
}

4、更新哈希映射

尽管键值对的数量是可以增长的,但是在任意时刻,每个键都只能对应一个值。

1.覆盖旧值

当我们将一个键值对插入哈希映射后,接着使用同样的键并配以不同的值来继续插入,之前的键所关联的值就会被替换掉。

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Blue"), 25);

    println!("{:?}", scores);
}

2.只在键没有对应值时插入数据

在实际工作中,我们需要检测一个键是否存在对应值,如果不存在,则为它插入一个值。哈希映射中提供了一个被称为entry的专用API来处理这种情形,它接收我们想要检测的键作为参数,并返回一个叫做Entry的枚举作为结果。

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

    scores.entry(String::from("Yellow")).or_insert(50);
    scores.entry(String::from("Blue")).or_insert(50);

    println!("{:?}", scores);
}

entry中的or_insert方法被定义为返回一个Entry键所指向值的可变引用,假如这个值不存在,就将参数作为新值插入哈希映射中,并把这个新值的可变引用返回。

3.基于旧值来更新值

哈希映射的另一个常见用法是查找某个键值所对应的值,并给予这个值来进行更新。

fn main() {
    use std::collections::HashMap;

    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{:?}", map);
}

4.哈希函数

HashMap默认使用一种叫做SipHash的哈希函数,它可以抵御设计哈希表的拒绝服务攻击。但是这不是最快的哈希算法,不过为了更高的安全性付出一些性能代价通常是值得的。

原文地址:http://www.cnblogs.com/ColoFly/p/16926638.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性