Rust 常用集合(上)

目录

1、使用 Vector 储存列表

1.1 新建 vector

1.2 更新 vector

1.3 读取 vector 的元素

1.4 遍历 vector 中的元素

1.5 使用枚举来储存多种类型

1.6 丢弃 vector 时也会丢弃其所有元素

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

2.1 什么是字符串?

2.2 新建字符串

2.3 更新字符串

2.3.1 使用 push_str 和 push 附加字符串

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

2.3.3 索引字符串

2.3.4 字符串 slice

2.3.5 遍历字符串的方法

2.3.6 字符串并不简单


Rust 标准库中包含一系列被称为 集合collections)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和成本,而根据当前情况选择合适的集合,以下三种是在 Rust 程序中被广泛使用的集合;

  • vector 允许我们一个挨着一个地储存一系列数量可变的值
  • 字符串string)是字符的集合。我们之前见过 String 类型,不过在本章我们将深入了解。
  • 哈希 maphash map)允许我们将值与一个特定的键(key)相关联。这是一个叫做 map 的更通用的数据结构的特定实现。

1、使用 Vector 储存列表

我们要讲到的第一个类型是 Vec<T>,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。

1.1 新建 vector

为了创建一个新的空 vector,可以调用 Vec::new 函数,如下所示:

let v: Vec<i32> = Vec::new();

这里,定义了一个变量v,是vector类型,类型后面使用一对尖括号来表示Vector类型的泛型,在这里指定的是i32类型,所以变量v存储的是一组i32组成的集合。

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

fn main() {
    let v = vec![1, 2, 3, 4, 5, 6];
    println!("{:?}", v.len()) // 6
}

因为我们提供了 i32 类型的初始值,Rust 可以推断出 v 的类型是 Vec<i32>,因此类型声明就不是必须的。

1.2 更新 vector

对于新建一个 vector 并向其增加元素,可以使用 push 方法

fn main() {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    println!("{:?}", v.len()) // 3
}

1.3 读取 vector 的元素

fn main() {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    let first = &v[0];
    println!("{:?}", first); // 3
    println!("{:?}", &v[100]); // index out of bounds:
    println!("{:?}", v.get(1)); // Some(2)
}

这里有几个细节需要注意。我们使用索引值 2 来获取第三个元素,因为索引是从数字 0 开始的。使用 & 和 [] 会得到一个索引位置元素的引用。当使用索引作为参数调用 get 方法时,会得到一个可以用于 match 的 Option<&T>

当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面引用这个元素是行不通的:

fn main() {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    let first = &v[0];
    v.push(100); // 报错......
    println!("{:?}", first); // 3
    println!("{:?}", &v[100]); // index out of bounds:
    println!("{:?}", v.get(1)); // Some(2)
}

会抛出如下错误:

所以在 vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。

1.4 遍历 vector 中的元素

如果想要依次访问 vector 中的每一个元素,我们可以遍历其所有的元素而无需通过索引一次一个的访问。如下所示:

fn main() {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    for i in &v {
        println!("{}", i)
    }
}

我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变它们。如下所示:

fn main() {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    for i in &mut v {
        *i *= 10;
    }
    println!("{:?}", v) // [10, 20, 30]
}

修改可变引用所指向的值,在使用 *= 运算符之前必须使用解引用运算符(*)获取 i 中的值。

1.5 使用枚举来储存多种类型

vector 只能储存相同类型的值。这是很不方便的;绝对会有需要储存一系列不同类型的值的用例。幸运的是,枚举的成员都被定义为相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举!看一下下面这个示例:

fn main() {
    #[derive(Debug)]
    enum Value {
        Int(i32),
        Float(f32),
        Text(String),
        Valid(bool),
    }

    let mut v = Vec::new();
    v.push(Value::Int(1));
    v.push(Value::Float(1.1));
    v.push(Value::Text(String::from("wangwu")));
    v.push(Value::Valid(true));
    println!("{:?}", v) // [Int(1), Float(1.1), Text("wangwu"), Valid(true)]
}

Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。

1.6 丢弃 vector 时也会丢弃其所有元素

类似于任何其他的 struct,vector 在其离开作用域时会被释放,如下所示:

