本文介绍: 不能这么做的原因是由于 vector工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配内存并将老的元素拷贝到新的空间中。宏,这个宏会根据我们提供的值来创建一个新的 vector。但是如果我们使用下面的方法,由于get方法返回值是Opton枚举的缘故,会返回None结果,不会产生报错,而是输出“There is no third element.”。在接下来示例中,为了更加清楚的说明我们已经标注了这些函数返回的值的类型

vector

写作vec,读作vector

主要特点:

创建vector

1. 使用new创建

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

新建一个空的 vector 来储存 i32 类型的值。

通常,我们会用初始值创建一个 Vec 而 Rust推断出储存值的类型,所以很少会需要这些类型注解。为了方便 Rust 提供了 vec! 宏,这个宏会根据我们提供的值来创建一个新的 vector

2. 使用vec宏来创建

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

通过使用vec!来实现创建初始值vector

因为我们提供了 i32 类型的初始值,Rust 可以推断v 的类型是 Vec,因此类型注解就不是必须的。接下来我们看看如何修改一个 vector

更改vector

增加元素

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

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

删除vector
对于vector删除就是超出vector作用域时,vector就会被删除,连带着其所有元素也会被删除

查找vector

有两种方法引用 vector 中储存的值:通过索引使用 get 方法。在接下来示例中,为了更加清楚的说明我们已经标注了这些函数返回的值的类型。

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

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

    let third: Option<&amp;i32&gt; = v.get(2);
    match third {
        Some(third) =&gt; println!("The third element is {third}"),
        None =&gt; println!("There is no third element."),
    }
}

上面是使用索引方式来查,下面是通过get方法。

索引超过了vector的范围,也就是发生了越界,那么我们运行程序,就会发生报错出现panic

但是如果我们使用下面的方法,由于get方法返回值是Opton枚举的缘故,会返回None的结果,不会产生报错,而是输出“There is no third element.”。

回忆一下不能在相同作用域中同时存在可变不可引用规则。当我们获取了 vector 的第一个元素的不可引用尝试在 vector 末尾增加一个元素的时候,如果尝试函数的后面引用这个元素是行不通的:

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

    let first = &amp;v[0];

    v.push(6);

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

代码看起来应该能够运行为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用指向了被释放的内存。借用规则阻止程序陷入这种状况。

遍历vector中的元素

寻常遍历

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

遍历可变vector并改变:

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

枚举存储多种类型

vector 只能储存相同类型的值。这是很不方便的;当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举

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),
    ];
}

String

新建字符串

    let mut s = String::new();

可以使用 to_string方法,它能用于任何实现Display trait 的类型,比如字符串字面值。

    let data = "initial contents";

    let s = data.to_string();

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

可以使用 String::from 函数来从字符串字面值创建 String。

	let s = String::from("initial contents");

更新字符串

使用 push_str 和 push 附加字符

    let mut s = String::from("foo");
    s.push_str("bar");

ush_str 方法采用字符slice,因为我们并不需要获取参数所有权
所以下面的代码通过编译

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

如果 push_str 方法获取了 s2 的所有权,就不能在最后一行打印出其值了。

push 方法被定义获取一个单独的字符作为参数,并附加到 String 中。

	let mut s = String::from("lo");
    s.push('l');

s最后包含“lol”。

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

    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &amp;s2; // 注意 s1 被移动了,不能继续使用

执行完这些代码之后,字符串 s3 将会包含 Hello, world!。s1 在相加后不再有效的原因,和使用 s2 的引用的原因,与使用 + 运算符调用的函数签名有关。+ 运算符使用了 add 函数,这个函数签名看起来像这样:

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

索引字符

Rust 的字符串不支持索引。原因我就省略了,追根求源点这里–>使用字符串存储UTF-8编码的文本

字符串Slice

let hello = "Здравствуйте";

let s = &amp;hello[0..4];

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

如果获取 &hello[0..1] 会发生什么呢?答案是:Rust 在运行时会 panic,就跟访问 vector 中的无效索引时一样:

$ cargo run
   Compiling collections v0.1.0 (file:///projects/collections)
    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
     Running `target/debug/collections`
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/main.rs:4:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

应该小心谨慎地使用这个操作,因为这么做可能会使你的程序崩溃

遍历字符串

操作字符串每一部分的最好的方法是明确表示需要字符还是字节。对于单独的 Unicode 标量值使用 chars 方法。对 “Зд” 调用 chars 方法会将其分开并返回两个 char 类型的值,接着就可以遍历其结果访问每一个元素了:

#![allow(unused)]
fn main() {
	for c in "Зд".chars() {
	    println!("{c}");
	}
}

这些代码打印如下内容

З
д

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

for b in "Зд".bytes() {
    println!("{b}");
}

这些代码打印出组成 String 的 4 个字节:

208
151
208
180

不过请记住有效Unicode标量值可能会由不止一个字节组成。

从字符串中获取如同天城文这样的字形簇是很复杂的,所以标准库并没有提供这个功能crates.io 上有些提供这样功能crate。

HashMap

创建HashMap

    use std::collections::HashMap;
    let mut scores = HashMap::new();

插入键值

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

查找索引

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).copied().unwrap_or(0);
}

get 方法返回 Option<&V>,如果某个键在哈希 map 中没有对应的值,get 会返回 None程序通过调用 copied 方法来获取一个 Option<i32> 而不是 Option<&i32>,接着调用 unwrap_orscores 中没有该键所对应的项时将其设置为零。

遍历查找

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}");
    }
}

哈希 map 和所有权

对于像 i32 这样的实现Copy trait 的类型,其值可以拷贝哈希 map。对于像 String 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者,如示例所示

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_namefield_value 不再有效
    // 尝试使用它们看看会出现什么编译错误
}

更新哈希 map

覆盖一个值

通过插入insert同一个键,就能对这个键覆盖

use std::collections::HashMap;

    let mut scores = HashMap::new();

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

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

只在键没有对应值时插入键值

entry 函数的返回值是一个枚举Entry,它代表可能存在也可能不存在的值。比如说我们想要检查黄队的键是否关联了一个值。如果没有,就插入值 50,对于蓝队也是如此。

	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);

输出结果

{"Yellow": 50, "Blue": 10}

根据旧值更新一个值

    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);

这会打印{"world": 2, "hello": 1, "wonderful": 1}。你可能会看到相同的键值对以不同顺序打印:回忆一下“访问哈希 map 中的值”部分中遍历哈希 map 会以任意顺序进行。

split_whitespace 方法返回一个由空格分隔 text 值子 slice迭代器。or_insert 方法返回这个键的值的一个可变引用(&mut V)。这里我们将这个可变引用存在 count 变量中,所以为了赋值必须首先使用星号(*)解引用 count。这个可变引用在 for 循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则

原文地址:https://blog.csdn.net/zdsey/article/details/134697384

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_39748.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注