老前端人学Rust - 第七课(通用集合类型-String)

各位兄弟姐妹,抱歉拖更了这么久,最近身体不适,加上年终各种绩效、总结、业务需求,有些忙,不过还是要道个歉,确实有自己懈怠了的原因~

还是要啰嗦一句,身体才是革命的本钱,身体好的时候这些乱七八糟的病根本想都不会去想~所以各位还是要多保重身体啊,其他都是假的,身体才是真的😁

这节课开始是集合类型,之前咱们接触的都是基础类型,集合类型有动态数组字符串哈希映射

下面我们先来看字符串吧,虽然字符串的原理是动态数组(Vec<u8>)的封装类型,不过咱对String比较熟,就从String先学起。

创建字符串

js中创建字符串的方式大致有如下几种:

javascript 复制代码
// 字面量
var a = "123";
var b = '123';

// 摸板字面量
var c = 2;
var c1 = `1${c}3`;

// 全局对象String创建
var d = String("123");

// String构造函数
var e = new String("123");

需要注意的是a === b === c1 === d !== e,这是因为String对象直接创建的是一个基础类型字符串,而e是一个String的实例对象。

在rust中创建字符串有如下几个方式:

  • String的命名空间函数,这样会创建一个空的字符串,然后可以往里添加字符,不过这个方式不是很方便,没法定义初始数据
rust 复制代码
let mut s = String::new();
  • 基于字符串字面量使用to_string方法创建字符串,如下所示,需要注意,s是字符串字面量,即是一个字符串切片,类型为&str,而不是一个字符串,需要用to_string()转成字符串。
rust 复制代码
let s: &str = "Hello world";
let s1: String = s.to_string();
  • 使用String::from()方法创建字符串,我个人比较喜欢这种方式
rust 复制代码
let s = String::from("Hello, String!");

rust的字符串是基于UTF-8编码的,记住,后面要考。

更新字符串

  • 使用push_str向字符串尾部添加字符串切片
rust 复制代码
// push_str向String中添加字符串切片
let mut s = String::from("foo");
s.push_str(" bar");
println!("{}", s);  // foo bar
  • 使用push向字符串尾部添加一个字符,注意只能是一个字符,即char类型
rust 复制代码
// push向String中添加字符
let mut s = String::from("lo");
s.push('l');
println!("{}", s);  // lol

如果我们需要将两个已经定义好的字符串拼接在一起呢?这时候可以使用 + 或者 format! 宏来拼接

  • 使用+拼接字符串
rust 复制代码
/*使用+来拼接字符串 */
let s1 = String::from("Hello");
let s2 = String::from("world");
let s3 = s1 + ", " + &s2; 
println!("{}", s3);

看出什么问题来了么?这里+的左边是s1,而右边是&s2,s1移动,而s2是借用,执行到s3那行之后s1就没法再使用了,因为已经移动了,这个前面讲过,不过,为啥要这么使用呢,why???

跟js不一样,js我们直接就s1+s2得到新的字符串了,而rust有所有权的概念,且跟+的拼接的原理有关,使用+操作符其实会调用一个add的函数,这个函数的签名如下:

rust 复制代码
fn add(self, s: &str) -> String 

噢,看到这你就懂了,其实add方法是就是将自身与s字符串切片拼接,而不能两个字符串相加。

这里又有一个问题,add的s的参数类型是字符串切片&str,而我们代码里的s2是String类型,其实这就跟js中的强转类似,执行这个函数的时候,编译器会自动将String类型强转成&str类型。

所以+其实并没有执行多次复制,其实只是做了拼接,这比单纯复制要高效。

但是感觉这个+好麻烦,让s1变成一次性的了,移动后就没法使用了,很不符合咱js程序员的习惯。没有关系,咱可以用format!。

  • 使用format!宏
rust 复制代码
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3);
println!("使用format!将三个字符串相加{}", s);

使用方式跟println!宏一样,简单易懂,而且s1、s2、s3的所有权还在~

字符串索引

rust 复制代码
/*字符串索引 */
let s: String = String::from("hello world");
let h = s[0];  

