Rust字符串
str
- str类型是一种原生类型,str类型具有切片的所有特性,包括无固定大小和只读。
- 因为str就是一个切片,通常会借用一个str,即&str。
- str类型有两个字段组成,指向字符串数据的指针和长度。
字符串字面量定义在(“…”)内,是程序整个生命周期内都存在的str值。
因此字符串字面量的生命周期是静态的,表示为&'static str
字符串
- 字符串(String)类型定义在Rust标准库中,是基于特化的向量实现的,由字符值组成。
- 字符串和向量一样是可变的也是可增长的。
- 字符串Striing类型包含3个字段:指向底层数组的指针、长度、容量。
- 底层数组是分配的内存空间
- 长度是按照utf-8编码占据的字节数
- 容量是底层分配给数组空间的长度
创建字符串实例
- 使用String::from(“str”)和str::to_string(),将str转成String
fn main() {let str1 = String::from("hello world1");let str2 = "hello world2".to_string();println!("{} {}",str1, str2);
}
- 也可以通过new构造函数为String创造一个新的空字符串,然后将字符串追加进里面,前提是可变
fn main() {let mut new_str = String::new();new_str.push_str("hello world3");println!("{}",new_str);
}
如前所述,字符串是一种特殊的字符值向量,你甚至可以直接从向量创建一个字符串。
fn main() {let v = vec![65, 114, 107, 97, 110, 115, 97, 115];let str = String::from_utf8(v).unwrap();println!("{}",str);
}
在这个示例中,"Arkansas"这个码值被存储到一个向量v内,例如码值65对应的字符A,通过from_utf函数将这个向量转换成一个字符串
字符串长度
一个给定的unicode字符的长度是多少?这个问题看似简单实际上很复杂。
首先,这取决于你是指字符串的字符个数还是字节。一个utf-8字符可以用1-4个字节来描述,asscii字符,在unicode中是1字节。然而位于代码空间其他位置的字符大小可能是多个字节
- Ascii是单字节大小
- 希腊字符是2字节大小
- 中文字符是3字节大小
- 表情符号是4字节大小
对于ascii,字节长度和字符数量是相同的。而对于其他字符集可能会有所不同。len函数返回字符串中的字节数
fn main() {let str = "你好世界";println!("{}",str.len());
}
理应打印的是4,实际上却是12
要获取字符串中字符的数量,可以首先使用chars返回字符串字符的迭代器,然后在迭代器上调用count方法来统计字符数量
fn main() {let str = "你好世界";let size = str.chars().count();println!("{}",size);
}
扩展字符串
你可以扩展一个字符串String的值,但你不能扩展str类型的值。下面提供了几个方法
- push 对于String追加char值
- push_str 对于String追加一个str
fn main() {let mut str = String::new();str.push_str("hello world");str.push('!');println!("{}",str);
}
- insert
- insert_str
有时候,你可能不仅仅想在字符串末尾追加新内容,而是想将新内容插入到已有字符串中间的某个位置。
fn main() {let mut characters = "ac".to_string();characters.insert(1, 'b');println!("{}",characters);let mut numbers = "one three".to_string();numbers.insert_str(4, "two");println!("{}", numbers);
}
字符串容量
作为特化的向量,String具有一个底层数组和一个容量。
- 底层数组是存储字符串字符的空间,容量是底层数组的总大小,而长度则是字符串当前占用的大小。
- 当长度超过容量时,底层数组必须重新分配并进行扩展,
当底层数组重新分配发生时,会有性能损失。因此避免不必要的重新分配可以提供程序的性能。
fn main() {let mut str = '我'.to_string();println!("容量:{} 长度:{}",str.capacity(), str.len());str.push('是');println!("容量:{} 长度:{}",str.capacity(), str.len());str.push('谁');println!("容量:{} 长度:{}",str.capacity(), str.len());
}
上述例子将字符转换为字符串,然后每次追加一个字符,都引起字符串str的扩容,发生了两次重新分配,对应的3->8->16
如果一开始就能预估需要多大的字符值数组,那么在前面例子就可以给出更高效的写法,通过with_capacity()可以在创建字符串时手动指定容量大小
fn main() {let mut str = String::with_capacity(12);str.push('我');str.push('是');str.push('谁');println!("容量:{} 长度:{} 内容:{}",str.capacity(), str.len(), str);
}
访问字符串的值
我们知道,字符串本质上就是字符值数组,所以通过数组下标索引来访问吗?
fn main() {let str = "你好世界".to_string();let ch = str[1];
}
然后你将会得到类似这样的错误
error[E0277]: the type `str` cannot be indexed by `{integer}`
虽然错误本身是正确的,但没有解释清楚根本原因。
实际上,根本问题是: 对字符串使用索引进行访问是存在歧义的,我们无法确定索引值究竟对应的字节位置还是字符位置。没有解决这一歧义,继续这种操作会被编译器认为是不安全的。
因此,在Rust中直接通过索引来访问字符串中的字符是明确禁止的。
你可以通过字符串切片来访问String中的字符,起始索引和结束索引来表示字节位置,切片的结果是一个str
string[startIndex..endIndex]
示例
fn main() {let str = "你好世界".to_string();let ch = &str[3..=5];println!("{}", ch);
}
字符位置如下图所示
当尝试获取字符串切片的前两个字符,但是切片的位置是不正确的,就会引发一个panic
示例
fn main() {let str = "你好世界".to_string();let ch = &str[0..8];println!("{}", ch);
}
输出
byte index 8 is not a char boundary; it is inside '世' (bytes 6..9) of `你好世界`
在获取字符串切片之前,你可以手动调用str类型的is_char_boundary方法来确定给定索引位置是否与字符边界的开始对齐
fn main() {let str = "你好世界".to_string();println!("{}",str.is_char_boundary(0)); // trueprintln!("{}",str.is_char_boundary(1)); //false
}
字符串里的字符
字符串由字符组成,一个很有用的操作是迭代每一个字符。比如对每个字符执行某种操作、对字符进行编码、统计字符数量、或者搜索并删除包含字母e的所有单词等等。
字符串的chars方法会返回一个字符串迭代器,方便我们遍历访问字符串中的每一个字符。
fn main() {let str = "你好世界".to_string();for ch in str.chars() {println!("{}", ch);}
}
也可以使用迭代器的nth方法读取某个位置的一个字符
fn main() {let str = "你好世界".to_string();let ch1 = str.chars().nth(1).unwrap();println!("{}", ch1);}
格式化字符串
在需要&str的场合,可以借用字符串的&String来代替。这时String会继承str的所有方法,这种隐式转换的原理就是String类型为str实现了Deref trait,这种自动转换行为被称为Deref强制转换,但反过来,从str转换为String类型是不允许的
fn foo(str: &str) {println!("{}", str);
}
fn main() {let str = "hello".to_string();foo(&str);
}
格式化字符串
如果你需要创建格式化的字符串,那么可以使用format!()宏,这个宏与println!()的用法类似,不同的是,它返回一个格式化后的字符串,而不是直接输出。
fn main() {let left = 5;let right = 10;let result = format!("{}+{}={}",left, right, left+right);println!("{}",result);
}