Skip to content

Ch 15: String字符串

String是Rust中可变的、堆分配的字符串类型。与&str(字符串切片)不同,String拥有其数据的所有权。

String的内部结构:

struct String {
vec: Vec<u8>, // UTF-8编码的字节向量
}

今天我们学习String的创建、修改和内存管理。

fn main() {
// 空字符串
let s1 = String::new();
// 从字面量创建(&str自动转换)
let s2 = String::from("hello");
let s3 = "world".to_string();
// 带容量的空字符串(预分配内存)
let s4 = String::with_capacity(100);
println!("s1: '{:?}', len: {}", s1, s1.len());
println!("s2: '{}', len: {}, capacity: {}", s2, s2.len(), s2.capacity());
println!("s4 capacity: {}", s4.capacity());
}
fn main() {
// 从字节数组创建(必须是有效的UTF-8)
let bytes = [104, 101, 108, 108, 111]; // "hello"
let s = String::from_utf8(bytes.to_vec()).unwrap();
println!("{}", s);
// 使用from_utf8_lossy处理无效UTF-8
let invalid_bytes = [0x80, 0x81, 0x82];
let s_lossy = String::from_utf8_lossy(&invalid_bytes);
println!("{}", s_lossy);
// 输出: ��� (用U+FFFD替换无效序列)
}
fn main() {
let mut s = String::from("hello");
// push 追加单个字符
s.push(',');
println!("push后: {}", s);
// 输出: hello,
// push_str 追加字符串切片
s.push_str(" world!");
println!("push_str后: {}", s);
// 输出: hello, world!
}
fn main() {
let name = "Alice";
let age = 30;
// format! 格式化拼接
let s = format!("{} is {} years old", name, age);
println!("{}", s);
// 重复拼接
let separator = "-".to_string();
let line = separator.repeat(10);
println!("{}", line);
// 输出: ----------
}

String内部使用Vec存储,所以有capacity的概念。

fn main() {
let mut s = String::with_capacity(10);
println!("初始capacity: {}", s.capacity());
s.push_str("hello");
println!("追加后capacity: {}", s.capacity());
// capacity不会自动缩减
s.shrink_to_fit();
println!("shrink后capacity: {}", s.capacity());
// shrink_to指定最小capacity
s.shrink_to(5);
println!("shrink_to(5)后capacity: {}", s.capacity());
}

注意:shrink_to_fit和shrink_to会尝试释放多余的内存,但具体行为取决于实现。

fn main() {
let mut s = String::with_capacity(5);
println!("初始: len={}, cap={}", s.len(), s.capacity());
s.push_str("abc");
println!("追加abc后: len={}, cap={}", s.len(), s.capacity());
// 再追加会导致重新分配
s.push_str("de");
println!("追加de后: len={}, cap={}", s.len(), s.capacity());
}

重新分配是昂贵的操作,如果知道最终大小,预分配可以避免多次重新分配。

from_utf8将字节转换为String,会检查UTF-8有效性:

fn main() {
// 有效的UTF-8
let valid_bytes = b"hello world";
match String::from_utf8(valid_bytes.to_vec()) {
Ok(s) => println!("有效: {}", s),
Err(e) => println!("错误: {:?}", e),
}
// 无效的UTF-8
let invalid_bytes = b"hello \xFF\xFE world";
match String::from_utf8(invalid_bytes.to_vec()) {
Ok(s) => println!("有效: {}", s),
Err(e) => {
println!("UTF-8错误: {}", e);
println!("无效字节: {:?}", e.into_bytes());
}
}
}
fn main() {
let mut s = String::from("hello world");
// replace 替换所有匹配
let s2 = s.replace("world", "rust");
println!("replace: {}", s2);
// 输出: hello rust
// retain 只保留满足条件的字符
let mut s3 = String::from("hello123world456");
s3.retain(|c| c.is_alphabetic());
println!("retain: {}", s3);
// 输出: helloworld
}
fn main() {
let s = String::from("42");
// parse 转换为数值类型
let num: i32 = s.parse().unwrap();
println!("parse: {}", num + 8);
// into_bytes 转换为字节数组
let bytes = s.into_bytes();
println!("bytes: {:?}", bytes);
// into_boxed_str 转换为Box<str>
let boxed: Box<str> = String::from("hello").into_boxed_str();
println!("boxed len: {}", boxed.len());
}
方法说明
new() / from()创建空字符串
with_capacity(n)创建带容量的字符串
push(c)追加字符
push_str(s)追加字符串
pop()移除并返回最后一个字符
truncate(n)截断到n个字符
clear()清空字符串
len()字节长度
is_empty()是否为空
capacity()当前容量
reserve(n)预留容量
shrink_to_fit()收缩到合适大小
replace(old, new)替换
parse::<T>()解析为其他类型
into_bytes()转为字节数组
  1. UTF-8编码:String是UTF-8编码的,一个字符可能占用1-4个字节。len()返回字节数,而非字符数。
  2. 按字节索引:不能用整数索引字符串,应使用chars()迭代器。
  3. 容量预分配:如果知道最终大小,预先分配容量可以避免重新分配。
fn main() {
let s = "hello";
// 错误:按字节索引
// println!("{}", s[0]); // 编译错误!
// 正确:迭代字符
for c in s.chars() {
print!("{} ", c);
}
println!();
// 输出: h e l l o
}

今天我们学习了String的核心操作:

  1. 创建方法 - new()、from()、to_string()、with_capacity()
  2. 追加操作 - push、push_str、format!
  3. 容量管理 - capacity、reserve、shrink_to
  4. UTF-8处理 - from_utf8、from_utf8_lossy
  5. 修改操作 - replace、retain、truncate

String是Rust中处理文本的基础类型,正确理解其UTF-8编码和容量管理对于写出高效的Rust代码至关重要。