15·复合类型入门

String 与 &str

String 与 &str

学习目标

  1. 理解 String&str 的区别
  2. 掌握字符串的创建和操作
  3. 理解 UTF-8 编码的影响
  4. 掌握字符串与其他类型的转换

核心概念

String vs &str

String(堆分配,可变,拥有所有权)
┌──────────┐    堆内存
│ ptr ────────→ [h, e, l, l, o]
│ len: 5   │
│ cap: 8   │    ← 预分配的容量
└──────────┘

&str(切片引用,不可变,借用)
┌──────────┐
│ ptr ────────→ 某处的 UTF-8 字节
│ len: 5   │
└──────────┘
特性String&str
存储引用
可变
所有权拥有借用
大小24 字节(ptr+len+cap)16 字节(ptr+len)

创建字符串

fn main() {
    // 从字面量创建
    let s1 = String::from("hello");
    let s2 = "hello".to_string();
    let s3 = "hello".to_owned();

    // 空字符串
    let s4 = String::new();

    // 带容量
    let s5 = String::with_capacity(100);

    // 字符串字面量(&str 类型)
    let s6: &str = "hello";
}

字符串操作

fn main() {
    let mut s = String::from("hello");

    // 追加
    s.push('!');              // 追加单个字符
    s.push_str(" world");    // 追加字符串切片

    // 拼接
    let s1 = String::from("hello");
    let s2 = String::from(" world");
    let s3 = s1 + &s2;       // 注意:s1 被移动,s2 被借用
    // println!("{}", s1);   // ❌ s1 已失效

    // format!(推荐,不移动任何值)
    let a = String::from("hello");
    let b = String::from("world");
    let c = format!("{} {}", a, b);  // a, b 仍有效

    // 替换
    let s = String::from("hello world");
    let replaced = s.replace("world", "rust");  // "hello rust"

    // 分割
    let parts: Vec<&str> = "a,b,c".split(',').collect();
    // ["a", "b", "c"]

    // 修剪
    let s = "  hello  ";
    let trimmed = s.trim();  // "hello"
}

索引问题

fn main() {
    let hello = String::from("hello");

    // ✅ 按字节索引
    let byte = hello.as_bytes()[0];  // 104 ('h' 的 ASCII)

    // ❌ 不能直接用字符串索引
    // let c = hello[0];  // 编译错误

    // ✅ 用 chars() 遍历
    for c in hello.chars() {
        println!("{}", c);
    }

    // ✅ 用 bytes() 遍历字节
    for b in hello.bytes() {
        println!("{}", b);
    }

    // ✅ 按字符索引
    let first = hello.chars().nth(0);  // Some('h')
}

UTF-8 与中文

fn main() {
    let s = String::from("你好世界");

    println!("字节数: {}", s.len());       // 12(每个中文字符 3 字节)
    println!("字符数: {}", s.chars().count()); // 4

    // ❌ 在非字符边界切片会 panic
    // let slice = &s[0..2];

    // ✅ 安全的切片方式
    let first_two: String = s.chars().take(2).collect();
    println!("{}", first_two);  // "你好"
}

类型转换

fn main() {
    // &str → String
    let s1: String = "hello".to_string();
    let s2: String = String::from("hello");
    let s3: String = "hello".to_owned();

    // String → &str
    let s = String::from("hello");
    let slice: &str = &s;
    let slice: &str = s.as_str();

    // 数字 → String
    let n = 42;
    let s = n.to_string();
    let s = format!("{}", n);

    // String → 数字
    let n: i32 = "42".parse().unwrap();
    let n: f64 = "3.14".parse().unwrap();

    // Vec<u8> → String
    let bytes = vec![104, 101, 108, 108, 111];
    let s = String::from_utf8(bytes).unwrap();
}

常用方法

fn main() {
    let s = String::from("Hello, World!");

    // 查询
    println!("len: {}", s.len());              // 字节数
    println!("is_empty: {}", s.is_empty());    // 是否为空
    println!("contains: {}", s.contains("World")); // 是否包含
    println!("starts_with: {}", s.starts_with("Hello")); // 是否以开头
    println!("ends_with: {}", s.ends_with("!")); // 是否以结尾

    // 查找
    let pos = s.find("World");  // Some(7)

    // 大小写
    let upper = s.to_uppercase();  // "HELLO, WORLD!"
    let lower = s.to_lowercase();  // "hello, world!"
}

实践练习

练习 1:字符串反转

fn reverse_string(s: &str) -> String {
    s.chars().rev().collect()
}

fn main() {
    println!("{}", reverse_string("hello"));     // "olleh"
    println!("{}", reverse_string("你好世界"));   // "界世好你"
}

练习 2:统计字符

fn char_count(s: &str) -> std::collections::HashMap<char, usize> {
    let mut map = std::collections::HashMap::new();
    for c in s.chars() {
        *map.entry(c).or_insert(0) += 1;
    }
    map
}

fn main() {
    let counts = char_count("hello world");
    for (ch, count) in &counts {
        println!("'{}': {}", ch, count);
    }
}

练习 3:首字母大写

fn capitalize_first(s: &str) -> String {
    let mut chars = s.chars();
    match chars.next() {
        None => String::new(),
        Some(first) => {
            let upper: String = first.to_uppercase().collect();
            upper + chars.as_str()
        }
    }
}

fn main() {
    println!("{}", capitalize_first("hello"));  // "Hello"
    println!("{}", capitalize_first("rust"));   // "Rust"
    println!("{}", capitalize_first(""));       // ""
}

练习 4:单词计数

fn word_count(text: &str) -> usize {
    text.split_whitespace().count()
}

fn main() {
    let text = "Rust is a systems programming language";
    println!("单词数: {}", word_count(text));
}

常见错误

1. 字符串索引

let s = String::from("hello");
// let c = s[0];  // ❌ 不能索引 String
let c = s.chars().nth(0).unwrap();  // ✅

2. 中文字符切片

let s = String::from("你好");
// let slice = &s[0..1];  // ❌ panic: not a char boundary
let first_char: String = s.chars().take(1).collect();  // ✅

3. 性能陷阱

// ❌ 在循环中用 + 拼接(每次创建新 String)
let mut result = String::new();
for i in 0..1000 {
    result = result + &i.to_string();  // O(n²)
}

// ✅ 用 push_str 或 format!
let mut result = String::new();
for i in 0..1000 {
    result.push_str(&i.to_string());  // O(n)
}

小结

操作语法
创建String::from("..."), "..."..to_string()
追加push, push_str
拼接format!, +
切片&s[a..b](按字节,需在字符边界)
遍历chars(), bytes()
转换parse(), to_string(), as_str()

练习编辑器

rust
Loading...