点击我的视频网校,学习编程课程,或者关注我的微信公众号“编程之路从0到1”,了解课程更新

Rust 标准库(五)

标准库

基础

字符串拼接

在系统编程语言中,字符串操作通常没有脚本语言那么简单,Rust 也不例外。有多种方法可以做到这一点,它们各自以不同的方式管理涉及的资源。

fn main() {
  by_moving();
  by_cloning();
  by_mutating();
}

fn by_moving() {
  let hello = "hello ".to_string();
  let world = "world!";

  // 将 hello 移入新变量
  let hello_world = hello + world;
  // hello 不能再使用了
  println!("{}", hello_world); // 打印 "hello world!"
}

fn by_cloning() {
  let hello = "hello ".to_string();
  let world = "world!";

  // 创建 hello 的副本并将其移入一个新变量中
  let hello_world = hello.clone() + world;
  // hello 仍可使用
  println!("{}", hello_world); // 打印 "hello world!"
}

fn by_mutating() {
  let mut hello = "hello ".to_string();
  let world = "world!";

  // hello 被适当修改
  hello.push_str(world);
  // hello 既可用又可修改
  println!("{}", hello); // 打印 "hello world!"
}

在所有的函数中,我们首先为一个长度可变的字符串分配内存。通过创建一个字符串切片(&str)并在其上调用 to_string 函数来实现这一点。

在Rust中连接字符串的一种方法,如by_moving函数所示,是将上述分配的内存,连同一个额外的字符串切片,移入一个新的变量。用+运算符连接,非常简单明了。但这种方法是有缺点的,hello在第12行之后就不能使用了,因为它被移动了。

by_cloning看起来与第一个函数几乎相同,但它将分配的字符串克隆到一个临时对象中,在进程中分配新的内存,然后将其移动,使原始的 hello 保持不变并且仍然可以访问。当然,这样做的代价是在运行时进行多余的内存分配。

by_mutating是解决我们问题的有状态方法。它在原来的地方执行所涉及的内存管理,这意味着性能应该与 by_moving 中的相同。最后,它使hello可变,为进一步的改变做好准备。这个函数看起来不那么优雅,因为它没有使用 + 运算符。这是有意为之,因为 Rust 试图推动您通过其设计来移动数据,以便在不改变现有变量的情况下创建新变量。

使用 format!

还有一种组合字符串的方法,也可用于将字符串与其他数据类型(如数字)组合。

fn main() {
    let colour = "red";
    // '{}'会被参数替换。
    let favourite = format!("My favourite colour is {}", colour);
    println!("{}", favourite);

    // 可以添加多个参数
    let hello = "hello ";
    let world = "world!";
    let hello_world = format!("{}{}", hello, world);
    println!("{}", hello_world); 

    // format! 宏可以连接任何实现了'Display' trait的数据类型,例如数字
    let favourite_num = format!("My favourite number is {}", 42);
    println!("{}", favourite_num); // "My favourite number is 42

    // 如果要在字符串中多次包含某些参数,可以使用位置索引参数
    let duck_duck_goose = format!("{0}, {0}, {0}, {1}!", "duck", "goose");
    println!("{}", duck_duck_goose); // "duck, duck, duck, goose!"

    // 你还可以给参数命名
    let introduction = format!(
        "My name is {surname}, {forename} {surname}",
        surname = "Bond",
        forename = "James"
    );
    println!("{}", introduction) // "My name is Bond, James Bond"
}

提供一个默认实现

通常,当处理代表配置的结构时,你可能不关心某些值,只想默默地给它们分配一个标准值。

fn main() {
    // 几乎所有基元类型都有一个默认值
    let foo: i32 = Default::default();
    println!("foo: {}", foo); // 打印 "foo: 0"


    // 派生自 Default 的结构体可以这样初始化
    let pizza: PizzaConfig = Default::default();
    // 打印 "wants_cheese: false
    println!("wants_cheese: {}", pizza.wants_cheese);

    // 打印 "number_of_olives: 0"
    println!("number_of_olives: {}", pizza.number_of_olives);

    // 打印 "special_message: "
    println!("special message: {}", pizza.special_message);

    let crust_type = match pizza.crust_type {
        CrustType::Thin => "Nice and thin",
        CrustType::Thick => "Extra thick and extra filling",
    };
    // 打印 "crust_type: Nice and thin"
    println!("crust_type: {}", crust_type);


    // 也可以只配置某些值
    let custom_pizza = PizzaConfig {
        number_of_olives: 12,
        ..Default::default()
    };

    // 可以定义任意多的值
    let deluxe_custom_pizza = PizzaConfig {
        number_of_olives: 12,
        wants_cheese: true,
        special_message: "Will you marry me?".to_string(),
        ..Default::default()
    };

}

#[derive(Default)]
struct PizzaConfig {
    wants_cheese: bool,
    number_of_olives: i32,
    special_message: String,
    crust_type: CrustType,
}

// 可以为自己的类型轻松实现默认设置
enum CrustType {
    Thin,
    Thick,
}
impl Default for CrustType {
    fn default() -> CrustType {
        CrustType::Thin
    }
}

Rust 中几乎每种类型都有一个 Default 实现。当你定义自己的结构体,而且仅包含已经拥有Default的元素时,你可以选择派生自Default 。对于枚举或复杂结构体,你可以轻松编写自己的 Default 实现,因为你只需提供一个方法。在此之后,通过Default::default()返回的结构体隐式地被推断为是你的类型,前提是你告诉编译器你的类型实际是什么。这就是为什么在第[3]行我们必须写foo: i32,否则Rust将不知道默认对象实际上应该成为什么类型。。

