XÂU KÝ TỰ TRONG C++
Chào các bạn học sinh và lập trình viên! Trong các bài toán lập trình thi đấu thực tế trên hệ thống Sen Code Online Judge!, bên cạnh việc xử lý các con số toán học, chúng ta thường xuyên gặp các bài toán liên quan đến văn bản, ký tự như kiểm tra tính hợp lệ của mật khẩu, chuẩn hóa họ tên học sinh, hay xử lý dữ liệu lớn.
Để thực hiện việc này, C++ cung cấp một kiểu dữ liệu cực kỳ mạnh mẽ là Xâu ký tự (std::string). Bài viết này sẽ hướng dẫn chi tiết từ khái niệm cơ bản, cách nhập xuất tối ưu cho đến các hàm xử lý xâu nâng cao kèm ví dụ minh họa trực quan.
1. Khái niệm về xâu ký tự (std::string)
Xâu ký tự (chuỗi ký tự) là một dãy gồm các ký tự liên tiếp nhau. Trong ngôn ngữ C++ hiện đại, thay vì sử dụng mảng ký tự kiểu C cổ điển (char[]) rất dễ gây lỗi tràn bộ nhớ, chúng ta sử dụng lớp std::string nằm trong thư viện chuẩn để quản lý xâu ký tự một cách tự động, an toàn và linh hoạt.
2. Khai báo biến xâu ký tự
Cú pháp:
string tên_xâu; // Khai báo xâu rỗng
string tên_xâu = "giá_trị_khởi_tạo"; // Khai báo và gán giá trị ban đầu
Lưu ý: Hằng xâu ký tự trong C++ luôn được đặt trong cặp dấu nháy kép "" (ví dụ: "Sen Code"), khác với ký tự đơn lẻ đặt trong cặp dấu nháy đơn '' (ví dụ: 'A').
3. Nhập xuất xâu ký tự (CỰC KỲ QUAN TRỌNG)
Thao tác nhập xuất xâu ký tự trong C++ có một số bẫy rất dễ gây lỗi trôi lệnh cho học sinh mới học.
a. Nhập xâu không chứa khoảng trắng (Không chứa dấu cách)
Dùng lệnh cin >> thông thường. Hệ thống sẽ đọc dữ liệu từ luồng nhập và dừng lại khi gặp khoảng trắng, dấu tab hoặc dấu xuống dòng.
string s;
cin >> s; // Nhập "Sen Code" thì s chỉ nhận giá trị "Sen"
b. Nhập xâu chứa khoảng trắng (Đọc cả dòng văn bản)
Để đọc toàn bộ một dòng văn bản bao gồm cả dấu cách, ta sử dụng hàm getline:
string s;
getline(cin, s); // Nhập "Sen Code Online Judge!" thì s nhận đầy đủ cả dòng
c. Bẫy trôi lệnh khi dùng getline và cách xử lý
- Hiện tượng: Nếu trước khi gọi lệnh
getline(cin, s)chương trình có sử dụngcin >>để nhập một số nguyên (ví dụ nhập số lượng bộ test N), ký tự xuống dòng\nsau số nguyên đó vẫn nằm trong bộ đệm. Lệnhgetlinetiếp theo sẽ lập tức đọc ký tự\nnày và kết thúc ngay mà không cho phép người dùng nhập dữ liệu thực tế. - Cách xử lý: Sử dụng lệnh
cin.ignore()ngay sau câu lệnhcin >>để xóa ký tự xuống dòng khỏi bộ đệm trước khi gọigetline.
Ví dụ minh họa:
#include <bits/stdc++.h>
using namespace std;
int main() {
int N;
cin >> N;
cin.ignore(); // Xóa ký tự xuống dòng còn sót lại trong bộ đệm để tránh trôi lệnh
string s;
getline(cin, s); // Nhập cả dòng văn bản tiếp theo
cout << "So N: " << N << "\n";
cout << "Xau s: " << s << "\n";
return 0;
}
4. Các phép toán cơ bản trên xâu
C++ hỗ trợ các toán tử trực quan giúp xử lý xâu dễ dàng như các kiểu dữ liệu số:
Phép ghép xâu (
+hoặc+=): Nối hai xâu lại với nhau.string a = "Sen "; string b = "Code"; string c = a + b; // c nhận giá trị "Sen Code"Phép so sánh (
==,!=,<,>): So sánh hai xâu ký tự theo thứ tự từ điển (dựa vào bảng mã ASCII của từng ký tự từ trái qua phải).if (a == b) { /* hai xâu giống hệt nhau */ }Truy cập ký tự: Ta có thể truy cập hoặc sửa đổi từng ký tự của xâu thông qua chỉ số giống như mảng:
s[i]. Chỉ số bắt đầu từ0đếns.length() - 1.
5. Các hàm và phương thức xử lý xâu quan trọng
a. Lấy độ dài xâu: s.length() hoặc s.size()
Trả về số lượng ký tự hiện tại của xâu s.
string s = "ABC";
cout << s.length(); // In ra 3
b. Kiểm tra xâu rỗng: s.empty()
Trả về true nếu xâu không chứa ký tự nào, ngược lại trả về false.
c. Trích xuất xâu con: s.substr(pos, len)
Cắt ra một xâu con của s bắt đầu từ vị trí chỉ số pos với độ dài là len ký tự.
string s = "Sen Code";
string sub = s.substr(4, 4); // sub nhận giá trị "Code"
d. Tìm kiếm xâu con: s.find(str)
Tìm vị trí xuất hiện đầu tiên của xâu str trong s.
- Nếu tìm thấy, trả về chỉ số đầu tiên nơi
strxuất hiện. - Nếu không tìm thấy, trả về giá trị hằng số đặc biệt
string::npos.
string s = "Sen Code";
int pos = s.find("Code"); // pos nhận giá trị 4
if (s.find("Java") == string::npos) {
cout << "Khong tim thay\n";
}
e. Chèn, Xóa xâu:
s.insert(pos, str): Chèn xâustrvào vị tríposcủa xâus.s.erase(pos, len): Xóalenký tự của xâusbắt đầu từ vị trípos.
f. Chuyển đổi giữa Xâu và Số:
to_string(num): Chuyển đổi một số nguyên/thực sang xâu ký tự.stoi(s): Chuyển đổi xâussang số nguyên kiểuint. (Dùngstoll(s)cho sốlong longvàstod(s)cho số thựcdouble).
g. Các hàm xử lý ký tự đơn lẻ (Thư viện <cctype>):
Duyệt qua từng ký tự của xâu c = s[i], ta có thể sử dụng các hàm kiểm tra sau:
isdigit(c): Trả vềtruenếuclà chữ số ('0'-'9').isalpha(c): Trả vềtruenếuclà chữ cái ('a'-'z','A'-'Z').islower(c)/isupper(c): Kiểm tra chữ viết thường / viết hoa.tolower(c)/toupper(c): Trả về ký tựcđã được chuyển sang viết thường / viết hoa.
6. ĐỀ XUẤT 10 BÀI TẬP THỰC HÀNH THỰC TẾ (XÂU KÝ TỰ)
Hãy lập trình đọc/ghi dữ liệu bằng tệp thông qua freopen đối với 10 bài tập ứng dụng thực tế đa dạng lĩnh vực sau đây:
Bài 1: Chuẩn hóa họ tên thành viên
- Ngữ cảnh: Hệ thống đăng ký tài khoản của Sen Code Online Judge! nhận dữ liệu họ tên do người dùng tự nhập. Do người dùng nhập tự do, họ tên thường bị thừa khoảng trắng ở đầu, cuối, ở giữa các từ và viết hoa lộn xộn. Bạn cần chuẩn hóa họ tên về dạng chuẩn.
- Yêu cầu: Đọc chuỗi tên từ tệp
username.inp, loại bỏ các khoảng trắng thừa ở đầu, cuối và giữa các từ; viết hoa chữ cái đầu tiên của mỗi từ và viết thường các chữ cái còn lại. Ghi kết quả ra tệpusername.out. - Đầu vào (Input - tệp
username.inp): Một dòng duy nhất chứa chuỗi họ tên S (độ dài không quá 1000 ký tự). - Đầu ra (Output - tệp
username.out): Chuỗi họ tên sau khi đã được chuẩn hóa. - Ví dụ:
- Input:
nGUYEn vAn A - Output:
Nguyen Van A
- Input:
Bài 2: Kiểm tra mật khẩu mạnh bảo mật hệ thống
- Ngữ cảnh: Để đăng ký tài khoản, hệ thống an ninh mạng yêu cầu người dùng thiết lập mật khẩu mạnh thỏa mãn các điều kiện:
- Độ dài tối thiểu của mật khẩu là 8 ký tự.
- Phải chứa ít nhất 1 chữ cái viết hoa (
A-Z). - Phải chứa ít nhất 1 chữ cái viết thường (
a-z). - Phải chứa ít nhất 1 chữ số (
0-9).
- Yêu cầu: Đọc mật khẩu từ tệp
password.inpvà ghi kết quả kiểm tra ra tệppassword.out. - Đầu vào (Input - tệp
password.inp): Một chuỗi ký tự S không chứa khoảng trắng (độ dài không quá 100 ký tự). - Đầu ra (Output - tệp
password.out): In raSTRONGnếu mật khẩu đạt yêu cầu, ngược lại in raWEAK. - Ví dụ:
- Input:
Sencode2026 - Output:
STRONG
- Input:
Bài 3: Kiểm duyệt từ khóa nhạy cảm
- Ngữ cảnh: Kiểm duyệt viên cần quét một đoạn văn bản bài viết dài S để tìm kiếm xem có chứa từ khóa nhạy cảm T hay không nhằm ngăn chặn các nội dung không phù hợp.
- Yêu cầu: Đọc văn bản S và từ khóa T từ tệp
censor.inp. Ghi vị trí xuất hiện đầu tiên của từ khóa ra tệpcensor.out. - Đầu vào (Input - tệp
censor.inp):- Dòng 1 chứa chuỗi từ khóa nhạy cảm T.
- Dòng 2 chứa chuỗi văn bản S (độ dài không quá 104 ký tự).
- Đầu ra (Output - tệp
censor.out): Một số nguyên duy nhất là chỉ số (index bắt đầu từ 0) của vị trí xuất hiện đầu tiên của T trong S. Nếu không tìm thấy, in ra-1. Ví dụ:
Input:
cheat hoc sinh khong duoc cheat bai thi tren he thongOutput:
23
Bài 4: Mã hóa tin nhắn quân sự (Mật mã Caesar)
- Ngữ cảnh: Trong liên lạc quân sự cổ đại, thông tin được mã hóa bằng mật mã Caesar: dịch chuyển mỗi chữ cái trong tin nhắn tiến lên K vị trí trong bảng chữ cái tiếng Anh theo vòng tròn từ A đến Z (và từ a đến z). Các ký tự không phải chữ cái (dấu cách, chữ số, dấu câu) được giữ nguyên.
- Yêu cầu: Đọc tin nhắn S và bước dịch chuyển K từ tệp
caesar.inp, ghi tin nhắn đã mã hóa ra tệpcaesar.out. - Đầu vào (Input - tệp
caesar.inp):- Dòng 1 chứa số nguyên dương K (1 <= K <= 25).
- Dòng 2 chứa chuỗi tin nhắn S cần mã hóa (chứa khoảng trắng, độ dài không quá 1000 ký tự).
- Đầu ra (Output - tệp
caesar.out): Chuỗi tin nhắn sau khi mã hóa. Ví dụ:
Input:
3 Hello World!Output:
Khoor Zruog!
Bài 5: Xác thực mã số thẻ định danh
- Ngữ cảnh: Một chuỗi số đại diện cho mã số định danh của sản phẩm. Mã số được coi là hợp lệ nếu tổng giá trị số học của tất cả các chữ số xuất hiện trong chuỗi là một số chia hết cho 10. Các ký tự không phải số trong chuỗi sẽ bị bỏ qua.
- Yêu cầu: Đọc chuỗi mã số từ tệp
card.inpvà ghi kết quả xác thực ra tệpcard.out. - Đầu vào (Input - tệp
card.inp): Một chuỗi ký tự S có thể chứa cả chữ cái và chữ số (độ dài không quá 1000 ký tự). - Đầu ra (Output - tệp
card.out): In raVALIDnếu hợp lệ, ngược lại in raINVALID. - Ví dụ:
- Input:
ID-123400LL-901 - Output:
VALID(Các chữ số là 1, 2, 3, 4, 0, 0, 9, 0, 1 -> Tổng = 20 chia hết cho 10)
- Input:
Bài 6: Tìm từ đối xứng (Palindrome)
- Ngữ cảnh: Một nhà ngôn ngữ học nghiên cứu cấu trúc của các từ đối xứng (Palindrome) - từ đọc xuôi hay ngược đều giống nhau.
- Yêu cầu: Đọc một từ từ tệp
palindrome.inpvà ghi kết quả kiểm tra ra tệppalindrome.out. - Đầu vào (Input - tệp
palindrome.inp): Một chuỗi S viết thường không chứa khoảng trắng (độ dài không quá 1000 ký tự). - Đầu ra (Output - tệp
palindrome.out): In raYESnếu S là xâu đối xứng, ngược lại in raNO. - Ví dụ:
- Input:
racecar - Output:
YES
- Input:
Bài 7: Nén dữ liệu truyền tin (Run-Length Encoding)
- Ngữ cảnh: Để tiết kiệm băng thông truyền tin qua vệ tinh, người ta nén chuỗi ký tự bằng thuật toán RLE: đếm số lần lặp liên tiếp của từng ký tự và viết dưới dạng ký tự kèm số lần lặp.
- Yêu cầu: Nén chuỗi ký tự từ tệp
compress.inpghi ra tệpcompress.out. - Đầu vào (Input - tệp
compress.inp): Một chuỗi S chỉ gồm các chữ cái viết hoa viết liền nhau (độ dài không quá 1000 ký tự). - Đầu ra (Output - tệp
compress.out): Chuỗi sau khi được nén. - Ví dụ:
- Input:
AAABBCDDDD - Output:
A3B2C1D4
- Input:
Bài 8: Đếm số lượng từ trong bài viết
- Ngữ cảnh: Phần mềm soạn thảo văn bản cần thống kê số lượng từ của bài viết để tính nhuận bút cho biên tập viên. Các từ trong văn bản được ngăn cách bởi một hoặc nhiều khoảng trắng (dấu cách).
- Yêu cầu: Đọc văn bản S từ tệp
wordcount.inpvà ghi số lượng từ ra tệpwordcount.out. - Đầu vào (Input - tệp
wordcount.inp): Một dòng văn bản S (độ dài không quá 104 ký tự). - Đầu ra (Output - tệp
wordcount.out): Một số nguyên duy nhất là số lượng từ của văn bản. - Ví dụ:
- Input:
Hoc lap trinh C++ cung Sen Code - Output:
7
- Input:
Bài 9: Trích xuất tên miền Email
- Ngữ cảnh: Hệ thống phân tích khách hàng cần lọc ra nhà cung cấp dịch vụ email (tên miền đứng sau ký tự
@) từ địa chỉ email đầy đủ của người đăng ký. - Yêu cầu: Đọc địa chỉ email từ tệp
email.inpvà ghi tên miền trích xuất được ra tệpemail.out. - Đầu vào (Input - tệp
email.inp): Chuỗi email định dạngusername@domain(độ dài không quá 200 ký tự). - Đầu ra (Output - tệp
email.out): Chuỗi tên miền thu được. - Ví dụ:
- Input:
sencode.io.vn@gmail.com - Output:
gmail.com
- Input:
Bài 10: Cộng hai số nguyên siêu lớn (Big Integer)
- Ngữ cảnh: Trong hệ thống bảo mật RSA, ta cần cộng hai số nguyên siêu lớn có hàng ngàn chữ số. Các số này vượt quá giới hạn lưu trữ của kiểu dữ liệu lớn nhất trong C++ (
long long). Hãy mô phỏng lại phép toán cộng viết tay đặt tính rồi tính bằng cách xử lý xâu ký tự. - Yêu cầu: Đọc hai số lớn A và B từ tệp
bigadd.inpvà ghi tổng của chúng ra tệpbigadd.out. - Đầu vào (Input - tệp
bigadd.inp):- Dòng 1 chứa số nguyên lớn A.
- Dòng 2 chứa số nguyên lớn B.
- (Độ dài số A và B không quá 1000 chữ số, chỉ chứa các chữ số từ
0đến9).
- Đầu ra (Output - tệp
bigadd.out): Chuỗi chữ số biểu thị tổng của hai số. Ví dụ:
Input:
99999999999999999999 1Output:
100000000000000000000
Lời kết
Xâu ký tự std::string là một công cụ cực kỳ linh hoạt và xuất hiện liên tục trong lập trình thi đấu. Hãy ghi nhớ bẫy trôi lệnh khi sử dụng getline kết hợp cin >> và luyện tập thuần thục các hàm cắt ghép xâu nhé!
Chúc các bạn rèn luyện thật tốt và đạt thật nhiều điểm xanh lá trên Sen Code Online Judge!! Chúc các bạn thành công!
Bình luận