老前端人学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为了安全、性能,从而牺牲了自由度。

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

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

相关推荐
Elena_Lucky_baby19 分钟前
实现路由懒加载的方式有哪些?
前端·javascript·vue.js
Domain-zhuo19 分钟前
如何利用webpack来优化前端性能?
前端·webpack·前端框架·node.js·ecmascript
理想不理想v23 分钟前
webpack如何自定义插件?示例
前端·webpack·node.js
小华同学ai41 分钟前
ShowDoc:Star12.3k,福利项目,个人小团队的在线文档“简单、易用、轻量化”还专门针对API文档、技术文档做了优化
前端·程序员·github
一雨方知深秋42 分钟前
智慧商城:封装getters实现动态统计 + 全选反选功能
开发语言·javascript·vue2·foreach·find·every
海威的技术博客44 分钟前
关于JS中的this指向问题
开发语言·javascript·ecmascript
王解1 小时前
Vue CLI 脚手架创建项目流程详解 (2)
前端·javascript·vue.js
刘大浪1 小时前
vue.js滑动到顶便锁定位置
前端·javascript·vue.js
小金刚®1 小时前
构建简洁之美:我的第一个前端页面
前端
ordinary902 小时前
指令-v-for的key
前端·javascript·vue.js