用户工具

站点工具


rust:第九章字符串处理

第九章 字符串处理

9.1 Rust中的字符串类型

Rust中有两种主要的字符串类型:

  • String:可变的、有所有权的UTF-8编码字符串
  • str:不可变的字符串slice,通常以&str形式使用

9.2 String类型

创建String

// 空字符串
let mut s = String::new();
 
// 从字符串字面量创建
let s = String::from("hello");
let s = "hello".to_string();
 
// 从其他类型创建
let s = format!("{}-{}-{:?}", "hello", 42, true);

修改String

追加字符串:

let mut s = String::from("foo");
s.push_str("bar");  // foobar
 
let mut s = String::from("lo");
s.push('l');  // lol,push追加单个字符

连接字符串:

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;  // s1被移动,s2被借用
 
// 使用format!(不会获取所有权)
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);  // s1, s2, s3都仍然有效

插入和删除:

let mut s = String::from("Hello");
 
// 插入字符
s.insert(5, ',');  // "Hello,"
 
// 插入字符串
s.insert_str(6, " world");  // "Hello, world"
 
// 删除字符(按索引的字节位置)
s.remove(0);  // 删除第一个字符
 
// 清除
s.clear();  // 清空字符串

9.3 字符串索引

Rust不支持按索引访问字符串:

let s = String::from("hello");
// let h = s[0];  // 错误!

原因:UTF-8编码

let hello = "中国人";
 
// 每个汉字占3字节
println!("字节数:{}", hello.len());  // 9
 
// 获取字节
for b in hello.bytes() {
    println!("{}", b);
}

使用chars()遍历Unicode标量值:

for c in "中国人".chars() {
    println!("{}", c);
}
// 输出:中、国、人

使用char_indices():

for (i, c) in "中国人".char_indices() {
    println!("索引{}:字符'{}'", i, c);
}
// 索引0:字符'中'
// 索引3:字符'国'
// 索引6:字符'人'

9.4 字符串Slice

创建Slice:

let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];

注意字节边界:

let s = "中国人";
let zhong = &s[0..3];  // "中" - 正确
// let guo = &s[0..2];    // 错误!不是有效的UTF-8边界

9.5 字符串遍历

按字符遍历

for c in "hello 中国人".chars() {
    println!("{}", c);
}

按字节遍历

for b in "hello".bytes() {
    println!("{}", b);
}

按行遍历

let text = "line1\nline2\nline3";
for line in text.lines() {
    println!("{}", line);
}

按单词遍历

let text = "hello world from Rust";
for word in text.split_whitespace() {
    println!("{}", word);
}

9.6 字符串方法

查找和替换

let s = String::from("hello world");
 
// 查找子串位置
let pos = s.find("world");  // Some(6)
let pos = s.find("xyz");    // None
 
// 替换
let new_s = s.replace("world", "Rust");  // "hello Rust"
 
// 替换指定次数
let new_s = s.replacen("l", "L", 1);  // "heLlo world"

分割字符串

let s = "a,b,c,d";
 
// 按字符分割
let parts: Vec<&str> = s.split(',').collect();
 
// 按多个字符分割
let s = "a b\tc\nd";
let parts: Vec<&str> = s.split_whitespace().collect();
 
// 按字符串分割
let s = "hello::world::Rust";
let parts: Vec<&str> = s.split("::").collect();
 
// 限制分割次数
let s = "a,b,c,d";
let parts: Vec<&str> = s.splitn(2, ',').collect();  // ["a", "b,c,d"]

修剪空白

let s = "  hello world  ";
 
// 去除两端
let trimmed = s.trim();      // "hello world"
 
// 去除开头
let trimmed = s.trim_start();
 
// 去除结尾
let trimmed = s.trim_end();
 
// 去除指定字符
let s = "xxxhello worldxxx";
let trimmed = s.trim_matches('x');  // "hello world"

大小写转换

let s = "Hello";
 
println!("{}", s.to_uppercase());  // "HELLO"
println!("{}", s.to_lowercase());  // "hello"

检查

let s = "hello world";
 
// 是否以...开头
assert!(s.starts_with("hello"));
 
// 是否以...结尾
assert!(s.ends_with("world"));
 
// 是否包含
assert!(s.contains("lo wo"));
 