如果你只想指定某些元素,而让其他元素保持默认,可以使用第 [29] 行中的语法。请记住,你可以根据需要配置和跳过任意多个值。

使用构造函数模式

你可能会问,Rust 没有构造函数,如何在 Rust 中惯用地初始化复杂的结构体呢?答案很简单,构造函数是存在的,它只是一种约定而非规则。Rust 的标准库经常使用这种模式,因此如果我们想有效地使用 std,就必须了解它。

fn main() {
    // 我们不需要关心NameLength的内部结构,
    // 只需调用它的构造函数即可
    let name_length = NameLength::new("John");

    // 打印 "The name 'John' is '4' characters long"
    name_length.print();
}

struct NameLength {
    name: String,
    length: usize,
}

impl NameLength {
    // 调用者无需设置长度
    // 我们来做这件事
    fn new(name: &str) -> Self {
        NameLength {
            length: name.len(),
            name.to_string(),
        }
    }

    fn print(&self) {
        println!(
            "The name '{}' is '{}' characters long",
            self.name,
            self.length
        );
    }
}

如果一个结构体提供了一个返回 Selfnew 方法,那么该结构体的用户将不会配置或依赖该结构体的成员,因为它们被认为处于内部隐藏状态。

换句话说,如果你看到一个结构体有一个 new 函数,那么一定要使用它来创建结构体。
这样做的好处是,可以随心所欲地更改结构体的成员,而调用者却不会察觉到任何变化,因为他们无论如何都不应该查看这些成员。

使用这种模式的另一个原因是引导调用者以正确的方式实例化结构体。如果一个结构体中只有一大串必须填入值的成员,那么用户可能会感到有些迷茫。然而,如果一个方法只有几个自描述的参数,就会让人感觉更有吸引力。

有时,你的结构体可能需要不止一种初始化方法。在这种情况下,请尽量提供一个 new() 方法作为默认的构造方法,并根据其他选项与默认方法的不同来命名它们。Vector就是一个很好的例子,它不仅提供了一个 Vec::new() 构造函数,还提供了一个 Vec::with_capacity(10),用于为 10 个项初始化足够的空间。

当接受一种字符串(&str,即借用的字符串片段,或 String,即拥有的字符串)并计划将其存储在结构体中时,就像我们在示例中做的那样,也要考虑使用 Cow。不,不是指大型的产奶动物,朋友们。在Rust中,Cow是一个写时复制(Clone On Write)的包装类型,这意味着它会尽可能长时间地借用一个类型,只有在绝对必要的时候才会克隆数据,这在第一次变异时发生。。这样做的实际效果是,如果我们以下面的方式重写 NameLength 结构,它就不会关心调用者传递给它的是 &str 还是 String,而是会尽量以最高效的方式工作:

use std::borrow::Cow;
struct NameLength<'a> {
    name: Cow<'a, str>,
    length: usize,
}

impl<'a> NameLength<'a> {
    fn new<S>(name: S) -> Self
    where
        S: Into<Cow<'a, str>>,
    {
        let name: Cow<'a, str> = name.into();
        NameLength {
            length: name.len(),
            name,
        }
    }

    fn print(&self) {
        println!(
            "The name '{}' is '{}' characters long",
            self.name, self.length
        );
    }
}

使用构建者模式

有时你需要的是介于构造函数的定制化和默认实现的隐含性之间的东西。这时就可以使用构建者模式,这是Rust标准库经常使用的另一种技术,因为它允许调用者流畅地链接起他们关心的配置,并让他们忽略他们不关心的细节。

fn main() {
    // 我们可以轻松创建不同的配置
    let normal_burger = BurgerBuilder::new().build();
    let cheese_burger = BurgerBuilder::new().cheese(true).salad(false).build();
    let veggie_bigmac = BurgerBuilder::new().vegetarian(true).patty_count(2).build();

    if let Ok(normal_burger) = normal_burger {
        normal_burger.print();
    }
    if let Ok(cheese_burger) = cheese_burger {
        cheese_burger.print();
    }
    if let Ok(veggie_bigmac) = veggie_bigmac {
        veggie_bigmac.print();
    }

    //  我们的构建器可以检查无效配置
    let invalid_burger = BurgerBuilder::new().vegetarian(true).bacon(true).build();
    if let Err(error) = invalid_burger {
        println!("Failed to print burger: {}", error);
    }

    // 如果省略最后一步,我们就可以重复使用我们的构建器
    let cheese_burger_builder = BurgerBuilder::new().cheese(true);
    for i in 1..10 {
        let cheese_burger = cheese_burger_builder.build();
        if let Ok(cheese_burger) = cheese_burger {
            println!("cheese burger number {} is ready!", i);
            cheese_burger.print();
        }
    }
}

struct Burger {
    patty_count: i32,
    vegetarian: bool,
    cheese: bool,
    bacon: bool,
    salad: bool,
}
impl Burger {
    // 此方法仅供参考
    fn print(&self) {
        let pretty_patties = if self.patty_count == 1 {
            "patty"
        } else {
            "patties"
        };
        let pretty_bool = |val| if val { "" } else { "no " };
        let pretty_vegetarian = if self.vegetarian { "vegetarian " } else { "" };
        println!(
            "This is a {}burger with {} {}, {}cheese, {}bacon and {}salad",
            pretty_vegetarian,
            self.patty_count,
            pretty_patties,
            pretty_bool(self.cheese),
            pretty_bool(self.bacon),
            pretty_bool(self.salad)
        )
    }
}

