RUST 学习日记 第15课 ——字符串的常用方法(一)
0x00 回顾与开篇
前面用了两篇文章介绍了Rust的字符串,也了解了它们之间的联系和区别。这节课开始介绍Rust字符串的修改,添加,删除等常用的方法,进一步的来了解Rust字符串。
0x01 追加(Push)
在字符串尾部可以使用push()
方法追加单个字符char
,也可以使用push_str()
方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串。由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的。即字符串变量必须由mut关键字修饰。
示例代码如下:
let mut string = String::from("hello ");
string.push('r');
println!("追加字符 push() -> {}", string);
string.push_str("ust!");
println!("追加字符串 push_str() -> {}", string);
代码运行结果:
追加字符 push() -> hello r
追加字符串 push_str() -> hello rust!
0x02 插入(Insert)
可以使用insert()
方法插入单个字符char
,也可以使用insert_str()
方法插入字符串字面量。与push()
方法不同,这俩方法需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是要插入的字符(串)。索引从0开始计数,如果越界则会发生错误。由于字符串插入操作要修改原来的字符串,则该字符串必须是可变的。即字符串变量必须由mut关键字修饰。
示例代码如下:
let mut insert_string = String::from("hello rust!");
insert_string.insert(5, ',');
println!("插入字符 insert() -> {}", insert_string);
insert_string.insert_str(6, " I like");
println!("插入字符串 insert_str() -> {}", insert_string);
代码运行结果:
插入字符 insert() -> hello, rust!
插入字符串 insert_str() -> hello, I like rust!
0x03 连接(Catenate)
1、使用+
或者+=
连接字符串
使用+
或者+=
连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。其实当调用+
的操作符时,相当于调用了string标准库中的add
方法。
源码如下:
[inline]
fn add(mut self, other: &str) -> String {
self.push_str(other);
self
}
这里add
方法的第二个参数是一个引用的类型。因此我们在使用+
,必须传递切片引用类型。不能直接传递String
类型。+
和+=
都是返回一个新的字符串。所以变量声明可以不需要mut关键字修饰。
示例代码如下:
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!!!!
看到这里,是不是有人有问题要问。
Q:看源码明明是操作了原有的字符串啊,只是在原有的字符串中调用了push_str
追加了新的内容,为啥还要返回一个新的字符串呢?岂不是多此一举吗?
A:那我们就直接输出下string_append
这个字符串看下不就可以了吗。如果你打印string_append
,你会很绝望,因为编译器会发生下面的错误。
println!("输出 string_append -> {}", string_append);
| ^^^^^^^^^^^^^ value borrowed here after move
直译的意思是:这个值在移动后又被使用了。简单解释下,string_append
这个变量通过调用了add
方法后,所有权被转移到add
方法里面,add
方法调用后就被释放了,同时string_append
也被释放了。再使用string_append
就会发生错误。仅了解即可,这里涉及到所有权转移(Move)的相关知识。仅仅了解即可,我在这里埋个坑,后面将所有权会详细解释。
2、使用format!连接字符串
format!
这种方式适用于String和&str。format!
的用法与print!
的用法类似,在前面“第9课——输入与输出”已经详细介绍过了。
示例代码如下:
let s1 = "hello";
let s2 = String::from("rust");
let s = format!("{} {}!", s1, s2);
println!("{}", s);
代码运行结果:
hello rust!
0x04 替换(Replace)
如果想要把字符串中的某个字符串替换成其它的字符串,那可以使用replace()
方法。与替换有关的方法有三个。
1、replace
该方法可适用于String和&str类型。replace
方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。
示例代码如下:
// replacen
let string_replace = "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
方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串。
示例代码如下:
// replacen
let string_replacen = "I like rust. Learning rust is my favorite!";
let new_string_replacen = string_replacen.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关键字修饰。
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!"
0x05 删除(Delete)
与字符串删除相关的方法有4个,他们分别是pop
,remove
,truncate
,clear
。这四个方法仅适用于String类型。
1、pop——删除并返回字符串的最后一个字符。
该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个Option
示例代码如下:
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
方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
-
什么是不合法的字符边界?
在Rust中,一个英文字符或者阿拉伯数字是占一个字节的,而一个汉字是占3个字符串。可以使用
std::mem::size_of_val()
方法查看在内存占用几个字节。还有一些Emoji表情还占了4个字节。也许你会问为什么会这样呢?这个与UTF-8编码有关系,这里就不解释了,如果后面有时间,我可以拿出一篇文章来讲解下Unicode的背景。如果想知道答案也可以通过网络搜索下。
示例代码如下:
let word = "中"; let ch = "1"; println!("word 占 {} 个字节", std::mem::size_of_val(word)); println!("ch 占 {} 个字节", std::mem::size_of_val(ch));
代码运行结果:
word 占 3 个字节 ch 占 1 个字节
经过上面的例子,也许你理解了什么叫做不合法的字符边界。看下面的代码示例,string_remove
是一个String类型,按照上面说的规则,计算下的它的字节长度是18字节。如果要删除第一个汉字,那就传参数为0。如果想直接删除第二个汉字,则需要传参数为3。如果你传递参数为1或者2,则会出现不合法的字符边界错误。
示例代码如下:
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 = "试rmove方法"
3、truncate——删除字符串中从指定位置开始到结尾的全部字符。
该方法是直接操作原来的字符串。无返回值。该方法truncate
方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
示例代码如下:
let mut string_truncate = String::from("测试truncate");
string_truncate.truncate(3);
dbg!(string_truncate);
代码运行结果:
string_truncate = "测"
4、clear——清空字符串
该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于truncate
方法参数为0的时候。
示例代码如下:
let mut string_clear = String::from("string clear");
string_clear.clear();
dbg!(string_clear);
代码运行结果:
string_clear = ""
0x06 小结
简单介绍了字符串的常用修改方法,总共分为追加,插入,连接,替换,删除5部分。下节继续介绍字符串的常用方法——字符串的访问。
0x04 本节源码
015 · StudyRust - 码云 - 开源中国 (gitee.com)
下节预告——字符串的常用方法(二)。