看上面这段代码,有问题么?感觉木有问题,js咱就是这么用的。

那就执行一下。噢,不用执行,编译就通不过~

what? rust的字符串不支持索引??why???

回到开头,有介绍到,String其实是一个Vec<u8> 的封装类型,且是UTF-8编码的,所以其实一个字符串在内存中的格式其实是 [232, 128, 129, 229, 137, 141] 这样,我们打印两个字符串长度看一下

rust 复制代码
let s = String::from("hello world");
println!("s的长度: {}", s.len()); // 11
let s = String::from("老前端");
println!("s的长度: {}", s.len()); // 9

这是因为在utf-8编码中,中文占3个字节,英文字符占一个字节,这就导致了,如果使用下标索引,在非1个字节的字符串中,会取不到整个字符,比如"老",在vec<u8>中表示为[232, 128, 129],s[0]拿的不是"老",而是232,这样就会引起歧义,毕竟我们想要的不是一个字节值,所以rust干脆就不让这么使用,避免意外的返回值或者在运行时才暴露问题,毕竟rust都是为了你好🐶~

字符串切片

那上面的字符串,我到底该如何拿到"老"呢?rust提供了一种方法,使用索引来创建字符串切片。

rust 复制代码
let s = String::from("老前端");
println!("s的长度: {}", s.len()); // 9
let s1 = &s[0..3]; 
println!("s1: {}", s1); // 老

&s[0..3]这个方法我应该在前面文章有使用过,就是通过索引,从字符串的引用中根据索引获取字符串切片,索引区间是一个左闭右开的区间,即其实是0-2这3个索引。那中文是3个字节,我索引尾部到4呢,会怎么样?

rust 复制代码
let s = String::from("老前端");
println!("s的长度: {}", s.len()); // 9
let s1 = &s[0..4]; 
println!("s1: {}", s1); 

运行时会报错:

thread 'main' panicked at 'byte index 4 is not a char boundary; it is inside '前' (bytes 3..6) of 老前端', src/main.rs:46:15 note: run with RUST_BACKTRACE=1 environment variable to display a backtrace

所以我们使用索引方式获得字符串切片的话,需要明确知道索引区间内的下标起始位置是否正确✅,而且这是运行时才能报错的,没法在编译期查出来,所以你需要特别注意。

遍历字符串的方法

我们还有其他方式来访问字符串中的元素,类似js,rust中的字符串也是可以遍历的。

rust 复制代码
let s: String = String::from("老前端");

for c in s.chars() {
    println!("字符: {}", c);
}
// 字符: 老
// 字符: 前
// 字符: 端

for b in s.bytes() {
    println!("字节: {}", b);
}
// 字节: 232
// 字节: 128
// 字节: 129
// 字节: 229
// 字节: 137
// 字节: 141
// 字节: 231
// 字节: 171
// 字节: 175

如果需要在某个索引下处理某些逻辑,可以使用s.chars().enumerate()函数遍历字符串

rust 复制代码
for (idx, c) in s.chars().enumerate() {
   println!("idx: {}, 字符: {}", idx, c);
}
// idx: 0, 字符: 老
// idx: 1, 字符: 前
// idx: 2, 字符: 端

这样就方便了,可以在idx===1时处理某些逻辑。

总结

看下来,确实Rust的字符串比js的字符串复杂一些,使用也没那么方便和自由。不同的语言做的抉择不同,rust为了安全、性能,从而牺牲了自由度。

也许,自由都是需要代价的吧

感谢你的阅读,并希望对你有所帮助,让我们下节课再见👋🏻👋🏻👋🏻

相关推荐
TDengine (老段)2 分钟前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
niandb4 分钟前
The Rust Programming Language 学习 (九)
windows·rust
杉之1 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端1 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡1 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木2 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!3 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷3 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript
还是鼠鼠4 小时前
Node.js全局生效的中间件
javascript·vscode·中间件·node.js·json·express
自动花钱机4 小时前
WebUI问题总结
前端·javascript·bootstrap·css3·html5