struct BurgerBuilder {
    patty_count: i32,
    vegetarian: bool,
    cheese: bool,
    bacon: bool,
    salad: bool,
}
impl BurgerBuilder {
    // 在构造函数中,我们可以指定标准值
    fn new() -> Self {
        BurgerBuilder {
            patty_count: 1,
            vegetarian: false,
            cheese: false,
            bacon: false,
            salad: true,
        }
    }

    // 现在我们必须为每个可配置值定义一个方法
    fn patty_count(mut self, val: i32) -> Self {
        self.patty_count = val;
        self
    }

    fn vegetarian(mut self, val: bool) -> Self {
        self.vegetarian = val;
        self
    }
    fn cheese(mut self, val: bool) -> Self {
        self.cheese = val;
        self
    }
    fn bacon(mut self, val: bool) -> Self {
        self.bacon = val;
        self
    }
    fn salad(mut self, val: bool) -> Self {
        self.salad = val;
        self
    }

    // 最后一个方法实际上是在构建我们的对象
    fn build(&self) -> Result<Burger, String> {
        let burger = Burger {
            patty_count: self.patty_count,
            vegetarian: self.vegetarian,
            cheese: self.cheese,
            bacon: self.bacon,
            salad: self.salad,
        };
        // 检查配置是否无效
        if burger.vegetarian && burger.bacon {
            Err("Sorry, but we don't server vegetarian bacon yet".to_string())
        } else {
            Ok(burger)
        }
    }
}

在第一部分中,我们将说明如何使用这种模式毫不费力地配置一个复杂的对象。为此,我们将依赖合理的标准值,并只指定我们真正关心的内容:

let normal_burger = BurgerBuilder::new().build();
let cheese_burger = BurgerBuilder::new()
    .cheese(true)
    .salad(false)
    .build();
let veggie_bigmac = BurgerBuilder::new()
    .vegetarian(true)
    .patty_count(2)
    .build();

在我们的构建者模式版本中,我们返回一个包装在Result中的对象,以告诉世界存在某些无效配置,我们的构建者可能并不总是能够生成有效的产品。因此,我们必须在访问汉堡之前检查其有效性。

我们的无效配置是vegetarian(true)和bacon(true)。当你启动程序时,你会看到下面一行将打印错误信息:

if let Err(error) = invalid_burger {
    println!("Failed to print burger: {}", error);
}

通过简单线程实现并行

随着处理器的物理内核越来越多,并行性和并发性变得越来越重要。在大多数语言中,编写并行代码都很棘手。Rust 则不然,因为它从一开始就围绕着无畏并发的原则进行设计。

use std::thread;