{
    let v = vec![1, 2, 3, 4];

    // todo
} // 超出作用域,v被丢弃,对应的元素值也会被丢弃

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

2.1 什么是字符串?

在开始深入这些方面之前,我们需要讨论一下术语 字符串 的具体意义。Rust 的核心语言中只有一种字符串类型:字符串 slice str,它通常以被借用的形式出现,&str。第四章讲到了 字符串 slices:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。

字符串(String)类型由 Rust 标准库提供,而不是编入核心语言,它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型。当 Rustaceans 提及 Rust 中的 "字符串 "时,他们可能指的是 String 或 string slice &str 类型,而不仅仅是其中一种类型。虽然本节主要讨论 String,但这两种类型在 Rust 的标准库中都有大量使用,而且 String 和 字符串 slices 都是 UTF-8 编码的。

2.2 新建字符串

很多 Vec 可用的操作在 String 中同样可用,事实上 String 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于 Vec<T> 和 String 函数的例子是用来新建一个实例的 new 函数,如下所示:

    let mut s = String::new();

上面创建了一个叫做 s 的空的字符串,接着我们可以向其中装载数据。通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 to_string 方法,它能用于任何实现了 Display trait 的类型,比如字符串字面值。如下所示:

fn main() {
    let data = "test string";

    let s = data.to_string();

    // 该方法也可直接用于字符串字面值:
    let s = "test string".to_string();
    println!("{:?}", s) // test string
}

也可以使用 String::from 函数来从字符串字面值创建 String。如下所示:

let data = String::from("test string");

2.3 更新字符串

String 的大小可以增加,其内容也可以改变,就像可以放入更多数据来改变 Vec 的内容一样。另外,可以方便的使用 + 运算符或 format! 宏来拼接 String 值。

2.3.1 使用 push_str 和 push 附加字符串

可以通过 push_str 方法来附加字符串 slice,从而使 String 变长,如下所示:

fn main() {
    let mut s = String::from("hello ");
    s.push_str("world");
    println!("{}", s) // hello world
}

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

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

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

通常你会希望将两个已知的字符串合并在一起。一种办法是像这样使用 + 运算符,如下所示:

fn main() {
    let h = String::from("hello ");
    let w = String::from("world");
    let res = h + &w;
    println!("{res}"); // hello world
}

执行完代码之后,h在相加之后不再有效,+运算符调用时跟函数签名有关,+运算符使用了add函数,这个函数看起来像这样:

    fn add(mut self, other: &str) -> String {
        self.push_str(other);
        self
    }

首先,w 使用了 &,意味着我们使用第二个字符串的 引用 与第一个字符串相加。这是因为 add 函数的 s 参数:只能将 &str 和 String 相加,不能将两个 String 值相加。不过等一下 —— &w的类型是 &String, 而不是 add 第二个参数所指定的 &str。那么为什么还能编译呢?

之所以能够在 add 调用中使用 &w 是因为 &String 可以被 强转coerced)成 &str。当add函数被调用时,Rust 使用了一个被称为 Deref 强制转换deref coercion)的技术,你可以将其理解为它把 &w 变成了 &w[..]。因为 add 没有获取参数的所有权,所以 w 在这个操作后仍然是有效的 String

其次,可以发现签名中 add 获取了 self 的所有权,因为 self 没有 使用 &。这意味着示例 8-18 中的 h 的所有权将被移动到 add 调用中,之后就不再有效。所以虽然 let res = h + &w; 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 h 的所有权,附加上从 w 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。

如果想要级联多个字符串,+ 的行为就显得笨重了:

fn main() {
    let h = String::from("hello ");
    let w = String::from("world");
    let t = String::from(", 123");
    let res = h + &w + &t;
    println!("{res}"); // hello world, 123
}

对于更为复杂的字符串链接,我们可以使用 format! 宏:

fn main() {
    let h = String::from("hello ");
    let w = String::from("world");
    let t = String::from(", 123");
    println!("{h}{w}{t}"); // hello world, 123
}

2.3.3 索引字符串

在很多语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果你尝试使用索引语法访问 String 的一部分,会出现一个错误。

fn main() {
    let h = String::from("hello ");
    let res = h[0];
}

我们可以看下字符串的实现。

pub struct String {
    vec: Vec<u8>,
}

