【未经书面同意,严禁转载】 -- 2020-10-14 --

架构是道,数据是术。道可道,非常道;术不名,不成术!道无常形,术却可循规。

学习与分析数据类型,最基本的方法就是搞清楚其存储原理,变量和对象数据是在栈、堆、静态区如何分布。把数据和数据的表现形式抓住了,就能很快地明白类型的行为、转换,还有Rust牵扯到所有权和所有权移动、借用。

书接上回!

数据类型上篇依次阐述了整型、浮点型、布尔、字符型、元组、指针、数组和向量。除了向量,其他都属于基本类型,在赋值、传参时是复制一份传递过去(即值传递),而向量是引用传递类型,赋值或传参的时候,传递的是存储在栈上的指针和相关信息,而数据本身在堆内存不动。

本篇关注一下最常用的另外几种类型:切片、字符串(以及文本字符串)。

切片Slice

切片其实不是真正的“数据”类型,而指的是别人的一段数据。顾名思义,切片就是从别的序列类型(如数组、向量等)上面切了一段的数据(当然,这一段还是属于别人的一部分,并没有切下)。

但这个切片,在实际代码中是没法用的,数据属于别人的,由于Rust的所有权概念(下篇讲这个),别人的东西不能直接拿来用。而是用另一种形式使用:引用。

现在,可以把引用理解为指针。

切片的类型写法是 [T],其中T是切片中元素数据的类型,可以是u8、String等等。切片引用的写法是&[T]。后者在代码中更常用。

而切片通常指的就是前文所说的切了一段数据的引用,切片的引用包含1个指针、1个数据长度(切片所切的元素数目)。比如:

let v: Vec<f64> = vec![0.0, 0.707, 1.0, 0.707]; //一个向量
let sv: &[f64] = &v[1..3]; //sv是切片引用,指向v的索引1的元素至索引3的元素(不包括索引3的元素),切片有2个元素

/* &v[1..3]中,中括号内的表达式由两个整数和一个..操作符构成,..操作符是range生成操作符,产生一个range类型(其实
根据两个整数是否省略分为4种类型,统称为range类型)的值。用于序列类型的后面,产生一个子序列。两个整数形成一个前闭
后开的区间,此例中是[1,3)。如果需要包含右边的整数索引,可以写成[1..=3];如果从切片是从开始元素到索引2,则可以
写成[..3];从索引2到末尾,写成[2..];取其所有元素,简写为[..]。
*/

其内存分布图为(图中sv中的数字2就是切片的元素数):

甚至可以把整个序列的引用赋值给一个切片引用:

let v: Vec<f64> = vec![0.0, 0.707, 1.0, 0.707]; //一个向量,数据在堆上
let a: [f64; 4] = [0.0, -0.707, -1.0, -0.707]; //一个数组,分布在栈上
let sv: &[f64] = &v; //sv是包含向量v所有元素的切片引用,由向量v的引用转换得到
let sa: &[f64] = &a; //sa是包含数组a所有元素的切片引用,由数组a的引用转换得到

在最后两行,Rust会自动将&Vec<f64>引用和&[f64;4]引用转换为直接指向数据的切片引用。

其内存分布图为:

想编写一个对任何同类型数据序列(无论是存储在数组、向量、堆栈或堆中)进行操作的函数时,切片引用都是一个不错的选择。例如,这里有一个函数可以打印一段数字,每行一个:

fn print(n: &[f64]) {
  for elt in n {
    println!("{}", elt);
  }
} print(&v); // works on vectors 可以用于vector
print(&a); // works on arrays 可以用于数组

因为这个函数接受切片引用作为参数,所以可以将它应用于向量或数组。实际上,Rust在切片上定义了很多方法:例如,sort和reverse方法,都可以运用在向量或数组上,对其元素进行排序或反转。

最后再重复一次,真正的切片是母序列的一段数据。由于切片几乎总是以引用出现,所以通常说的切片,实际上是切片引用&[T]。

字符串String 和文本字符串(或称为字符串字面量 英文为String Literal)

Rust中,最常用的两种字符串:字符串字面量str类型、可变字符串String类型。另外还有CString、OsString等,以后用到再说。

