📝 Làm Việc Với Chuỗi
Mục Tiêu Bài Học
Sau bài này, bạn sẽ:
- ✅ Sử dụng các methods cơ bản: len, is_empty, trim
- ✅ Chuyển đổi case: to_uppercase, to_lowercase
- ✅ Tách chuỗi với split
- ✅ Nối chuỗi với +, format!, push_str
🤔 Tại Sao Cần Xử Lý Chuỗi?
Trong lập trình, chúng ta thường xuyên cần:
- Kiểm tra độ dài text
- Chuyển chữ hoa/thường
- Loại bỏ khoảng trắng
- Tách chuỗi thành từng phần
- Nối nhiều chuỗi lại
String methods giúp làm việc này dễ dàng! 🎯
Ẩn Dụ
Hãy tưởng tượng String methods như công cụ sửa chữa biển báo công trình:
🛠️ Bộ công cụ:
- ✂️ Cắt (split): Cắt biển thành từng phần
- 📏 Đo (len): Đo độ dài chữ
- 🧹 Lau (trim): Xóa bụi bẩn hai đầu
- 🔠 Viết hoa (uppercase): Làm chữ to hơn
- 🔗 Hàn (push_str): Nối thêm text
Đồ bảo hộ an toàn (Rust) đảm bảo bạn không phá hỏng biển gốc! 🛡️
📏 len() - Độ Dài Chuỗi
Trả về số bytes trong String:
fn main() {
let text = String::from("Hello");
println!("Độ dài: {}", text.len());
}
Kết quả:
Độ dài: 5
Lưu Ý Với Unicode
fn main() {
let english = String::from("Hello");
let vietnamese = String::from("Xin chào");
println!("English - Bytes: {}", english.len());
println!("English - Chars: {}", english.chars().count());
println!("Vietnamese - Bytes: {}", vietnamese.len());
println!("Vietnamese - Chars: {}", vietnamese.chars().count());
}
Kết quả:
English - Bytes: 5
English - Chars: 5
Vietnamese - Bytes: 10
Vietnamese - Chars: 8
Chú Ý
.len() trả về số bytes, không phải số ký tự!
Với tiếng Việt có dấu, mỗi ký tự có thể chiếm nhiều bytes:
- 'a' = 1 byte
- 'à' = 2 bytes
- 'ă' = 2 bytes
Dùng .chars().count() để đếm số ký tự.
❓ is_empty() - Kiểm Tra Rỗng
Kiểm tra String có rỗng không:
fn main() {
let text1 = String::from("Hello");
let text2 = String::from("");
println!("text1 rỗng? {}", text1.is_empty());
println!("text2 rỗng? {}", text2.is_empty());
}
Kết quả:
text1 rỗng? false
text2 rỗng? true
Ví Dụ Thực Tế
fn main() {
let mut input = String::new();
if input.is_empty() {
println!("⚠️ Vui lòng nhập dữ liệu!");
} else {
println!("✅ Đã nhập: {}", input);
}
}
Kết quả:
⚠️ Vui lòng nhập dữ liệu!
🔠 to_uppercase() / to_lowercase()
Chuyển đổi chữ hoa/thường:
fn main() {
let text = String::from("Rust Programming");
let upper = text.to_uppercase();
let lower = text.to_lowercase();
println!("Gốc: {}", text);
println!("Hoa: {}", upper);
println!("Thường: {}", lower);
}
Kết quả:
Gốc: Rust Programming
Hoa: RUST PROGRAMMING
Thường: rust programming
Ví Dụ: So Sánh Không Phân Biệt Hoa Thường
fn main() {
let input = String::from("RUST");
let target = "rust";
if input.to_lowercase() == target {
println!("✅ Khớp!");
} else {
println!("❌ Không khớp!");
}
}
Kết quả:
✅ Khớp!
🧹 trim() - Loại Bỏ Khoảng Trắng
Xóa khoảng trắng đầu và cuối chuỗi:
fn main() {
let text = String::from(" Hello World ");
println!("Trước: '{}'", text);
println!("Sau: '{}'", text.trim());
}
Kết quả:
Trước: ' Hello World '
Sau: 'Hello World'
Các Biến Thể
fn main() {
let text = String::from(" Hello ");
println!("trim_start: '{}'", text.trim_start()); // Xóa đầu
println!("trim_end: '{}'", text.trim_end()); // Xóa cuối
println!("trim: '{}'", text.trim()); // Xóa cả hai
}
Kết quả:
trim_start: 'Hello '
trim_end: ' Hello'
trim: 'Hello'
Ví Dụ Thực Tế: Xử Lý Input
use std::io;
fn main() {
let mut input = String::new();
println!("Nhập tên:");
io::stdin().read_line(&mut input).expect("Lỗi đọc input");
let name = input.trim(); // ✅ Xóa \n và khoảng trắng
if !name.is_empty() {
println!("Xin chào, {}!", name);
} else {
println!("Bạn chưa nhập tên!");
}
}
✂️ split() - Tách Chuỗi
Tách chuỗi thành các phần nhỏ:
fn main() {
let text = "Táo,Cam,Chuối,Xoài";
for fruit in text.split(',') {
println!("🍎 {}", fruit);
}
}
Kết quả:
🍎 Táo
🍎 Cam
🍎 Chuối
🍎 Xoài
split_whitespace() - Tách Theo Khoảng Trắng
fn main() {
let sentence = "Học Rust rất vui";
let words: Vec<&str> = sentence.split_whitespace().collect();
println!("Số từ: {}", words.len());
for (i, word) in words.iter().enumerate() {
println!("{}. {}", i + 1, word);
}
}
Kết quả:
Số từ: 4
1. Học
2. Rust
3. rất
4. vui
Ví Dụ: Phân Tích CSV
fn main() {
let csv_line = "An,20,8.5";
let parts: Vec<&str> = csv_line.split(',').collect();
let ten = parts[0];
let tuoi = parts[1].parse::<i32>().unwrap();
let diem = parts[2].parse::<f64>().unwrap();
println!("📝 Sinh viên: {}", ten);
println!("🎂 Tuổi: {}", tuoi);
println!("📊 Điểm: {}", diem);
}
Kết quả:
📝 Sinh viên: An
🎂 Tuổi: 20
📊 Điểm: 8.5
🔗 Nối Chuỗi
Cách 1: Toán Tử +
fn main() {
let s1 = String::from("Hello");
let s2 = String::from(" World");
let result = s1 + &s2; // ✅ s1 bị move, s2 mượn
// println!("{}", s1); // ❌ Lỗi - s1 đã move
println!("{}", result);
println!("{}", s2); // ✅ s2 vẫn dùng được
}
Kết quả:
Hello World
World
Lưu Ý Ownership
Với +:
- String đầu bị move (không dùng được nữa)
- String sau được mượn (&)
let s1 = String::from("Hello");
let s2 = String::from(" World");
let result = s1 + &s2; // s1 move, s2 borrow
Cách 2: format! Macro
Không move ownership, tạo String mới:
fn main() {
let s1 = String::from("Hello");
let s2 = String::from("World");
let result = format!("{} {}", s1, s2);
println!("{}", result);
println!("Vẫn dùng: {} và {}", s1, s2); // ✅ Cả hai OK
}
Kết quả:
Hello World
Vẫn dùng: Hello và World