所以在获取索引的时候,并不会返回我们所期望的第一个字母,而在Rust在编译过程就会阻止,并报错。

2.3.4 字符串 slice

字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 [] 和单个值的索引,可以使用 [] 和一个 range 来创建含特定字节的字符串 slice:

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

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

2.3.5 遍历字符串的方法

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

fn main() {
    let str = "Здравствуйте";
    for i in str.chars() {
        println!("{i}") // З д р ...
    }
}

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

fn main() {
    let str = "Здравствуйте";
    for i in str.bytes() {
        println!("{i}") // 208 151 ...
    }
}

2.3.6 字符串并不简单

总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理 String 数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。

好消息是标准库提供了很多围绕 String 和 &str 构建的功能,来帮助我们正确处理这些复杂场景。请务必查看这些使用方法的文档,例如 contains 来搜索一个字符串,和 replace 将字符串的一部分替换为另一个字符串。

称作 String 的类型是由标准库提供的,而没有写进核心语言部分,它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 String 或字符串 slice &str 类型,而不特指其中某一个。虽然本部分内容大多是关于 String 的,不过这两个类型在 Rust 标准库中都被广泛使用,String 和字符串 slices 都是 UTF-8 编码的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/303622.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

tiktok云手机有用吗?用哪个好?

很多做独立站的跨境卖家都会搭配一些社媒平台给自己引流带货&#xff0c;比如说目前很火的TikTok&#xff0c;这也是目前比较有效的一种引流方式。本文将介绍tiktok运营方法以及如何用tiktok云手机规避运营风险。 TikTok是个不错的风口&#xff0c;不过我们在国内想要运营好Tik…

如何在CentOS安装SQL Server数据库并通过内网穿透工具实现公网访问

文章目录 前言1. 安装sql server2. 局域网测试连接3. 安装cpolar内网穿透4. 将sqlserver映射到公网5. 公网远程连接6.固定连接公网地址7.使用固定公网地址连接 前言 简单几步实现在Linux centos环境下安装部署sql server数据库&#xff0c;并结合cpolar内网穿透工具&#xff0…

第19课 在Android环境中使用FFmpeg和openCV进行开发的一般步骤

在上节课&#xff0c;根据模板文件我们对在Android环境中使用FFmpeg和openCV进行开发有了一个初步的体验&#xff0c;这节课&#xff0c;我们来具体看一下其工作流程。 1.程序的入口 与VS2013程序开发类似&#xff0c;Android程序开发也有一个入口&#xff0c;在这个模板中&a…

哈希-力扣202快乐数

题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&…

lm358充电器应用电路

什么是LM358 LM358是双运算放大器。内部包括有两个独立的、高增益、内部频率补偿的运算放大器&#xff0c;适合于电源电压范围很宽的单电源使用&#xff0c;也适用于双电源工作模式&#xff0c;在推荐的工作条件下&#xff0c;电源电流与电源电压无关。它的使用范围包括传感放…

「PyMuPDF 专栏 」PyMuPDF创建PDF、拆分PDF

文章目录 一、本章前言二、使用PyMuPDF创建PDF文档1、实例代码2、过程详解①. 安装PyMuPDF②. 导入PyMuPDF模块③. 创建一个新的PDF文档④. 添加页面和内容⑤. 保存文档 三、使用PyMuPDF拆分PDF文档1、实例代码2、过程解析①. 导入模块②. 定义函数③. 打开源PDF文件④. 遍历页…

数字后端设计实现之自动化useful skew技术(Concurrent Clock Data)

在数字IC后端设计实现过程中&#xff0c;我们一直强调做时钟树综合要把clock skew做到最小。原因是clock skew的存在对整体设计的timing是不利的。 但是具体到某些timing path&#xff0c;可能它的local clock skew对timing是有帮助的&#xff0c;比如如下图所示。 第一级FF到第…

C++面试宝典第16题:盛最多水的容器

题目 给定n个非负整数a1、a2、…、an,每个数代表坐标中的一个点(i, ai)。画n条垂直线,使得第i条垂直线的两个端点分别为(i, ai)和(i, 0)。找出其中的两条线,使得它们与x轴共同构成的容器可以容纳最多的水。说明:不能倾斜容器,且n的取值至少为2。 在下图中,垂直线代表的输…