// 是否符合模式
assert!(s.matches("l").count() == 3);

9.7 String与&str的转换

String转&str

let s = String::from("hello");
 
// &操作符
let slice: &str = &s;
 
// as_str()
let slice = s.as_str();
 
// 自动解引用
fn takes_str(s: &str) {}
takes_str(&s);  // 自动转换

&str转String

let slice = "hello";
 
// to_string()
let s = slice.to_string();
 
// String::from()
let s = String::from(slice);
 
// into()
let s: String = slice.into();

9.8 UTF-8处理

获取字符数

let s = "中国人";
 
// 字节数
println!("字节数:{}", s.len());  // 9
 
// 字符数(Unicode标量值)
println!("字符数:{}", s.chars().count());  // 3

字符边界检查

fn safe_slice(s: &str, start: usize, end: usize) -> Option<&str> {
    if s.is_char_boundary(start) && s.is_char_boundary(end) {
        Some(&s[start..end])
    } else {
        None
    }
}
 
let s = "中国人";
println!("{:?}", safe_slice(s, 0, 3));   // Some("中")
println!("{:?}", safe_slice(s, 0, 2));   // None

9.9 字符串与所有权

迭代时消耗所有权

let s = String::from("hello");
 
// into_bytes()消耗所有权
let bytes = s.into_bytes();
// s在这里已失效

避免不必要的克隆

// 不好的做法
let s1 = String::from("hello");
let s2 = s1.clone();  // 深拷贝
let slice = &s2[0..2];
 
// 好的做法
let s1 = String::from("hello");
let slice = &s1[0..2];  // 借用

练习题

练习题9.1:反转字符串

fn reverse(s: &str) -> String {
    s.chars().rev().collect()
}
 
fn main() {
    let s = "hello 中国";
    println!("原字符串:{}", s);
    println!("反转后:{}", reverse(s));
}

练习题9.2:判断回文

fn is_palindrome(s: &str) -> bool {
    let s: String = s.chars()
        .filter(|c| c.is_alphanumeric())
        .map(|c| c.to_lowercase().next().unwrap())
        .collect();
 
    s == s.chars().rev().collect::<String>()
}
 
fn main() {
    println!("{}", is_palindrome("A man, a plan, a canal: Panama"));  // true
    println!("{}", is_palindrome("race a car"));  // false
    println!("{}", is_palindrome("中国人中国"));  // true
}

练习题9.3:字符串统计

fn analyze(s: &str) {
    println!("字符串:{}", s);
    println!("字节数:{}", s.len());
    println!("字符数:{}", s.chars().count());
    println!("单词数:{}", s.split_whitespace().count());
    println!("行数:{}", s.lines().count());
 
    // 统计各类字符
    let mut letters = 0;
    let mut digits = 0;
    let mut spaces = 0;
    let mut others = 0;
 
    for c in s.chars() {
        if c.is_alphabetic() {
            letters += 1;
        } else if c.is_numeric() {
            digits += 1;
        } else if c.is_whitespace() {
            spaces += 1;
        } else {
            others += 1;
        }
    }
 
    println!("字母:{},数字:{},空白:{},其他:{}", 
             letters, digits, spaces, others);
}
 
fn main() {
    let text = "Hello, Rust 2024!\n这是第2行。";
    analyze(text);
}

练习题9.4:CSV解析简化版

fn parse_csv_line(line: &str) -> Vec<&str> {
    line.split(',').map(|s| s.trim()).collect()
}
 
fn main() {
    let csv = "name, age, city\nAlice, 30, New York\nBob, 25, London";
 
    for line in csv.lines() {
        let fields = parse_csv_line(line);
        println!("{:?}", fields);
    }
}

本章小结

本章学习了Rust的字符串处理:

  • String:可变的、有所有权的字符串
  • &str:字符串slice,不可变借用
  • 索引限制:Rust不支持字符串索引,因为UTF-8变长编码
  • 遍历方式:chars()、bytes()、lines()等
  • 常用方法:find、replace、split、trim等
  • UTF-8处理:注意字符边界,使用is_char_boundary检查

理解String和&str的区别,以及UTF-8编码特性,是Rust字符串处理的关键。

rust/第九章字符串处理.txt · 最后更改: 127.0.0.1