字符串字面量是指的一串文本,因为它是确定的,不像变量一样变化,所以在程序中硬编码到程序文件内,是静态的,我们不能移动或改变这串文本。字符串字面量用双引号括起来。特殊字符用反斜杠转义序列,也可以换行:

let speech = "\"Ouch!\" it's said the well.\n"; //单行文本,双引号需要转义,但单引号不需要

//多行文本,Singing后有一个空格和换行符,第二行的前面有多个空行。打印时这些都会保留
println!("In the room the women come and go, Singing
of Mount Abora");

//末尾有反斜杠转义,虽然第一行的末尾和第二行前面有多个空格、换行符,但是在and和there中间只输出一个空格!
println!("It was a bright, cold day in April, and \
there were four of us—\
more or less.");

在一些情况下,需要将字符串中的反斜杠转义是一种麻烦的事情(比如在正则表达式和Windows路径)。用小写字母“r”标记为原始字符串(raw string)。原始字符串中的所有反斜杠和空白字符都会逐字包含在字符串中。不会进行转义。但问题是,双引号就没法输入了(因为不存在转义,所以加反斜杠也没用),这时候可以在原始字符串的开始和结束加“#”号标记:

let default_win_install_path = r"C:\Program Files\Gorillas"; //Windows路径
let pattern = Regex::new(r"\d+(\.\d+)*"); //正则表达式 //用字母r和#表示的原始字符串,r是raw string的首字母
println!(r###"
This raw string started with 'r###"'.
Therefore it does not end until we reach a quote mark ('"') followed immediately by three pound signs ('###'):
"###);

加“#”的原始字符串,规则是:字母r,后面是n个#(n≥1),后面跟双引号引起来的字符串,后面是n个#(跟前面的n必须相等):

可变的字符串

内容和长度可变的字符串是String类型,和大多数语言中的可变字符串相似。但是也有很多严格不同之处。就像开头所述,要明白它的行为,需要先摸清它在内存是怎么存储的。

String存储一串字符,字符的编码是UTF-8,而UTF-8是变长的,前篇我们已经详解了。所以存储了“abc”的字符串长度是3个字节,而存储了“a中”的长度是4个字节,其中中文占3字节。

let s1: String = "abc".to_string();
let s2: String = "a中".to_string();
println!("{}  {}  {} ", s1, s1.capacity(), s1.len()); //输出 abc 3 3
println!("{}  {}  {} ", s2, s2.capacity(), s2.len()); //输出 a中 4 4

亮点来了哈:

如果用len()函数计算字符串的长度,结果是字节数,而不是有多少个字符!!所以,无法把字符串简单的看做字符的数组,也就不能直接用索引法 s1[1]来表示第2个字符,也不能用for...in 来遍历字符串!!

当然,切片也不建议用,因为s1[1..3]能正确的得出是“bc”,而s2[1..3]则会出错,因为这个切片是切了“中”字三个字节当中的前两个字节。

这么神奇吗?

作为最常用类型,当然不会这么弱智!办法是用到String类型的chars方法。s1.chars()返回一个序列类型(类型名称是Chars),包含了a、b、c三个元素,s2.chars()返回的序列包含a、中两个字符(序列的类型是前面我们讲到的char类型,每个char类型占4字节空间)。

所以,索引、迭代、切片的使用方法,都可以用在这个序列上!

String的用法(更多用法可以查参考手册):

//创建字符串
let s1 = "too many pets".to_string(); //用字符串字面量的to_string方法
let s2 = format!("{}年龄:{}", "林冲", 29); //用format!宏创建,它的使用方法和printnl!宏相同,但是末尾不加换行 let bits = vec!["阮小二", "阮小五", "阮小七"];
assert_eq!(bits.concat(), "阮小二阮小五阮小七"); //用序列类型的concat方法
assert_eq!(bits.join(", "), "阮小二, 阮小五, 阮小七"); //用序列类型的join方法,可以在各元素中间加间隔符 //比较
assert!("ONE".to_lowercase() == "one"); //支持比较运算符==、!=、>、<、<=、>和>= //一些方法
let mut s = String::from("源字符串"); //必须声明为mut,才能改变字符串的内容
assert_eq!(s.pop(), Some('o')); //弹出最后一个字符,返回一个Option类型,其中包装了弹出的值,或者None
s.push("新增"); //追加到末尾
assert!("peanut".contains("nut")); //是否包含文字
assert_eq!("?_?".replace("?", "■"), "■_■"); //替换
assert_eq!(" clean\n".trim(), "clean"); //去除收、尾的空格、换行符等等空白字符
for word in "veni, vidi, vici".split(", ") { //分割为字符串序列
  assert!(word.starts_with("v")); //测试开始字符串,ends_with是测试末尾
}

&str类型

&str是一种引用类型,类似切片,引用了字符串字面量或者是String的一段,它其实也是一个组合指针:包括引用目标的指针和引用的长度(当然也是以占用字节数计量的)。

&str最大的用处是在函数传参中。因为它可以引用任何字符串的任何片段,不管它是字符串字面量文本(存储在可执行文件中)还是字符串(在运行时分配和释放)。想让函数允许传递任何一种字符串的参数时,&str比其他字符串类型更适合。

最后,说一下引用和非引用。

引用的实质是指针,在C语言中,如果用指针所指的值,需要解引用;在C++中有引用类型,它掩盖了解引用的步骤:

// C或C++
int a = 32;
int *ra = &a;
//修改a的值
*ra = 64; // 在C++代码中
int x = 10;
int &r = x; // initialization creates reference implicitly
r = 20; // stores 20 in x, r itself still points to x

在Rust中,指针类型其实也需要解引用。使用&运算符显式创建引用,并使用*运算符显式解引用:

// 在Rust中
let x = 10;
let r = &x; // &x 是指向 x 的引用
assert!(*r == 10); // 显式地解引用r

但是,在需要的时候,点.运算符为解引用提供了便利,可以隐式解引用其左操作数而不用写 *,这在面向对象代码中大量使用。例如访问结构体对象的元素或元组中的元素:

let t = (123, "bubble", "测试");
let rt = &t; //元组的引用

assert_eq!(rt.2, "测试");
// 和上面一句相同:
assert_eq!((*rt).2, "测试");

struct Anime {
age: i32,
bechdel_pass: bool
};
let aria = Anime {
age: 32,
bechdel_pass: true
};
let anime_ref = &aria; //结构体的引用
assert_eq!(anime_ref.age, 32);
// 和上面一句相同:
assert_eq!((*anime_ref).age, 32);

字符串在各种语言中都是最常用的,但是其复杂度经常超出普通人的想象。所以,有坑慢慢踩,且行且珍惜!

除了上篇和本篇,还有三种用户自定义类型:结构体struct、枚举enum和特性trait:struct、enum类似其他语言里的结构体/类和枚举,但也有很多不同之处,不可混为一谈。trait在其他语言中也有出现,但本人不是太了解其他语言的trait是什么......

这三种类型将在后面分别单独阐述。

基础的数据类型就到这里吧,下篇开始进入Rust的核心!

Rust之路(3)——数据类型 下篇的更多相关文章

  1. python之路:数据类型初识

    python开发之路:数据类型初识 数据类型非常重要.不过我这么说吧,他不重要我还讲个屁? 好,既然有人对数据类型不了解,我就讲一讲吧.反正这东西不需要什么python代码. 数据类型我讲的很死板.. ...

  2. Scala进阶之路-高级数据类型之集合的使用

    Scala进阶之路-高级数据类型之集合的使用 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Scala 的集合有三大类:序列 Seq.集 Set.映射 Map,所有的集合都扩展自 ...

  3. Scala进阶之路-高级数据类型之数组的使用

    Scala进阶之路-高级数据类型之数组的使用 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数组的初始化方式 1>.长度不可变数组Array 注意:顾名思义,长度不可变数 ...

  4. Rust之路(0)

    Rust--一个2012年出现,2015年推出1.0版本的"年轻"语言.在 2016 至 2018 年的 stack overflow 开发人员调查中,被评比为 "最受欢 ...

  5. Rust之路(2)——数据类型 上篇

    [未经书面同意,严禁转载] -- 2020-10-13 -- Rust是系统编程语言.什么意思呢?其主要领域是编写贴近操作系统的软件,文件操作.办公工具.网络系统,日常用的各种客户端.浏览器.记事本. ...

  6. Rust之路(1)

    [未经书面许可,严禁转载]-- 2020-10-09 -- 正式开始Rust学习之路了! 思而不学则罔,学而不思则殆.边学边练才能快速上手,让我们先来个Hello World! 但前提是有Rust环境 ...

  7. Rust学习笔记一 数据类型

    写在前面 我也不是什么特别厉害的大牛,学历也很低,只是对一些新语言比较感兴趣,接触过的语言不算多也不算少,大部分也都浅尝辄止,所以理解上可能会有一些偏差. 自学了Java.Kotlin.Python. ...

  8. Rust之路(4)——所有权

    [未经书面同意,严禁转载] -- 2020-10-14 -- 所有权是Rust的重中之重(这口气咋像高中数学老师 WTF......). 所有权是指的对内存实际存储的数据的访问权(包括读取和修改),在 ...

  9. Python之路-python数据类型(列表、字典、字符串、元祖)操作

    一.列表: 列表的语法,以中括号开通和结尾,元素以逗号隔开.例如:name = [] 列表是以下标取值,第一个元素下标是0,第二个元素下标是1,最后一个元素下标是-1.   1.增加 #name = ...

随机推荐

  1. opencv-python函数

    opencv-python读取.展示和存储图像 1.imshow函数 imshow函数作用是在窗口中显示图像,窗口自动适合于图像大小,我们也可以通过imutils模块调整显示图像的窗口的大小.函数官方 ...

  2. mysql InnoDB引擎是否支持hash索引

    看一下mysql官方文档:https://dev.mysql.com/doc/refman/5.7/en/create-index.html , 从上面的图中可以得知,mysql 是支持hash索引的 ...

  3. C#中SQL Server的几点注意事项

     背景 在C#中处理sql会遇到一些奇怪的问题,在这里做一个小的总结,内容会随着经历不断积累. 内容 1.DataTime?和DataTime的区别. DataTime?定义的数据为可空类型,允许其为 ...

  4. Kubernetes笔记(五):了解Pod(容器组)

    Kubernetes 中, 容器总是以 Pod(容器组)的方式进行调度与运行.因此对 Pod 的理解与掌握是学习 Kubernetes 的基础. 理解 Pod Pod(容器组)是 Kubernetes ...

  5. Kubernetes探针踩坑记

    1. 荒腔走板 最近一两个月生产K8s集群频繁出现短时503 Service Temporarily Unavailable,还不能主动复现,相当郁闷,压力山大. HTTP 5xx响应状态码用于定义服 ...

  6. 尚硅谷阳哥JVM笔记

    JVM体系结构 类加载器(快递员): 只负责加载java文件,编译后的class文件在文件开头有特定的文件表示,将class文件字节码内容从硬盘加载到JVM内存中并将这些内容转换成方法区的运行时数据结 ...

  7. Prometheus之Exporter开发

    Prometheus开发Exporter简介 Exporter 本身是一个http 服务,其指标结果只要符合 Prometheus 规范就可以被 Prometheus 使用. Prometheus中m ...

  8. linux命令之修改yum源为国内镜像

    命令: centos7 修改yum源为阿里源 首先是到yum源设置文件夹里 1. 查看yum源信息: yum repolist 2. 安装base reop源 cd /etc/yum.repos.d ...

  9. ucore操作系统学习笔记(一) ucore lab1系统启动流程分析

    一.ucore操作系统介绍 操作系统作为一个基础系统软件,对下控制硬件(cpu.内存.磁盘网卡等外设),屏蔽了底层复杂多样的硬件差异:对上则提供封装良好的应用程序接口,简化应用程序开发者的使用难度.站 ...

  10. Spring学习(一)--Spring的设计与整体架构

    之前只是在学校里大概的学习了一下Spring框架的使用以及一些最基本.浅显的原理,并没有做出深入的学习,等到工作之后想提升自己的时候发现所掌握的Spring框架的简直烂如狗屎,为监督自己的学习进度,立 ...