C++——冒泡排序

作用&#xff1a;最常用的排序算法&#xff0c;对数组内元素进行排序 1&#xff0c;比较相邻的元素&#xff0c;如果第一个比第二个大&#xff0c;就交换他们两个。 2&#xff0c;对每一对相邻元素做同样的工作&#xff0c;执行完毕后&#xff0c;找到第一个最大值。 3&…

RHCE9学习指南 第16章 访问NFS存储及自动挂载

16.1 访问NFS存储 前面介绍了使用本地存储&#xff0c;本章介绍使用网络上的存储设备。NFS全称是网络文件系统&#xff0c;所实现的是Linux和Linux之间的共享。 下面的练习我们将会在server上创建一个文件夹/share&#xff0c;然后通过NFS把它共享&#xff0c;然后在server2上…

LUT预设.cube格式PR/达芬奇/FCP/剪映等视频电影调色预设LUTs

对于将标准镜头转换为让人想起高端电影的视觉冲击场景至关重要。这些LUT经过专业设计&#xff0c;以模仿电影行业中的电影质量、深度和情感&#xff0c;使其成为电影制作人、摄像师和内容创作者的理想选择&#xff0c;希望为你的作品带来专业的电影色彩。 电影LUT的类别&#…

鸿蒙系统应用开发之开发准备

今天我们来聊一聊鸿蒙系统应用开发之前&#xff0c;要做什么准备工作&#xff0c;如下图所示&#xff0c;我们要做的就是安装DevEco Studio&#xff0c;然后配置开发环境。 老规矩&#xff0c;拍拍手&#x1f44f;&#xff0c;上菜。 安装DevEco Studio 首先我们打开链接HUAWEI…

学习笔记 | Activiti7

什么是工作流&#xff1f; 业务流程。 举个例子: 假设有一个在线博客平台&#xff0c;我们要让一篇新的文章从作者的头脑里发表出来。整个过程可以分为以下几个步骤&#xff1a; 创建文章草稿 &#xff1a;作者登录博客平台&#xff0c;点击“写新文章”的按钮&#xff0c…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)HttpResponse的定义和初始化 以及组织 HttpResponse 响应消息

一、HttpResponse的定义 1.定义状态码枚举 // 定义状态码枚举 enum HttpStatusCode {Unknown 0,OK 200,MovedPermanently 301,MovedTemporarily 302,BadRequest 400,NotFound 404 }; 2.HTTP 响应报文格式 这个数据块主要是分为四部分 第一部分是状态行第二部分是响应…

docker swarm 常用命令简介以及使用案例

docker swarm Docker Swarm 是Docker官⽅的跨节点的容器编排⼯具。⽤户只需要在单⼀的管理节点上操作&#xff0c;即可管理集群下的所有节点和容器 解决的问题 解决docker server的集群化管理和部署Swarm通过对Docker宿主机上添加的标签信息来将宿主机资源进⾏细粒度分区&am…

vue-cli项目优化gzip实践

背景&#xff1a;某天测试小妹气冲冲跑过来说你的网站首次打开平均16秒&#xff0c;慢得不行啊&#xff0c;空白时间太久&#xff0c;这样客户是不收货的&#xff0c;必须优化。谁叫我们是以测试驱动开发的&#xff0c;测试妹子的话等同与老板的命令。 空白是吧&#xff0c;我…

[Kubernetes]4. 借助腾讯云TKE快速创建Pod、Deployment、Service部署k8s项目

前面讲解了通过命令行方式来部署k8s项目,下面来讲讲通过腾讯云TKE来快速创建Pod、Deployment、Service部署k8s项目,云平台搭建Kubernetes可参考[Kubernetes]1.Kubernetes(K8S)介绍,基于腾讯云的K8S环境搭建集群以及裸机搭建K8S集群 一.通过腾讯云TKE创建集群 1.创建集群 参考上…

群辉安装gitea

群辉安装gitea 安装giteagitea容器配置 安装gitea gitea容器配置

【Internal Server Error】pycharm解决关闭flask端口依然占用问题

Internal Server Error The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application. 起因&#xff1a; 我们在运行flask后&#xff0c;断开服务依然保持运行&#xff0…

Meta的Fairy:快速并行化指令引导的视频到视频合成

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…