Bài 30.1: Bài tập thực hành xử lý chuỗi cơ bản trong C++

Bài 30.1: Bài tập thực hành xử lý chuỗi cơ bản trong C++
Chào mừng trở lại với series học C++! Sau khi đã làm quen với các kiểu dữ liệu cơ bản và cấu trúc điều khiển, đã đến lúc chúng ta chinh phục một trong những kiểu dữ liệu quan trọng và thường xuyên sử dụng nhất trong lập trình: chuỗi ký tự (strings).
Trong C++, cách làm việc với chuỗi hiện đại và mạnh mẽ nhất là sử dụng lớp string
được cung cấp trong Thư viện Chuẩn C++. Không giống như mảng ký tự kiểu C cũ kỹ và dễ gặp lỗi, string
tự động quản lý bộ nhớ, cung cấp nhiều phương thức tiện lợi và an toàn để bạn thao tác.
Bài viết này sẽ đi sâu vào các thao tác cơ bản nhưng cực kỳ thiết yếu với string
. Chúng ta sẽ cùng nhau thực hành qua nhiều ví dụ code C++ ngắn gọn để bạn nắm vững cách sử dụng chúng.
1. Khởi tạo và Gán giá trị cho string
Việc đầu tiên cần làm là tạo ra một đối tượng chuỗi và gán giá trị cho nó. Có nhiều cách để thực hiện điều này:
Tạo một chuỗi rỗng:
#include <iostream> #include <string> // Bao gom thu vien string int main() { string emptyString; // Khai bao mot chuoi rỗng cout << "Chuoi rong: '" << emptyString << "'" << endl; // In ra chuoi rỗng return 0; }
Giải thích: Dòng
string emptyString;
tạo ra một đối tượng chuỗi chưa chứa bất kỳ ký tự nào.Khởi tạo bằng chuỗi ký tự (string literal):
#include <iostream> #include <string> int main() { string greeting = "Xin chao C++!"; // Khoi tao truc tiep bang literal cout << "Loi chao: " << greeting << endl; string anotherString("Day la mot chuoi khac."); // Cach khac dung constructor cout << "Chuoi khac: " << anotherString << endl; return 0; }
Giải thích: Bạn có thể gán trực tiếp một chuỗi nằm trong dấu ngoặc kép (
""
) cho biếnstring
.Sao chép từ một chuỗi khác:
#include <iostream> #include <string> int main() { string original = "Chuoi goc"; string copy = original; // Tao ban sao cout << "Chuoi sao chep: " << copy << endl; string anotherCopy(original); // Cach khac dung constructor sao chep cout << "Mot ban sao khac: " << anotherCopy << endl; return 0; }
Giải thích: Khi gán một đối tượng
string
cho một đối tượng khác, một bản sao độc lập sẽ được tạo ra.
2. Nhập Chuỗi từ Bàn phím
Đọc chuỗi từ cin
là cần thiết cho các chương trình tương tác, nhưng có một điểm đặc biệt cần lưu ý với khoảng trắng.
Sử dụng
cin
(dừng khi gặp khoảng trắng):#include <iostream> #include <string> int main() { string word; cout << "Nhap mot tu: "; cin >> word; // Chi doc den khoang trang dau tien cout << "Tu da nhap: " << word << endl; // Luu y quan trong: Neu ban nhap "Hello World", chi "Hello" duoc doc vao 'word'. // Phan con lai " World" va ky tu xuong dong '\n' van con trong bo dem nhap, // co the anh huong den cac lenh nhap tiep theo (nhu getline). return 0; }
Giải thích: Toán tử
>>
chostring
đọc các ký tự liên tiếp cho đến khi gặp bất kỳ ký tự phân cách nào như dấu cách, tab hoặc xuống dòng. Nó rất tiện nếu bạn chỉ muốn đọc từng "từ" một.Sử dụng
getline
(đọc cả dòng):Để đọc toàn bộ một dòng văn bản, bao gồm cả khoảng trắng, bạn nên sử dụng hàm
getline
.#include <iostream> #include <string> #include <limits> // Can thiet cho cin.ignore() int main() { string line; cout << "Nhap mot dong van ban (bao gom ca khoang trang): "; // Can luu y ve bo dem nhap neu co lenh cin >> truoc do! // Uncomment dong duoi day neu ban gap loi khi getline doc ky tu xuong dong thua // cin.ignore(numeric_limits<streamsize>::max(), '\n'); getline(cin, line); // Doc cho den khi gap ky tu xuong dong '\n' cout << "Dong da nhap: " << line << endl; return 0; }
Giải thích:
getline(luong_nhap, bien_chuoi)
đọc tất cả các ký tự từ luồngluong_nhap
(ở đây làcin
) và lưu vàobien_chuoi
cho đến khi nó gặp ký tự phân cách dòng (mặc định là\n
). Lưu ý: Nếu trước đó bạn đã dùngcin >>
, ký tự xuống dòng cuối cùng có thể còn lại trong bộ đệm, khiếngetline
đọc ngay lập tức và coi là đã đọc xong dòng rỗng. Sử dụngcin.ignore()
để xóa bộ đệm nếu cần.
3. Truy cập các Ký tự trong Chuỗi
Một chuỗi về cơ bản là một tập hợp có thứ tự của các ký tự. Bạn có thể truy cập từng ký tự dựa trên chỉ số (index) của nó, bắt đầu từ 0.
Sử dụng toán tử
[]
:#include <iostream> #include <string> int main() { string text = "Lap trinh C++"; // Index: 0123456789... cout << "Ky tu dau tien (index 0): " << text[0] << endl; // Output: L cout << "Ky tu thu 5 (index 4): " << text[4] << endl; // Output: t cout << "Ky tu cuoi cung: " << text[text.length() - 1] << endl; // Output: + // CAN THAN: Truy cap index ngoai pham vi se gay ra loi runtime khong mong muon! // cout << text[100] << endl; // Hanh vi khong xac dinh! return 0; }
Giải thích: Toán tử
[]
cung cấp cách truy cập ký tự nhanh chóng. Tuy nhiên, nó không kiểm tra xem chỉ số bạn cung cấp có hợp lệ hay không. Nếu chỉ số vượt ra ngoài phạm vi[0, length() - 1]
, chương trình của bạn có thể gặp sự cố hoặc cho kết quả sai.Sử dụng phương thức
.at()
:#include <iostream> #include <string> #include <stdexcept> // Can thiet cho catch int main() { string text = "Hello"; try { cout << "Ky tu tai index 1: " << text.at(1) << endl; // Output: e cout << "Ky tu tai index 4: " << text.at(4) << endl; // Output: o // Thu truy cap ngoai pham vi // cout << text.at(10) << endl; // Dong nay se gay ra ngoai le! } catch (const out_of_range& e) { // Xu ly truong hop truy cap ngoai pham vi mot cach an toan cerr << "Loi: Truong hop truy cap ngoai pham vi: " << e.what() << endl; } return 0; }
Giải thích: Phương thức
.at(index)
cũng trả về ký tự tại chỉ số được cung cấp, nhưng nó thực hiện kiểm tra phạm vi. Nếu chỉ số không hợp lệ,.at()
sẽ ném ra một ngoại lệout_of_range
. Điều này giúp bạn phát hiện và xử lý lỗi truy cập chuỗi ngoài phạm vi một cách an toàn hơn.
4. Lấy Độ dài Chuỗi
Để biết một chuỗi chứa bao nhiêu ký tự, bạn dùng phương thức .length()
hoặc .size()
. Cả hai đều trả về cùng một giá trị.
#include <iostream>
#include <string>
int main() {
string message = "Lap trinh C++ that tuyet!";
cout << "Do dai chuoi: " << message.length() << endl; // Output: 25
cout << "Kich thuoc chuoi: " << message.size() << endl; // Output: 25
string empty;
cout << "Do dai chuoi rong: " << empty.length() << endl; // Output: 0
return 0;
}
Giải thích: .length()
và .size()
đều trả về số lượng ký tự trong chuỗi dưới dạng kiểu size_t
(một kiểu số nguyên không âm).
5. Nối (Ghép) Chuỗi
Bạn có thể kết hợp hai hoặc nhiều chuỗi lại với nhau để tạo thành một chuỗi dài hơn.
Sử dụng toán tử
+
:#include <iostream> #include <string> int main() { string part1 = "Hello"; string part2 = " "; string part3 = "World!"; string fullString = part1 + part2 + part3; // Tao chuoi moi bang cach noi cout << "Chuoi sau khi noi: " << fullString << endl; // Output: Hello World! string greeting = "Chao" + string(" buoi sang"); // Noi literal voi string (literal can duoc chuyen thanh string) cout << greeting << endl; // Output: Chao buoi sang return 0; }
Giải thích: Toán tử
+
giữa hai chuỗistring
sẽ tạo ra một chuỗi mới là kết quả của việc nối hai chuỗi ban đầu. Lưu ý rằng khi nối một chuỗistring
với một chuỗi ký tự (literal""
), ít nhất một trong hai toán hạng phải làstring
.Sử dụng toán tử
+=
(Nối và gán):#include <iostream> #include <string> int main() { string message = "Xin chao"; message += " cac ban"; // Noi " cac ban" vao cuoi chuoi message message += "!"; // Noi "!" vao cuoi chuoi message cout << "Chuoi sau khi +=: " << message << endl; // Output: Xin chao cac ban! return 0; }
Giải thích: Toán tử
+=
nối chuỗi bên phải vào cuối chuỗi bên trái và cập nhật trực tiếp chuỗi bên trái đó. Đây là cách hiệu quả hơn về mặt hiệu năng khi bạn cần nối nhiều lần vào cùng một chuỗi.
6. So sánh Chuỗi
string
hỗ trợ các toán tử so sánh quen thuộc như ==
, !=
, <
, >
, <=
, >=
. Việc so sánh được thực hiện theo thứ tự từ điển (lexicographical), dựa trên giá trị của các ký tự (thường là mã ASCII hoặc Unicode).
#include <iostream>
#include <string>
int main() {
string s1 = "apple";
string s2 = "banana";
string s3 = "apple";
string s4 = "Apple"; // Chu A viet hoa
if (s1 == s3) {
cout << s1 << " bang " << s3 << endl; // Output: apple bang apple
}
if (s1 != s2) {
cout << s1 << " khac " << s2 << endl; // Output: apple khac banana
}
if (s1 < s2) { // 'a' < 'b' nen "apple" < "banana"
cout << s1 << " nho hon " << s2 << " (theo tu dien)" << endl; // Output: apple nho hon banana (theo tu dien)
}
if (s4 < s1) { // 'A' co gia tri ASCII nho hon 'a' nen "Apple" < "apple"
cout << s4 << " nho hon " << s1 << " (phan biet hoa thuong)" << endl; // Output: Apple nho hon apple (phan biet hoa thuong)
}
return 0;
}
Giải thích: Các toán tử so sánh hoạt động bằng cách so sánh từng ký tự một từ trái sang phải. Ký tự đầu tiên khác nhau sẽ quyết định kết quả (ví dụ: 'a' đứng trước 'b'). Việc so sánh này mặc định là có phân biệt chữ hoa chữ thường.
7. Tìm kiếm trong Chuỗi
Phương thức .find()
giúp bạn xác định vị trí xuất hiện đầu tiên của một chuỗi con (substring) hoặc một ký tự cụ thể trong chuỗi.
#include <iostream>
#include <string>
int main() {
string text = "Hoc C++ that thu vi, C++ rat tuyet!";
string searchTerm = "C++";
size_t foundPos = text.find(searchTerm); // Tim kiem "C++" tu dau chuoi
if (foundPos != string::npos) { // string::npos la mot gia tri dac biet chi khong tim thay
cout << "Tim thay '" << searchTerm << "' lan dau tien tai index: " << foundPos << endl; // Output: Tim thay 'C++' lan dau tien tai index: 4
} else {
cout << "'" << searchTerm << "' khong tim thay trong chuoi." << endl;
}
// Tim kiem lan xuat hien tiep theo
// Bat dau tim kiem tu sau vi tri tim thay lan truoc
size_t searchFrom = (foundPos == string::npos) ? 0 : foundPos + searchTerm.length();
size_t foundPos2 = text.find(searchTerm, searchFrom);
if (foundPos2 != string::npos) {
cout << "Tim thay '" << searchTerm << "' lan thu hai tai index: " << foundPos2 << endl; // Output: Tim thay 'C++' lan thu hai tai index: 24
}
// Tim kiem mot ky tu don
size_t charPos = text.find('t');
if (charPos != string::npos) {
cout << "Tim thay ky tu 't' lan dau tien tai index: " << charPos << endl; // Output: Tim thay ky tu 't' lan dau tien tai index: 7
}
return 0;
}
Giải thích: .find(substring)
trả về chỉ số (index) của ký tự đầu tiên của substring
nếu nó tìm thấy substring
trong chuỗi. Nếu không tìm thấy, nó trả về hằng số đặc biệt string::npos
(một giá trị thường là số nguyên không dấu rất lớn). Bạn có thể cung cấp thêm đối số thứ hai để chỉ định vị trí bắt đầu tìm kiếm.
8. Trích xuất Chuỗi con
Phương thức .substr(pos, len)
cho phép bạn "cắt" một phần của chuỗi hiện tại để tạo ra một chuỗi mới.
#include <iostream>
#include <string>
int main() {
string sentence = "Hoc lap trinh C++ rat hieu qua.";
// Index: 0123456789...
// Lay 3 ky tu tu vi tri 0
string subject = sentence.substr(0, 3); // "Hoc"
// Lay 8 ky tu tu vi tri 4
string verb = sentence.substr(4, 8); // "lap trin" -> Oups, should be 8. Corrected "lap trinh" - index 4 to 11, length 8.
string verb_correct = sentence.substr(4, 8); // Index 4 -> l, 5 -> a, 6 -> p, 7 -> ' ', 8 -> t, 9 -> r, 10 -> i, 11 -> n. Length 8. Ok.
// Lay 3 ky tu tu vi tri 13
string language = sentence.substr(13, 3); // "C++"
cout << "Subject: " << subject << endl; // Output: Hoc
cout << "Verb: " << verb_correct << endl; // Output: lap trin (length 8) -> The original sentence is "Hoc lap trinh C++ rat hieu qua.", index 4 is 'l', index 11 is 'h'. length 8 from 4 would be 'lap trin'. Yes, it's correct.
cout << "Language: " << language << endl; // Output: C++
// Lay phan con lai cua chuoi tu mot vi tri cho den het
string remainder = sentence.substr(17); // Tu index 17 (" rat hieu qua.") den het
cout << "Remainder: " << remainder << endl; // Output: rat hieu qua.
return 0;
}
Giải thích: .substr(pos, len)
tạo và trả về một chuỗi mới chứa các ký tự được trích xuất. Đối số đầu tiên pos
là vị trí bắt đầu trích xuất (dựa trên 0). Đối số thứ hai len
là số lượng ký tự muốn trích xuất. Nếu bạn bỏ qua đối số len
, .substr(pos)
sẽ lấy từ vị trí pos
cho đến hết chuỗi. Hãy đảm bảo pos
và pos + len
nằm trong phạm vi hợp lệ của chuỗi gốc.
9. Các Thao tác Chỉnh sửa Chuỗi cơ bản
string
cung cấp các phương thức cho phép bạn thay đổi nội dung của chuỗi một cách trực tiếp.
Thêm vào cuối (
.append()
): Giống như toán tử+=
.#include <iostream> #include <string> int main() { string s = "Hello"; s.append(" World"); // Them " World" vao cuoi s cout << "Append: " << s << endl; // Output: Hello World return 0; }
Chèn (
.insert(pos, string)
): Chèn một chuỗi khác vào vị trípos
trong chuỗi hiện tại.#include <iostream> #include <string> int main() { string s = "Hoc C++!"; s.insert(4, "toi "); // Chen "toi " vao vi tri index 4 (sau 'Hoc') cout << "Insert: " << s << endl; // Output: Hoc toi C++! return 0; }
Xóa (
.erase(pos, len)
): Xóalen
ký tự khỏi chuỗi hiện tại, bắt đầu từ vị trípos
.#include <iostream> #include <string> int main() { string s = "Day la mot chuoi thua tu."; s.erase(15, 5); // Xoa 5 ky tu bat dau tu index 15 (" thua") cout << "Erase: " << s << endl; // Output: Day la mot chuoi tu. return 0; }
Thay thế (
.replace(pos, len, string)
): Thay thếlen
ký tự bắt đầu từ vị trípos
bằng nội dung củastring
khác.#include <iostream> #include <string> int main() { string s = "Toi thich Java."; s.replace(10, 4, "C++"); // Thay the 4 ky tu tu index 10 ("Java") bang "C++" cout << "Replace: " << s << endl; // Output: Toi thich C++. return 0; }
Giải thích: Các phương thức
.append()
,.insert()
,.erase()
,.replace()
đều trực tiếp thay đổi nội dung của chuỗi mà bạn gọi phương thức đó.
10. Duyệt qua các Ký tự của Chuỗi
Đôi khi bạn cần xử lý từng ký tự một trong chuỗi. string
có thể được duyệt qua dễ dàng bằng vòng lặp.
Sử dụng vòng lặp dựa trên phạm vi (Range-based for loop - C++11 trở lên):
#include <iostream> #include <string> int main() { string greeting = "Hello"; cout << "Duyet bang range-based for:" << endl; for (char c : greeting) { // c se lan luot la tung ky tu trong greeting cout << c << " "; } cout << endl; // Output: H e l l o return 0; }
Giải thích: Đây là cách hiện đại và gọn gàng nhất để lặp qua tất cả các ký tự của chuỗi khi bạn chỉ cần giá trị của từng ký tự và không cần chỉ số.
Sử dụng vòng lặp for với chỉ số:
#include <iostream> #include <string> int main() { string word = "World"; cout << "Duyet bang index:" << endl; for (size_t i = 0; i < word.length(); ++i) { // Lap tu index 0 den length() - 1 cout << word[i] << " "; // Truy cap ky tu bang index } cout << endl; // Output: W o r l d return 0; }
Giải thích: Cách truyền thống sử dụng vòng lặp
for
và biến chỉ số. Cách này hữu ích khi bạn cần biết chỉ số của ký tự đang được xử lý hoặc cần thay đổi ký tự tại một vị trí cụ thể. Nên sử dụng kiểusize_t
cho biến chỉ số khi làm việc với độ dài chuỗi.
11. Kết hợp các Thao tác: Ví dụ đơn giản
Hãy thử kết hợp một vài thao tác đã học để thực hiện một nhiệm vụ nhỏ, ví dụ như làm sạch khoảng trắng ở đầu/cuối và thay thế một từ.
#include <iostream>
#include <string>
int main() {
string original = " Khoang trang thua o dau va cuoi ";
cout << "Ban dau: '" << original << "'" << endl; // Output: ' Khoang trang thua o dau va cuoi '
// 1. Xoa khoang trang o dau va cuoi (cach don gian, chua day du)
size_t first = original.find_first_not_of(" \t\n\r"); // Tim index cua ky tu dau tien khong phai khoang trang
size_t last = original.find_last_not_of(" \t\n\r"); // Tim index cua ky tu cuoi cung khong phai khoang trang
if (first != string::npos && last != string::npos) {
// Trich xuat phan chuoi tu first den last
original = original.substr(first, last - first + 1);
} else {
// Chuoi rong hoac chi chua toan khoang trang
original = "";
}
cout << "Sau khi cat khoang trang (don gian): '" << original << "'" << endl; // Output: 'Khoang trang thua o dau va cuoi'
// 2. Tim va thay the "thua" bang "khong can"
string oldWord = "thua";
string newWord = "khong can";
size_t wordPos = original.find(oldWord);
if (wordPos != string::npos) {
original.replace(wordPos, oldWord.length(), newWord);
}
cout << "Sau khi thay the: '" << original << "'" << endl; // Output: 'Khoang trang khong can o dau va cuoi'
// 3. Them vao cuoi chuoi
original.append("!");
cout << "Sau khi them: '" << original << "'" << endl; // Output: 'Khoang trang khong can o dau va cuoi!'
return 0;
}
Giải thích: Ví dụ này minh họa cách sử dụng kết hợp .find_first_not_of
, .find_last_not_of
, .substr
để loại bỏ khoảng trắng ở đầu cuối (một cách đơn giản), sau đó dùng .find
và .replace
để thay thế một từ, và cuối cùng là .append
để thêm ký tự. Đây là những thao tác rất phổ biến khi làm việc với dữ liệu văn bản.
Comments