当前位置:网站首页>Rust学习:6.1_复合类型之切片
Rust学习:6.1_复合类型之切片
2022-08-10 07:33:00 【我不想头秃阿】
Rust学习:6.1_复合类型之切片
前言
为了学习Rust,阅读了github上的Rust By Practice电子书,本文章只是用来记录自己的学习过程,感兴趣的可以阅读原书,希望大家都能掌握Rust!
概念解析——复合类型
本章的重点在复合类型上,顾名思义,复合类型是由其它类型组合而成的,最典型的就是结构体 struct
和枚举 enum
。例如平面上的一个点 point(x, y)
,它由两个数值类型的值 x
和 y
组合而来。我们无法单独去维护这两个数值,因为单独一个 x
或者 y
是含义不完整的,无法标识平面上的一个点,应该把它们看作一个整体去理解和处理。
来看一段代码,它使用我们之前学过的内容来构建文件操作:
#![allow(unused_variables)]
type File = String;
fn open(f: &mut File) -> bool {
true
}
fn close(f: &mut File) -> bool {
true
}
#[allow(dead_code)]
fn read(f: &mut File, save_to: &mut Vec<u8>) -> ! {
unimplemented!()
}
fn main() {
let mut f1 = File::from("f1.txt");
open(&mut f1);
//read(&mut f1, &mut vec![]);
close(&mut f1);
}
接下来我们的学习非常类似原型设计:有的方法只提供 API 接口,但是不提供具体实现。此外,有的变量在声明之后并未使用,因此在这个阶段我们需要排除一些编译器噪音(Rust 在编译的时候会扫描代码,变量声明后未使用会以 warning
警告的形式进行提示),引入 #![allow(unused_variables)]
属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 warning
警告,具体的常见编译器属性你可以在这里查阅:编译器属性标记。
read
函数也非常有趣,它返回一个 !
类型,这个表明该函数是一个发散函数,不会返回任何值,包括 ()
。unimplemented!()
告诉编译器该函数尚未实现,unimplemented!()
标记通常意味着我们期望快速完成主要代码,回头再通过搜索这些标记来完成次要代码,类似的标记还有 todo!()
,当代码执行到这种未实现的地方时,程序会直接报错。你可以反注释 read(&mut f1, &mut vec![]);
这行,然后再观察下结果。
同时,从代码设计角度来看,关于文件操作的类型和函数应该组织在一起,散落得到处都是,是难以管理和使用的。而且通过 open(&mut f1)
进行调用,也远没有使用 f1.open()
来调用好,这就体现出了只使用基本类型的局限性:无法从更高的抽象层次去简化代码。
接下来,我们将引入一个高级数据结构 —— 结构体 struct
,来看看复合类型是怎样更好的解决这类问题。 开始之前,先来看看 Rust 的重点也是难点:字符串 String
和 &str
。
字符串与切片
1.字符串
在其他语言中,字符串往往是送分题,因为实在是太简单了,例如 "hello, world"
就是字符串章节的几乎全部内容了,但是如果你带着同样的想法来学 Rust,我保证,绝对会栽跟头,因此这一章大家一定要重视,仔细阅读,这里有很多其它 Rust 书籍中没有的内容。
首先来看段很简单的代码:
fn main() {
let my_name = "Pascal";
greet(my_name);
}
fn greet(name: String) {
println!("Hello, {}!", name);
}
greet
函数接受一个字符串类型的 name
参数,然后打印到终端控制台中,非常好理解,你们猜猜,这段代码能否通过编译?
error[E0308]: mismatched types
--> src/main.rs:3:11
|
3 | greet(my_name);
| ^^^^^^^
| |
| expected struct `std::string::String`, found `&str`
| help: try using a conversion method: `my_name.to_string()`
error: aborting due to previous error
Bingo,果然报错了,编译器提示 greet
函数需要一个 String
类型的字符串,却传入了一个 &str
类型的字符串,相信读者心中现在一定有几头草泥马呼啸而过,怎么字符串也能整出这么多花活?
在讲解字符串之前,先来看看什么是切片?
2.切片(slice)
切片并不是 Rust 独有的概念,在 Go 语言中就非常流行,它允许你引用集合中部分连续的元素序列,而不是引用整个集合。
对于字符串而言,切片就是对 String
类型中某一部分的引用,它看起来像这样:
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
hello
没有引用整个 String s
,而是引用了 s
的一部分内容,通过 [0..5]
的方式来指定。
这就是创建切片的语法,使用方括号包括的一个序列:[开始索引…终止索引],其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个 右半开区间
。在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 终止索引
- 开始索引
的方式计算得来的。
对于 let world = &s[6..11];
来说,world
是一个切片,该切片的指针指向 s
的第 7 个字节(索引从 0 开始, 6 是第 7 个字节),且该切片的长度是 5
个字节。
在使用 Rust 的 ..
range 序列语法时,如果你想从索引 0 开始,可以使用如下的方式,这两个是等效的:
let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2];
同样的,如果你的切片想要包含 String
的最后一个字节,则可以这样使用:
let s = String::from("hello");
let len = s.len();
let slice = &s[4..len];
let slice = &s[4..];
你也可以截取完整的 String
切片:
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];
在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界,例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:
let s = "中国人"; let a = &s[0..2]; println!("{}",a);
因为我们只取
s
字符串的前两个字节,但是本例中每个汉字占用三个字节,因此没有落在边界处,也就是连中
字都取不完整,此时程序会直接崩溃退出,如果改成&s[0..3]
,则可以正常通过编译。 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作 UTF-8 字符串,参见这里
字符串切片的类型标识是 &str
,因此我们可以这样声明一个函数,输入 String
类型,返回它的切片: fn first_word(s: &String) -> &str
。
有了切片就可以写出这样的代码:
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);
}
fn first_word(s: &String) -> &str {
&s[..1]
}
编译器报错如下:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:18:5
|
16 | let word = first_word(&s);
| -- immutable borrow occurs here
17 |
18 | s.clear(); // error!
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("the first word is: {}", word);
| ---- immutable borrow later used here
回忆一下借用的规则:当我们已经有了可变借用时,就无法再拥有不可变的借用。因为 clear
需要清空改变 String
,因此它需要一个可变借用(利用 VSCode 可以看到该方法的声明是 pub fn clear(&mut self)
,参数是对自身的可变借用 );而之后的 println!
又使用了不可变借用,也就是在 s.clear()
处可变借用与不可变借用试图同时生效,因此编译无法通过。
从上述代码可以看出,Rust 不仅让我们的 API
更加容易使用,而且也在编译期就消除了大量错误!
3.其它切片
因为切片是对集合的部分引用,因此不仅仅字符串有切片,其它集合类型也有,例如数组:
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
该数组切片的类型是 &[i32]
,数组切片和字符串切片的工作方式是一样的,例如持有一个引用指向原始数组的某个元素和长度。
4.字符串字面量是切片
之前提到过字符串字面量,但是没有提到它的类型:
let s = "Hello, world!";
实际上,s
的类型是 &str
,因此你也可以这样声明:
let s: &str = "Hello, world!";
该切片指向了程序可执行文件中的某个点,这也是为什么字符串字面量是不可变的,因为 &str
是一个不可变引用。
了解完切片,可以进入本节的正题了。
5.什么是字符串?
顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。
Rust 在语言级别,只有一种字符串类型: str
,它通常是以引用类型出现 &str
,也就是上文提到的字符串切片。虽然语言级别只有上述的 str
类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String
类型。
str
类型是硬编码进可执行文件,也无法被修改,但是 String
则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,当 Rust 用户提到字符串时,往往指的就是 String
类型和 &str
字符串切片类型,这两个类型都是 UTF-8 编码。
除了 String
类型的字符串,Rust 的标准库还提供了其他类型的字符串,例如 OsString
, OsStr
, CsString
和 CsStr
等,注意到这些名字都以 String
或者 Str
结尾了吗?它们分别对应的是具有所有权和被借用的变量。
6.String 与 &str 的转换
在之前的代码中,已经见到好几种从 &str
类型生成 String
类型的操作:
String::from("hello,world")
"hello,world".to_string()
那么如何将 String
类型转为 &str
类型呢?答案很简单,取引用即可:
fn main() {
let s = String::from("hello,world!");
say_hello(&s);
say_hello(&s[..]);
say_hello(s.as_str());
}
fn say_hello(s: &str) {
println!("{}",s);
}
实际上这种灵活用法是因为 deref
隐式强制转换,具体我们会在 Deref
特征进行详细讲解。
7.操作字符串
由于 String
是可变字符串,下面介绍 Rust 字符串的修改,添加,删除等常用方法:
7.1追加 (Push)
在字符串尾部可以使用 push()
方法追加字符 char
,也可以使用 push_str()
方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串。由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut
关键字修饰。
示例代码如下:
fn main() {
let mut s = String::from("Hello ");
s.push('r');
println!("追加字符 push() -> {}", s);
s.push_str("ust!");
println!("追加字符串 push_str() -> {}", s);
}
代码运行结果:
追加字符 push() -> Hello r
追加字符串 push_str() -> Hello rust!
7.2插入 (Insert)
可以使用 insert()
方法插入单个字符 char
,也可以使用 insert_str()
方法插入字符串字面量,与 push()
方法不同,这俩方法需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是要插入的字符(串),索引从 0 开始计数,如果越界则会发生错误。由于字符串插入操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut
关键字修饰。
示例代码如下:
fn main() {
let mut s = String::from("Hello rust!");
s.insert(5, ',');
println!("插入字符 insert() -> {}", s);
s.insert_str(6, " I like");
println!("插入字符串 insert_str() -> {}", s);
}
代码运行结果:
插入字符 insert() -> Hello, rust!
插入字符串 insert_str() -> Hello, I like rust!
7.3替换 (Replace)
如果想要把字符串中的某个字符串替换成其它的字符串,那可以使用 replace()
方法。与替换有关的方法有三个。
1、replace
该方法可适用于 String
和 &str
类型。replace()
方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。
示例代码如下:
fn main() {
let string_replace = String::from("I like rust. Learning rust is my favorite!");
let new_string_replace = string_replace.replace("rust", "RUST");
dbg!(new_string_replace);
}
代码运行结果:
new_string_replace = "I like RUST. Learning RUST is my favorite!"
2、replacen
该方法可适用于 String
和 &str
类型。replacen()
方法接收三个参数,前两个参数与 replace()
方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串。
示例代码如下:
fn main() {
let string_replace = "I like rust. Learning rust is my favorite!";
let new_string_replacen = string_replace.replacen("rust", "RUST", 1);
dbg!(new_string_replacen);
}
代码运行结果:
new_string_replacen = "I like RUST. Learning rust is my favorite!"
3、replace_range
该方法仅适用于 String
类型。replace_range
接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut
关键字修饰。
示例代码如下:
fn main() {
let mut string_replace_range = String::from("I like rust!");
string_replace_range.replace_range(7..8, "R");
dbg!(string_replace_range);
}
代码运行结果:
string_replace_range = "I like Rust!"
7.4删除 (Delete)
与字符串删除相关的方法有 4 个,他们分别是 pop()
,remove()
,truncate()
,clear()
。这四个方法仅适用于 String
类型。
1、 pop
—— 删除并返回字符串的最后一个字符
该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option
类型,如果字符串为空,则返回 None
。 示例代码如下:
fn main() {
let mut string_pop = String::from("rust pop 中文!");
let p1 = string_pop.pop();
let p2 = string_pop.pop();
dbg!(p1);
dbg!(p2);
dbg!(string_pop);
}
代码运行结果:
p1 = Some(
'!',
)
p2 = Some(
'文',
)
string_pop = "rust pop 中"
2、 remove
—— 删除并返回字符串中指定位置的字符
该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove()
方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
示例代码如下:
fn main() {
let mut string_remove = String::from("测试remove方法");
println!(
"string_remove 占 {} 个字节",
std::mem::size_of_val(string_remove.as_str())
);
// 删除第一个汉字
string_remove.remove(0);
// 下面代码会发生错误
// string_remove.remove(1);
// 直接删除第二个汉字
// string_remove.remove(3);
dbg!(string_remove);
}
代码运行结果:
string_remove 占 18 个字节
string_remove = "试remove方法"
3、truncate
—— 删除字符串中从指定位置开始到结尾的全部字符
该方法是直接操作原来的字符串。无返回值。该方法 truncate()
方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
示例代码如下:
fn main() {
let mut string_truncate = String::from("测试truncate");
string_truncate.truncate(3);
dbg!(string_truncate);
}
代码运行结果:
string_truncate = "测"
4、clear
—— 清空字符串
该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate()
方法参数为 0 的时候。
示例代码如下:
fn main() {
let mut string_clear = String::from("string clear");
string_clear.clear();
dbg!(string_clear);
}
代码运行结果:
string_clear = ""
7.5连接 (Catenate)
1、使用 +
或者 +=
连接字符串
使用 +
或者 +=
连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。其实当调用 +
的操作符时,相当于调用了 std::string
标准库中的 add()
方法,这里 add()
方法的第二个参数是一个引用的类型。因此我们在使用 +
, 必须传递切片引用类型。不能直接传递 String
类型。+
和 +=
都是返回一个新的字符串。所以变量声明可以不需要 mut
关键字修饰。
示例代码如下:
fn main() {
let string_append = String::from("hello ");
let string_rust = String::from("rust");
// &string_rust会自动解引用为&str
let result = string_append + &string_rust;
let mut result = result + "!";
result += "!!!";
println!("连接字符串 + -> {}", result);
}
代码运行结果:
连接字符串 + -> hello rust!!!!
add()
方法的定义:
fn add(self, s: &str) -> String
因为该方法涉及到更复杂的特征功能,因此我们这里简单说明下:
fn main() {
let s1 = String::from("hello,");
let s2 = String::from("world!");
// 在下句中,s1的所有权被转移走了,因此后面不能再使用s1
let s3 = s1 + &s2;
assert_eq!(s3,"hello,world!");
// 下面的语句如果去掉注释,就会报错
// println!("{}",s1);
}
self
是 String
类型的字符串 s1
,该函数说明,只能将 &str
类型的字符串切片添加到 String
类型的 s1
上,然后返回一个新的 String
类型,所以 let s3 = s1 + &s2;
就很好解释了,将 String
类型的 s1
与 &str
类型的 s2
进行相加,最终得到 String
类型的 s3
。
由此可推,以下代码也是合法的:
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
// String = String + &str + &str + &str + &str
let s = s1 + "-" + &s2 + "-" + &s3;
String + &str
返回一个 String
,然后再继续跟一个 &str
进行 +
操作,返回一个 String
类型,不断循环,最终生成一个 s
,也是 String
类型。
s1
这个变量通过调用 add()
方法后,所有权被转移到 add()
方法里面, add()
方法调用后就被释放了,同时 s1
也被释放了。再使用 s1
就会发生错误。这里涉及到所有权转移(Move)的相关知识。
2、使用 format!
连接字符串
format!
这种方式适用于 String
和 &str
。format!
的用法与 print!
的用法类似,详见格式化输出。
示例代码如下:
fn main() {
let s1 = "hello";
let s2 = String::from("rust");
let s = format!("{} {}!", s1, s2);
println!("{}", s);
}
代码运行结果:
hello rust!
8.练习
8.1str和&str
1.
正常情况下我们无法使用 str
类型,但是可以使用 &str
来替代
// 修复错误,不要新增代码行
fn main() {
let s: str = "hello, world";
}
修改:
// 修复错误,不要新增代码行
fn main() {
let s: &str = "hello, world";
}
2.
如果要使用 str
类型,只能配合 Box
。 &
可以用来将 Box<str>
转换为 &str
类型
// 使用至少两种方法来修复错误
fn main() {
let s: Box<str> = "hello, world".into();
greetings(s)
}
fn greetings(s: &str) {
println!("{}",s)
}
修改1:
// 使用至少两种方法来修复错误
fn main() {
let s: Box<str> = "hello, world".into();
greetings(&s)
}
fn greetings(s: &str) {
println!("{}",s)
}
修改2:
// 使用至少两种方法来修复错误
fn main() {
let s: Box<&str> = "hello, world".into();
greetings(*s)
}
fn greetings(s: &str) {
println!("{}",s)
}
8.2String
String
是定义在标准库中的类型,分配在堆上,可以动态的增长。它的底层存储是动态字节数组的方式( Vec<u8>
),但是与字节数组不同,String
是 UTF-8
编码。
3.
// 填空
fn main() {
let mut s = __;
s.push_str("hello, world");
s.push('!');
assert_eq!(s, "hello, world!");
}
修改:
// 填空
fn main() {
let mut s = String::from("");
s.push_str("hello, world");
s.push('!');
assert_eq!(s, "hello, world!");
}
4.
// 修复所有错误,并且不要新增代码行
fn main() {
let s = String::from("hello");
s.push(',');
s.push(" world");
s += "!".to_string();
println!("{}", s)
}
修改:
// 修复所有错误,并且不要新增代码行
fn main() {
let mut s = String::from("hello");
s.push(',');
s.push_str(" world");
s += "!";
println!("{}", s)
}
5.
我们可以用 replace
方法来替换指定的子字符串
// 填空
fn main() {
let s = String::from("I like dogs");
// 以下方法会重新分配一块内存空间,然后将修改后的字符串存在这里
let s1 = s.__("dogs", "cats");
assert_eq!(s1, "I like cats")
}
修改:
// 填空
fn main() {
let s = String::from("I like dogs");
// 以下方法会重新分配一块内存空间,然后将修改后的字符串存在这里
let s1 = s.replace("dogs", "cats");
assert_eq!(s1, "I like cats")
}
6.
你只能将 String
跟 &str
类型进行拼接,并且 String
的所有权在此过程中会被 move
// 修复所有错误,不要删除任何一行代码
fn main() {
let s1 = String::from("hello,");
let s2 = String::from("world!");
let s3 = s1 + s2;
assert_eq!(s3,"hello,world!");
println!("{}",s1);
}
修改:
// 修复所有错误,不要删除任何一行代码
fn main() {
let s1 = String::from("hello,");
let s2 = String::from("world!");
let s3 = s1.clone() + &s2;
assert_eq!(s3,"hello,world!");
println!("{}",s1);
}
8.3&str
和 String
与 str
的很少使用相比,&str
和 String
类型却非常常用,因此也非常重要。
7.
我们可以使用两种方法将 &str
转换成 String
类型
// 使用至少两种方法来修复错误
fn main() {
let s = "hello, world";
greetings(s)
}
fn greetings(s: String) {
println!("{}",s)
}
修改1:
// 使用至少两种方法来修复错误
fn main() {
let s = "hello, world";
greetings(s.to_string())
}
fn greetings(s: String) {
println!("{}",s)
}
修改2:
// 使用至少两种方法来修复错误
fn main() {
let s = String::from("hello, world");
greetings(s)
}
fn greetings(s: String) {
println!("{}",s)
}
8.
我们可以使用 String::from
或 to_string
将 &str
转换成 String
类型
// 使用两种方法来解决错误,不要新增代码行
fn main() {
let s = "hello, world".to_string();
let s1: &str = s;
}
修改1:
// 使用两种方法来解决错误,不要新增代码行
fn main() {
let s = "hello, world".to_string();
let s1: &str = s.as_str();
}
修改2:
// 使用两种方法来解决错误,不要新增代码行
fn main() {
let s = "hello, world";
let s1: &str = s;
}
8.4字符串转义
9.
fn main() {
// 你可以使用转义的方式来输出想要的字符,这里我们使用十六进制的值,例如 \x73 会被转义成小写字母 's'
// 填空以输出 "I'm writing Rust"
let byte_escape = "I'm writing Ru\x73__!";
println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
// 也可以使用 Unicode 形式的转义字符
let unicode_codepoint = "\u{211D}";
let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";
println!("Unicode character {} (U+211D) is called {}",
unicode_codepoint, character_name );
// 还能使用 \ 来连接多行字符串
let long_string = "String literals can span multiple lines. The linebreak and indentation here \ can be escaped too!";
println!("{}", long_string);
}
修改:
fn main() {
// 你可以使用转义的方式来输出想要的字符,这里我们使用十六进制的值,例如 \x73 会被转义成小写字母 's'
// 填空以输出 "I'm writing Rust"
let byte_escape = "I'm writing Ru\x73\x74!";
println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
// 也可以使用 Unicode 形式的转义字符
let unicode_codepoint = "\u{211D}";
let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";
println!("Unicode character {} (U+211D) is called {}",
unicode_codepoint, character_name );
// 还能使用 \ 来连接多行字符串
let long_string = "String literals can span multiple lines. The linebreak and indentation here \ can be escaped too!";
println!("{}", long_string);
}
10.
有时候需要转义的字符很多,我们会希望使用更方便的方式来书写字符串: raw string.
/* 填空并修复所有错误 */
fn main() {
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
assert_eq!(raw_str, "Escapes don't work here: ? ℝ");
// 如果你希望在字符串中使用双引号,可以使用以下形式
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
// 如果希望在字符串中使用 # 号,可以如下使用:
let delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", delimiter);
// 填空
let long_delimiter = __;
assert_eq!(long_delimiter, "Hello, \"##\"")
}
修改:
/* 填空并修复所有错误 */
fn main() {
let raw_str = "Escapes don't work here: \x3F \u{211D}";
assert_eq!(raw_str, "Escapes don't work here: ? ℝ");
// 如果你希望在字符串中使用双引号,可以使用以下形式
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
// 如果希望在字符串中使用 # 号,可以如下使用:
let delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", delimiter);
// 填空
let long_delimiter = r###"Hello, "##""###;
assert_eq!(long_delimiter, "Hello, \"##\"")
}
8.5字符串索引string index
11.
你无法通过索引的方式去访问字符串中的某个字符,但是可以使用切片的方式 &s1[start..end]
,但是start
和 end
必须准确落在字符的边界处.
fn main() {
let s1 = String::from("hi,中国");
let h = s1[0]; // 修改当前行来修复错误,提示: `h` 字符在 UTF-8 格式中只需要 1 个字节来表示
assert_eq!(h, "h");
let h1 = &s1[3..5];// 修改当前行来修复错误,提示: `中` 字符在 UTF-8 格式中需要 3 个字节来表示
assert_eq!(h1, "中");
}
修改:
fn main() {
let s1 = String::from("hi,中国");
let h = &s1[0..1]; // 修改当前行来修复错误,提示: `h` 字符在 UTF-8 格式中只需要 1 个字节来表示
assert_eq!(h, "h");
let h1 = &s1[3..6];// 修改当前行来修复错误,提示: `中` 字符在 UTF-8 格式中需要 3 个字节来表示
assert_eq!(h1, "中");
}
8.6操作 UTF-8 字符串
12.
fn main() {
// 填空,打印出 "你好,世界" 中的每一个字符
for c in "你好,世界".__ {
println!("{}", c)
}
}
修改:
fn main() {
// 填空,打印出 "你好,世界" 中的每一个字符
for c in "你好,世界".chars() {
println!("{}", c)
}
}
边栏推荐
- 同步锁synchronized追本溯源
- DGIOT支持工业设备租赁以及远程管控
- 关于数据中心的设计方案,数据中心网络规划设计
- The precise effect of network integration promotion outsourcing in the era of Internet of Things-Shenzhen Win-Win World News
- 张驰课堂:老板会武术,谁也挡不住!六西格玛培训的魅力
- 浅谈C语言整型数据的存储
- 阿里巴巴(中国)网络技术有限公司、测试开发笔试二面试题(附答案)
- uni 小程序腾讯地图polygon背景透明度
- 1413. Stepwise Summation to Get Minimum Positive Numbers
- MySQL设置初始密码—注意版本mysql-8.0.30
猜你喜欢
Quickly enter the current date and time
初使jest 单元测试
探索神经网络架构教程视频,设计神经网络的步骤
物联网时代下的网络整合推广外包精准化效果-深圳双赢世讯
Confluence可以连接数据库但是在下一步就报错了
Introduction to C integer data storage
张驰课堂:老板会武术,谁也挡不住!六西格玛培训的魅力
Data types for database learning
基于STC8G2K64S4单片机通过OLED屏幕显示模拟量光敏模拟值
Relaxation class: the boss will martial arts, who also can not hold up against!The charm of six sigma training
随机推荐
【MySQL】使用MySQL Workbench软件新建表
【电商业务】外行为何难区别 商品属性与商品规格
复杂AB实验
神经网络可视化有3D版本了,美到沦陷 已开源
如何远程调试对方的H5页面
winget包管理器
自动化测试框架Pytest(二)——前后置处理
如何设计神经网络结构,神经网络设计与实现
Chapter 12 Other Database Tuning Strategies [2. Index and Tuning] [MySQL Advanced]
foreach遍历删除元素问题总结
MySQL事务隔离级别
简单业务类
【转】探秘钉钉的即时消息服务DTIM
全连接神经网络结构图,神经网络示意图怎么画
raid5的写性能,是不的比raid10快一些?
大佬,oracle单表增量同步时候源库服务器额外占用内存近2g,这不正常吧
【MySQL】SQL语句
.NET-7.WPF learning experience summary
90.(cesium之家)cesium高度监听事件
添加spark的相关依赖和打包插件(第六弹)