Rust之路(3)——数据类型 下篇
【未经书面同意,严禁转载】 -- 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)——数据类型 下篇的更多相关文章
- python之路:数据类型初识
python开发之路:数据类型初识 数据类型非常重要.不过我这么说吧,他不重要我还讲个屁? 好,既然有人对数据类型不了解,我就讲一讲吧.反正这东西不需要什么python代码. 数据类型我讲的很死板.. ...
- Scala进阶之路-高级数据类型之集合的使用
Scala进阶之路-高级数据类型之集合的使用 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Scala 的集合有三大类:序列 Seq.集 Set.映射 Map,所有的集合都扩展自 ...
- Scala进阶之路-高级数据类型之数组的使用
Scala进阶之路-高级数据类型之数组的使用 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数组的初始化方式 1>.长度不可变数组Array 注意:顾名思义,长度不可变数 ...
- Rust之路(0)
Rust--一个2012年出现,2015年推出1.0版本的"年轻"语言.在 2016 至 2018 年的 stack overflow 开发人员调查中,被评比为 "最受欢 ...
- Rust之路(2)——数据类型 上篇
[未经书面同意,严禁转载] -- 2020-10-13 -- Rust是系统编程语言.什么意思呢?其主要领域是编写贴近操作系统的软件,文件操作.办公工具.网络系统,日常用的各种客户端.浏览器.记事本. ...
- Rust之路(1)
[未经书面许可,严禁转载]-- 2020-10-09 -- 正式开始Rust学习之路了! 思而不学则罔,学而不思则殆.边学边练才能快速上手,让我们先来个Hello World! 但前提是有Rust环境 ...
- Rust学习笔记一 数据类型
写在前面 我也不是什么特别厉害的大牛,学历也很低,只是对一些新语言比较感兴趣,接触过的语言不算多也不算少,大部分也都浅尝辄止,所以理解上可能会有一些偏差. 自学了Java.Kotlin.Python. ...
- Rust之路(4)——所有权
[未经书面同意,严禁转载] -- 2020-10-14 -- 所有权是Rust的重中之重(这口气咋像高中数学老师 WTF......). 所有权是指的对内存实际存储的数据的访问权(包括读取和修改),在 ...
- Python之路-python数据类型(列表、字典、字符串、元祖)操作
一.列表: 列表的语法,以中括号开通和结尾,元素以逗号隔开.例如:name = [] 列表是以下标取值,第一个元素下标是0,第二个元素下标是1,最后一个元素下标是-1. 1.增加 #name = ...
随机推荐
- 做SQL解析时遇到的问题
相对于算术表达式,Json和XML等解析,SQL就比较复杂了. 举个例子来说:符号“(”是目前的SqlAnalyzer1.00中作为进入递归的标志之一,到“)”结束,中间是个查询语句.如果测试用例也是 ...
- 从两表连接看Oracle sql优化器的效果
select emp.*,dept.* from tb_emp03 emp,tb_dept03 dept where emp.deptno=dept.id -- 不加hint SQL> sele ...
- PHP + Redis 生成自定义订单编号
/** * 订单编号生成规则 * 14位 = 6位时间 + 5位自增 + 3位ID * @param string $prefix 前缀: 默认为order * @param int $userId ...
- [IDEA]Java:“程序包XXX不存在”问题的三种解决方案
###三种方案 ####01 出现jar包找不到的问题,首先有可能是项目依赖中有些jar没有下载完整 而mvn idea:idea这个命令可以检查并继续下载未下载完整的依赖jar. 在命令行输入mvn ...
- yum管理——linux字符界面安装图形化及两种界面的切换(3)
1.查看yum软件包组 yum groups list 2.选择安装带 GUI 的服务器 yum groups install "带 GUI 的服务器" 3.字符界面切换为图形化界 ...
- Java基础一篇过(七)Java8--stream流
一.简介 流(stream)也是Java8的一个重要的新特性,主要是对集合(Collector)功能的增强:在上一篇文章我们简单的了解了lambda表达式,现在我们学习下流的概念:使用流可以帮助我们做 ...
- Presto 标量函数注册和调用过程简述
在Presto 函数开发一文中已经介绍过如何进行函数开发,本文主要讲述标量函数(Scalar Function)实现之后,是如何在Presto内部进行注册和被调用的.主要讲述标量函数是因为:三类函数的 ...
- Kafka监控必备——Kafka-Eagle 2.0.2正式发布
对于经常使用Kafka的同学,拥有一个炫酷又实用的监控系统是非常有必要的.可以实时的监控数据流的情况,了解实时数据流的变化. Kafka Eagle Kafka Eagle是一个监控系统,监控Kafk ...
- 简述application.properties和application.yml 以及 @ConfigurationProperties 和@PropertySource @Value 和@ImportResource的用法,区别
问题: 如何在application.properties和application.yml中配置String,Date,Object,Map,List类型的属性,并且idea能提示 先写一个Perso ...
- 开源发丝分割数据集CelebAHairMask-HQ(国庆献礼)
在这个特别日子里,举国欢庆,什么都可以缺席,大礼包不行. 本次开源针对CelebAMask-HQ中发丝部分进行细化的数据集. 该数据集可用于发丝分割等方向的研究和探索. 在过去的一年时间里,疫情改变很 ...