fn main() {
    // 生成一个线程,让它执行一个 lambda
    let child = thread::spawn(|| println!("Hello from a new thread!"));
    println!("Hello from the main thread!");
    // 将子线程与主线程连接意味着主线程要等待子线程完成工作
    child.join().expect("Failed to join the child thread");

    let sum = parallel_sum(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
    println!("The sum of the numbers 1 to 10 is {}", sum);
}

// 编写一个函数,并行对切片中的数字求和
fn parallel_sum(range: &[i32]) -> i32 {
    // 我们将使用 4 个线程来对数字求和
    const NUM_THREADS: usize = 4;

    // 如果数量少于线程,那么对它们进行多线程处理就没有意义
    if range.len() < NUM_THREADS {
        sum_bucket(range)
    } else {
        // 将"bucket"定义为我们在单个线程中求和的数字量
        let bucket_size = range.len() / NUM_THREADS;
        let mut count = 0;
        // 该vector将跟踪我们的线程
        let mut threads = Vec::new();
        // 尝试在其他线程中尽可能多地求和
        while count + bucket_size < range.len() {
            let bucket = range[count..count + bucket_size].to_vec();
            let thread = thread::Builder::new()
                .name("calculation".to_string())
                .spawn(move || sum_bucket(&bucket))
                .expect("Failed to create the thread");
            threads.push(thread);

            count += bucket_size
        }
        // 我们将在主线程中总结其余部分
        let mut sum = sum_bucket(&range[count..]);

        // 是时候把结果加起来了
        for thread in threads {
            sum += thread.join().expect("Failed to join thread");
        }
        sum
    }
}

// 该函数将在线程中执行
fn sum_bucket(range: &[i32]) -> i32 {
    let mut sum = 0;
    for num in range {
        sum += *num;
    }
    sum
}

你可以调用 thread::spawn 创建一个新线程,然后开始执行提供的 lambda。这会返回一个 JoinHandle,你可以用它来加入线程。加入线程意味着等待线程完成工作。如果不加入线程,就无法保证线程能真正完成工作。不过,在设置线程来执行永远不会完成的任务(如监听传入连接)时,这可能是有效的。

请记住,你无法预先确定线程完成任何工作的顺序。在我们的示例中,我们无法预知是先打印新线程的 Hello!还是先打印主线程的 Hello!,不过大多数情况下可能是先打印主线程,因为操作系统需要花费一些精力来生成新线程。这就是为什么小型算法在不并行执行时速度会更快的原因。有时,让操作系统产生和管理新线程的开销并不值得。

从 stdin 读取数据

如果你想创建一个交互式应用程序,可以很容易地通过命令行创建功能原型。对于 CLI 程序来说,这就是你所需要的全部交互功能。

use std::io;
use std::io::prelude::*;

fn main() {
    print_single_line("Please enter your forename: ");
    let forename = read_line_iter();

    print_single_line("Please enter your surname: ");
    let surname = read_line_buffer();

    print_single_line("Please enter your age: ");
    let age = read_number();

    println!(
        "Hello, {} year old human named {} {}!",
        age, forename, surname
    );
}

fn print_single_line(text: &str) {
    // 我们可以打印不加换行符的行
    print!("{}", text);
    // 但是,我们需要随后刷新 stdout 以保证数据实际显示
    io::stdout().flush().expect("Failed to flush stdout");
}

fn read_line_iter() -> String {
    let stdin = io::stdin();
    // 以迭代器方式读取一行输入
    let input = stdin.lock().lines().next();
    input
        .expect("No lines in buffer")
        .expect("Failed to read line")
        .trim()
        .to_string()
}

fn read_line_buffer() -> String {
    // 按缓冲区风格读取一行输入
    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("Failed to read line");
    input.trim().to_string()
}

fn read_number() -> i32 {
    let stdin = io::stdin();
    loop {
        // 迭代所有将输入的行
        for line in stdin.lock().lines() {
            let input = line.expect("Failed to read line");
            // 尝试将字符串转换为数字
            match input.trim().parse::<i32>() {
                Ok(num) => return num,
                Err(e) => println!("Failed to read number: {}", e),
            }
        }
    }
}

为了从标准控制台输入(stdin)中读取数据,我们首先需要获得一个句柄。为此,我们需要调用 io::stdin() 。将返回的对象想象成对全局 stdin 对象的引用。这个全局缓冲区由一个 Mutex 管理,这意味着同一时间只有一个线程可以访问它。我们通过锁定(使用 lock())缓冲区来获得这种访问权,并返回一个新句柄。锁定后,我们可以调用 lines 方法,该方法会返回一个迭代器,遍历用户要写的行。

最后,我们可以遍历尽可能多的已提交行,直到达到某种中断条件为止,否则遍历将永远进行下去。在我们的例子中,一旦输入了一个有效的数字,我们就会中断数字检查循环。

如果我们对输入内容并不特别挑剔,只想要下一行,我们有两种选择:

  • 继续使用 lines() 提供的无限迭代器,但只需调用 next 即可获取第一行。这需要额外的错误检查,因为一般来说,我们无法保证存在下一个元素。

  • 可以使用 read_line 来填充现有的缓冲区。这并不需要我们先锁定处理程序,因为它是隐式完成的。

虽然两者的最终结果相同,但你应该选择第一个选项。它使用迭代器而不是可变状态,因此更易于维护和阅读。

集合

Vector

最基本的集合是向量,简称 Vec。它本质上是一个变长数组,开销非常低。因此,您在大多数情况下都会使用这种集合。

fn main() {
    // 创建一个vector 
    let fruits = vec!["apple", "tomato", "pear"];
    // 不能直接打印向量,但我们可以debug-print 
    println!("fruits: {:?}", fruits);

    // 创建一个空 vector 并填充它
    let mut fruits = Vec::new();
    fruits.push("apple");
    fruits.push("tomato");
    fruits.push("pear");
    println!("fruits: {:?}", fruits);

    // 删除最后一个元素
    let last = fruits.pop();
    if let Some(last) = last {
        println!("Removed {} from {:?}", last, fruits);
    }

    // 在指定索引处插入一个元素
    fruits.insert(1, "grape");
    println!("fruits after insertion: {:?}", fruits);

    // 交换两个元素
    fruits.swap(0, 1);
    println!("fruits after swap: {:?}", fruits);

    // 访问第一个和最后一个元素
    let first = fruits.first();
    if let Some(first) = first {
        println!("First fruit: {}", first);
    }
    let last = fruits.last();
    if let Some(last) = last {
        println!("Last fruit: {}", last);
    }

    // 访问任意元素
    let second = fruits.get(1);
    if let Some(second) = second {
        println!("Second fruit: {}", second);
    }
    // 访问任意元素,不需要检查返回值
    let second = fruits[1];
    println!("Second fruit: {}", second);

    // 用一个值初始化向量 
    // 这里,用五个零填充向量
    let bunch_of_zeroes = vec![0; 5];
    println!("bunch_of_zeroes: {:?}", bunch_of_zeroes);

    // 删除指定索引处的元素
    let mut nums = vec![1, 2, 3, 4];
    let second_num = nums.remove(1);
    println!("Removed {} from {:?}", second_num, nums);

    // 过滤 vector
    let mut names = vec!["Aaron", "Felicia", "Alex", "Daniel"];
    // 只保留以"A"开头的名字
    names.retain(|name| name.starts_with('A'));
    println!("Names starting with A: {:?}", names);

    // 检查vector是否包含元素
    println!("Does 'names' contain \"Alex\"? {}", names.contains(&"Alex"));

    // 删除连续的重复内容
    let mut nums = vec![1, 2, 2, 3, 4, 4, 4, 5];
    nums.dedup();
    println!("Deduped, pre-sorted nums: {:?}", nums);

    // 如果数据未排序,应小心使用
    let mut nums = vec![2, 1, 4, 2, 3, 5, 1, 2];
    nums.dedup();
    // 可能打印出来的不是你所期望的
    println!("Deduped, unsorted nums: {:?}", nums);

    // 进行排序
    nums.sort();
    println!("Manually sorted nums: {:?}", nums);
    nums.dedup();
    println!("Deduped, sorted nums: {:?}", nums);

    // 反转
    nums.reverse();
    println!("nums after being reversed: {:?}", nums);

    // 在一个区间上创建消费迭代器
    let mut alphabet = vec!['a', 'b', 'c'];
    print!("The first two letters of the alphabet are: ");
    for letter in alphabet.drain(..2) {
        print!("{} ", letter);
    }
    println!();
    // 被抽出的元素不再在vector中
    println!("alphabet after being drained: {:?}", alphabet);


    // 检查vector是否为空
    let mut fridge = vec!["Beer", "Leftovers", "Mayonaise"];
    println!("Is the fridge empty {}", fridge.is_empty());
    // 删除所有元素
    fridge.clear();
    println!("Is the fridge now empty? {}", fridge.is_empty());

    // 将一个vector分成两部分
    let mut colors = vec!["red", "green", "blue", "yellow"];
    println!("colors before splitting: {:?}", colors);
    let half = colors.len() / 2;
    let mut second_half = colors.split_off(half);
    println!("colors after splitting: {:?}", colors);
    println!("second_half: {:?}", second_half);

    // 将两个vector放到一起
    colors.append(&mut second_half);
    println!("colors after appending: {:?}", colors);
    // 这将清空第二个vector
    println!("second_half after appending: {:?}", second_half);

    // 拼接一个vector
    let mut stuff = vec!["1", "2", "3", "4", "5", "6"];
    println!("Original stuff: {:?}", stuff);
    let stuff_to_insert = vec!["a", "b", "c"];
    let removed_stuff: Vec<_> = stuff.splice(1..4, stuff_to_insert).collect();
    println!("Spliced stuff: {:?}", stuff);
    println!("Removed stuff: {:?}", removed_stuff);

    // 优化:初始化具有指定容量的vector
    let mut large_vec: Vec<i32> = Vec::with_capacity(1_000_000);
    println!("large_vec after creation:");
    println!("len:\t\t{}", large_vec.len());
    println!("capacity:\t{}", large_vec.capacity());

    // 尽可能地将vector缩小到接近其长度
    large_vec.shrink_to_fit();
    println!("large_vec after shrinking:");
    println!("len:\t\t{}", large_vec.len());
    println!("capacity:\t{}", large_vec.capacity());

    // 删除指定项,用最后一个替换它
    let mut nums = vec![1, 2, 3, 4];
    let second_num = nums.swap_remove(1);
    // 这会改变顺序,性能为O(1) 
    println!("Removed {} from {:?}", second_num, nums);
}

vector应该始终是首选集合。在内部,它被实现为一个存储在堆上的连续的内存块。

缩短vector时,额外的容量并没有消失。如果你有一个长度为10,000、容量为100,000的vector,并对其调用clear,你仍然会有100,000的预分配容量。当在有内存限制的系统上工作时,如微控制器,这可能成为一个问题。解决方案是定期对这些向量调shrink_to_fit。这将使容量尽可能地接近长度,但允许仍然留下一点预分配的空间。

另一种优化方法是调用swap_remove。通常,当从vector中移除一个元素时,后面的所有元素将向左移动,以保留连续的内存。当移除一个很大的vector中的第一个元素时,这是一个很耗时的工作。如果你不关心vector的顺序,则可以调用swap_remove而不是remove。它的工作原理是将要删除的元素与最后一个元素进行交换,并调整长度。这样就不会产生内存移动,仅交换内存是一个非常快的操作。

字符串

String 只是一种字符vector。Rust 为其字符串提供了异常庞大的功能。了解它可以让你在处理原始用户输入时少走弯路。

fn main() {
    // 由于 String 是一种vector,你可以用相同的方式构造它
    let mut s = String::new();
    s.push('H');
    s.push('i');
    println!("s: {}", s);

    // 然而,String也可以从字符串切片 (&str) 构造
    // 接下来的两种方法是等价的
    let s = "Hello".to_string();
    println!("s: {}", s);
    let s = String::from("Hello");
    println!("s: {}", s);

    // Rust 中的字符串总是有效的 UTF-8
    let s = "汉语 한글 Þjóðhildur 😉 🍺".to_string();
    println!("s: {}", s);
    
    // 将字符串相互附加
    let mut s = "Hello ".to_string();
    s.push_str("World");

    // 遍历字符
    // 此处将"character"定义为 Unicode 标量值
    for ch in "Tubular".chars() {
        print!("{}.", ch);
    }
    println!();
    // 但是要小心,"character"可能并不总是你所期望的
    for ch in "y̆".chars() {
        // 这不会打印 y̆
        print!("{} ", ch);
    }
    println!();

    // 以各种方式拆分字符串

    // 将一个字符串切片分成两半
    let (first, second) = "HelloThere".split_at(5);
    println!("first: {}, second: {}", first, second);

    // 在单独的行上拆分
    let haiku = "\
        she watches\n\
        satisfied after love\n\
        he lies\n\
        looking up at nothing\n\
    ";
    for line in haiku.lines() {
        println!("\t{}.", line);
    }

    // 拆分子串
    for s in "Never;Give;Up".split(';') {
        println!("{}", s);
    }
    // 当分割字符串在开头或结尾时,将导致空字符串
    let s: Vec<_> = "::Hi::There::".split("::").collect();
    println!("{:?}", s);
    
    // 可以使用 split_termitor 消除末尾的空字符串
    let s: Vec<_> = "Mr. T.".split_terminator('.').collect();
    println!("{:?}", s);

    // char 有一些方法可以用来拆分
    for s in "I'm2fast4you".split(char::is_numeric) {
        println!("{}", s);
    }

    // 只拆分一定的次数
    for s in "It's not your fault, it's mine".splitn(3, char::is_whitespace) {
        println!("{}", s);
    }

    // 只获取与模式匹配的子字符串,这与拆分相反
    for c in "The Dark Knight rises".matches(char::is_uppercase) {
        println!("{}", c);
    }

    // 检查字符串是否以某某开头
    let saying = "The early bird gets the worm";
    let starts_with_the = saying.starts_with("The");
    println!("Does \"{}\" start with \"The\"?: {}", saying, starts_with_the);
    let starts_with_bird = saying.starts_with("bird");
    println!("Does \"{}\" start with \"bird\"?: {}", saying, starts_with_bird);

    // 检查字符串是否以某某结尾
    let ends_with_worm = saying.ends_with("worm");
    println!("Does \"{}\" end with \"worm\"?: {}", saying, ends_with_worm);

    // 检查字符串是否包含某些内容
    let contains_bird = saying.contains("bird");
    println!("Does \"{}\" contain \"bird\"?: {}", saying, contains_bird);


    // 删除空格

    // 在空白处拆分可能不会产生期望的结果
    let a_lot_of_whitespace = "    I   love spaaace     ";
    let s: Vec<_> = a_lot_of_whitespace.split(' ').collect();
    println!("{:?}", s);
    // 改用 split_whitespace
    let s: Vec<_> = a_lot_of_whitespace.split_whitespace().collect();
    println!("{:?}", s);

    // 删除前后空格
    let username = "   P3ngu1n\n".trim();
    println!("{}", username);
    // 仅删除前面空格
    let username = "   P3ngu1n\n".trim_left();
    println!("{}", username);
    // 仅删除尾部空格
    let username = "   P3ngu1n\n".trim_right();
    println!("{}", username);


    // 将字符串解析为另一种数据类型
    // 这需要指定泛型
    let num = "12".parse::<i32>();
    if let Ok(num) = num {
        println!("{} * {} = {}", num, num, num * num);
    }

    // 修改字符串

    // 替换所有出现的模式
    let s = "My dad is the best dad";
    let new_s = s.replace("dad", "mom");
    println!("new_s: {}", new_s);

    // 用小写替换所有字符
    let lowercase = s.to_lowercase();
    println!("lowercase: {}", lowercase);

    // 用大写替换所有字符
    let uppercase = s.to_uppercase();
    println!("uppercase: {}", uppercase);

    // 这些也适用于其他语言
    let greek = "ὈΔΥΣΣΕΎΣ";
    println!("lowercase greek: {}", greek.to_lowercase());

    // 重复一个字符串
    let hello = "Hello! ";
    println!("Three times hello: {}", hello.repeat(3));
}

迭代器访问集合

fn main() {
    let names = vec!["Joe", "Miranda", "Alice"];
    // 可以通过多种方式访问迭代器
    // 几乎所有集合都实现了 .iter()
    let mut iter = names.iter();
    // 字符串本身是不可迭代的,但它的字符可以
    let mut alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars();
    // Range也是(有限的)迭代器
    let nums = 0..10;
    // 甚至可以创建无限迭代器
    let all_nums = 0..;

    // 你可以对迭代器进行迭代,这将消费迭代器
    for num in nums {
        print!("{} ", num);
    }
    // nums 不再可用
    println!();

    // 获取当前项的索引
    for (index, letter) in "abc".chars().enumerate() {
        println!("#{}. letter in the alphabet: {}", index + 1, letter);
    }

    // 通过迭代器,一步一步迭代
    if let Some(name) = iter.next() {
        println!("First name: {}", name);
    }
    if let Some(name) = iter.next() {
        println!("Second name: {}", name);
    }
    if let Some(name) = iter.next() {
        println!("Third name: {}", name);
    }
    if iter.next().is_none() {
        println!("No names left");
    }

    // 任意访问迭代器中的项
    let letter = alphabet.nth(3);
    if let Some(letter) = letter {
        println!("the fourth letter in the alphabet is: {}", letter);
    }
    // 这是通过消耗所有项直到指定位置
    let current_first = alphabet.nth(0);
    if let Some(current_first) = current_first {
        // 不会打印“A”
        println!(
            "The first item in the iterator is currently: {}",
            current_first
        );
    }

    // 访问最后一项;将消耗整个迭代器
    let last_letter = alphabet.last();
    if let Some(last_letter) = last_letter {
        println!("The last letter of the alphabet is: {}", last_letter);
    }

    // 将迭代器收集到集合中,这需要指定返回的集合类型
    // 下面两个是等价的:
    let nums: Vec<_> = (1..10).collect();
    println!("nums: {:?}", nums);
    let nums = (1..10).collect::<Vec<_>>();
    println!("nums: {:?}", nums);

    // 修改正在迭代的项

    // 只取前 n 项
    // 可以使无限迭代器有限
    let nums: Vec<_> = all_nums.take(5).collect();
    println!("The first five numbers are: {:?}", nums);

    // 跳过前n项
    let nums: Vec<_> = (0..11).skip(2).collect();
    println!("The last 8 letters in a range from zero to 10: {:?}", nums);

    // 使用take_while 和skip_while接受闭包
    let nums: Vec<_> = (0..).take_while(|x| x * x < 50).collect();
    println!(
        "All positive numbers that are less than 50 when squared: {:?}",
        nums
    );

    // 对于过滤已经排序的vector很有用
    let names = ["Alfred", "Andy", "Jose", "Luke"];
    let names: Vec<_> = names.iter().skip_while(|x| x.starts_with('A')).collect();
    println!("Names that don't start with 'A': {:?}", names);

    // 过滤迭代器
    let countries = [
        "U.S.A.", "Germany", "France", "Italy", "India", "Pakistan", "Burma"
    ];
    let countries_with_i: Vec<_> = countries
        .iter()
        .filter(|country| country.contains('i'))
        .collect();
    println!(
        "Countries containing the letter 'i': {:?}",
        countries_with_i
    );

    // 检查迭代器是否包含元素

    // 找到满足条件的第一个元素
    if let Some(country) = countries.iter().find(|country| country.starts_with('I')) {
        println!("First country starting with the letter 'I': {}", country);
    }

    // 不获取搜索的项,而是获取它的索引
    if let Some(pos) = countries
        .iter()
        .position(|country| country.starts_with('I'))
    {
        println!("It's index is: {}", pos);
    }

    // 检查是否至少有一项满足条件
    let are_any = countries.iter().any(|country| country.len() == 5);
    println!(
        "Is there at least one country that has exactly five letters? {}",
        are_any
    );

    // 检查所有项是否满足条件
    let are_all = countries.iter().all(|country| country.len() == 5);
    println!("Do all countries have exactly five letters? {}", are_all);

    // 数字项的有用操作
    let sum: i32 = (1..11).sum();
    let product: i32 = (1..11).product();
    println!(
        "When operating on the first ten positive numbers\n\
         their sum is {} and\n\
         their product is {}.",
        sum, product
    );

    let max = (1..11).max();
    let min = (1..11).min();
    if let Some(max) = max {
        println!("They have a highest number, and it is {}", max);
    }
    if let Some(min) = min {
        println!("They have a smallest number, and it is {}", min);
    }

    // 组合迭代器

    // 将迭代器与自身结合,使其无限
    // 当它走到尽头时,它又重新开始
    let some_numbers: Vec<_> = (1..4).cycle().take(10).collect();
    println!("some_numbers: {:?}", some_numbers);

    // 通过把两个迭代器放在另一个迭代器之后来合并它们
    let some_numbers: Vec<_> = (1..4).chain(10..14).collect();
    println!("some_numbers: {:?}", some_numbers);

    // 将两个迭代器压缩在一起,它们的第一个项分组到一起,第二项也在一起,以此类推
    let swiss_post_codes = [8957, 5000, 5034];
    let swiss_towns = ["Spreitenbach", "Aarau", "Suhr"];
    let zipped: Vec<_> = swiss_post_codes.iter().zip(swiss_towns.iter()).collect();
    println!("zipped: {:?}", zipped);

    // 因为 zip 是惰性的,所以你可以使用两个范围限制
    let zipped: Vec<_> = (b'A'..)
        .zip(1..)
        .take(10)
        .map(|(ch, num)| (ch as char, num))
        .collect();
    println!("zipped: {:?}", zipped);

    // 将函数应用于所有项

    // 更改项的类型
    let numbers_as_strings: Vec<_> = (1..11).map(|x| x.to_string()).collect();
    println!("numbers_as_strings: {:?}", numbers_as_strings);

    // 遍历所有项
    println!("First ten squares:");
    (1..11).for_each(|x| print!("{} ", x));
    println!();

    // 同时过滤和映射项
    let squares: Vec<_> = (1..50)
        .filter_map(|x| if x % 3 == 0 { Some(x * x) } else { None })
        .collect();
    println!(
        "Squares of all numbers under 50 that are divisible by 3: {:?}",
        squares
    );

    // 迭代器的真正优势来自于它们的组合

    // 检索整个字母表的小写和大写字母:
    let alphabet: Vec<_> = (b'A' .. b'z' + 1) // 从 u8 开始
        .map(|c| c as char)                   // 全部转换为字符
        .filter(|c| c.is_alphabetic())        // 仅过滤字母字符
        .collect();                           // 收集为 Vec<char>
    println!("alphabet: {:?}", alphabet);
}

iter()会创建一个借用元素项的迭代器。如果你想创建一个消耗元素项的迭代器,例如,通过移动它们来获得它们的所有权,你可以使用 into_iter()

HashMap

如果你把Vec想象成一个给数据分配索引(0, 1, 2等)的集合,那么HashMap就是一个可以将任何数据分配给任何数据的集合。它允许你将任意的、可哈希的数据映射到其他任意数据上。哈希和映射,这就是它名字的由来!

use std::collections::HashMap;

fn main() {
    // HashMap 可以将任何可散列类型映射到任何其他类型
    // 第一种类型称为"key",第二种称为"value"
    let mut tv_ratings = HashMap::new();
    // 这里,我们将 &str 映射到 i32
    tv_ratings.insert("The IT Crowd", 8);
    tv_ratings.insert("13 Reasons Why", 7);
    tv_ratings.insert("House of Cards", 9);
    tv_ratings.insert("Stranger Things", 8);
    tv_ratings.insert("Breaking Bad", 10);

    // 检查key是否存在
    let contains_tv_show = tv_ratings.contains_key("House of Cards");
    println!("Did we rate House of Cards? {}", contains_tv_show);
    let contains_tv_show = tv_ratings.contains_key("House");
    println!("Did we rate House? {}", contains_tv_show);

    // 访问value
    if let Some(rating) = tv_ratings.get("Breaking Bad") {
        println!("I rate Breaking Bad {} out of 10", rating);
    }

    // 给相同key插入不同的value,就会覆盖它
    let old_rating = tv_ratings.insert("13 Reasons Why", 9);
    if let Some(old_rating) = old_rating {
        println!("13 Reasons Why's old rating was {} out of 10", old_rating);
    }
    if let Some(rating) = tv_ratings.get("13 Reasons Why") {
        println!("But I changed my mind, it's now {} out of 10", rating);
    }

    // 删除一个key和它的value
    let removed_value = tv_ratings.remove("The IT Crowd");
    if let Some(removed_value) = removed_value {
        println!("The removed series had a rating of {}", removed_value);
    }

    // 迭代访问所有键和值
    println!("All ratings:");
    for (key, value) in &tv_ratings {
        println!("{}\t: {}", key, value);
    }

    // 还可以可变地迭代,迭代的时候修改
    println!("All ratings with 100 as a maximum:");
    for (key, value) in &mut tv_ratings {
        *value *= 10;
        println!("{}\t: {}", key, value);
    }

    // 在不引用 HashMap 的情况下进行迭代会移动其内容
    for _ in tv_ratings {}
    // tv_ratings 不再可用

    // 与其他集合一样,可以预先分配容量大小以优化性能
    let mut age = HashMap::with_capacity(10);
    age.insert("Dory", 8);
    age.insert("Nemo", 3);
    age.insert("Merlin", 10);
    age.insert("Bruce", 9);

    // 迭代所有 key
    println!("All names:");
    for name in age.keys() {
        println!("{}", name);
    }

    // 迭代所有 value
    println!("All ages:");
    for age in age.values() {
        println!("{}", age);
    }

    // 迭代所有值并修改
    println!("All ages in 10 years");
    for age in age.values_mut() {
        *age += 10;
        println!("{}", age);
    }

    // 使用entry 为尚未在 HashMap 中的值分配默认值
    {
        let age_of_coral = age.entry("coral").or_insert(11);
        println!("age_of_coral: {}", age_of_coral);
    }
    let age_of_coral = age.entry("coral").or_insert(15);
    println!("age_of_coral: {}", age_of_coral);
}

HashSet

描述 HashSet 的最佳方式是描述它的实现方式:HashMap<K, ()> 。它只是一个没有任何值的 HashMap

选择 HashSet 的两个最佳理由是:

  • 你不想处理重复值,甚至不包含重复值
  • 计划进行大量 Item 查找。即 "我的集合是否包含这个特定Item "的问题。在vector中,这个问题是在O(n)的时间复杂度中完成的,而 HashSet 可以在O(1)。
use std::collections::HashSet;

fn main() {
    // HashSet的大部分接口与HashMap相同,只是没有处理值的方法
    let mut books = HashSet::new();
    books.insert("Harry Potter and the Philosopher's Stone");
    books.insert("The Name of the Wind");
    books.insert("A Game of Thrones");

    // HashSet会忽略重复的条目,但会返回条目是否为新条目
    let is_new = books.insert("The Lies of Locke Lamora");
    if is_new {
        println!("We've just added a new book!");
    }

    let is_new = books.insert("A Game of Thrones");
    if !is_new {
        println!("Sorry, we already had that book in store");
    }

    // 检查它是否包含一个键
    if !books.contains("The Doors of Stone") {
        println!("We sadly don't have that book yet");
    }

    // 删除条目
    let was_removed = books.remove("The Darkness that comes before");
    if !was_removed {
        println!("Couldn't remove book; We didn't have it to begin with");
    }
    let was_removed = books.remove("Harry Potter and the Philosopher's Stone");
    if was_removed {
        println!("Oops, we lost a book");
    }

    // 比较两个HashSet

    let one_to_five: HashSet<_> = (1..6).collect();
    let five_to_ten: HashSet<_> = (5..11).collect();
    let one_to_ten: HashSet<_> = (1..11).collect();
    let three_to_eight: HashSet<_> = (3..9).collect();

    // 检查两个HashSet是否没有共同的元素
    let is_disjoint = one_to_five.is_disjoint(&five_to_ten);
    println!(
        "is {:?} disjoint from {:?}?: {}",
        one_to_five,
        five_to_ten,
        is_disjoint
    );
    let is_disjoint = one_to_five.is_disjoint(&three_to_eight);
    println!(
        "is {:?} disjoint from {:?}?: {}",
        one_to_five,
        three_to_eight,
        is_disjoint
    );

    // 检查一个HashSet是否被另一个HashSet完全包含
    let is_subset = one_to_five.is_subset(&five_to_ten);
    println!(
        "is {:?} a subset of {:?}?: {}",
        one_to_five,
        five_to_ten,
        is_subset
    );
    let is_subset = one_to_five.is_subset(&one_to_ten);
    println!(
        "is {:?} a subset of {:?}?: {}",
        one_to_five,
        one_to_ten,
        is_subset
    );

    // 检查一个HashSet是否完全包含另一个HashSet
    let is_superset = three_to_eight.is_superset(&five_to_ten);
    println!(
        "is {:?} a superset of {:?}?: {}",
        three_to_eight,
        five_to_ten,
        is_superset
    );
    let is_superset = one_to_ten.is_superset(&five_to_ten);
    println!(
        "is {:?} a superset of {:?}?: {}",
        one_to_ten,
        five_to_ten,
        is_superset
    );

    // 以不同方式合并两个HashSet
    
    // 获取存在于第一个HashSet中但不在第二个HashSet中的值
    let difference = one_to_five.difference(&three_to_eight);
    println!(
        "The difference between {:?} and {:?} is {:?}",
        one_to_five,
        three_to_eight,
        difference
    );

    // 获取那些存在于任一HashSet中,但不同时存在于两个HashSet中的值
    let symmetric_difference = one_to_five.symmetric_difference(&three_to_eight);
    println!(
        "The symmetric difference between {:?} and {:?} is {:?}",
        one_to_five,
        three_to_eight,
        symmetric_difference
    );

    // 获取同时存在于两个HashSet中的值
    let intersection = one_to_five.intersection(&three_to_eight);
    println!(
        "The intersection difference between {:?} and {:?} is {:?}",
        one_to_five,
        three_to_eight,
        intersection
    );

    // 获取两个HashSet中的所有值
    let union = one_to_five.union(&three_to_eight);
    println!(
        "The union difference between {:?} and {:?} is {:?}",
        one_to_five,
        three_to_eight,
        union
    );
}

关注公众号:编程之路从0到1

了解更多技术干货

编程之路从0到1

# Rust  

评论

公众号:编程之路从0到1

公众号

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×