Bài 10: Chuỗi ký tự (Strings)
📖 Giới thiệu
Chuỗi ký tự (string) là một trong những kiểu dữ liệu quan trọng nhất trong lập trình, cho phép chúng ta lưu trữ và xử lý văn bản. Trong C++, có hai cách chính để làm việc với chuỗi:
- C-style strings: Mảng ký tự kết thúc bằng
\0 - C++ string class: Lớp
std::stringvới nhiều phương thức tiện ích
Chuỗi được sử dụng rộng rãi trong:
- Xử lý thông tin người dùng: Tên, địa chỉ, email
- Ứng dụng web: URL, HTML, JSON
- Xử lý file: Đọc/ghi dữ liệu text
- Game development: Tên nhân vật, dialog, menu
- Hệ thống thông tin: Tìm kiếm, lọc, sắp xếp dữ liệu
🔧 Cú pháp
C-style strings
cpp
// Khai báo và khởi tạo
char str1[50]; // Chuỗi rỗng, tối đa 49 ký tự
char str2[20] = "Hello World"; // Khởi tạo với giá trị
char str3[] = "C++ Programming"; // Kích thước tự động
char str4[10] = {'H', 'i', '\0'}; // Khởi tạo từng ký tự
// Nhập xuất
cin >> str1; // Nhập (dừng tại space)
cin.getline(str1, 50); // Nhập cả dòng
cout << str1; // Xuất chuỗiC++ string class
cpp
#include <string>
// Khai báo và khởi tạo
string str1; // Chuỗi rỗng
string str2 = "Hello"; // Khởi tạo với giá trị
string str3("World"); // Constructor
string str4(str2); // Copy constructor
string str5(5, 'A'); // "AAAAA"
// Nhập xuất
cin >> str1; // Nhập (dừng tại space)
getline(cin, str1); // Nhập cả dòng
cout << str1; // Xuất chuỗiCác phép toán cơ bản
cpp
// Nối chuỗi
string result = str1 + str2;
str1 += str2; // Nối vào str1
// So sánh
if (str1 == str2) { ... }
if (str1 < str2) { ... } // So sánh theo thứ tự từ điển
// Truy cập ký tự
char ch = str1[0]; // Ký tự đầu tiên
str1[0] = 'H'; // Thay đổi ký tự
char last = str1.back(); // Ký tự cuối cùng
// Kích thước
int len = str1.length();
int size = str1.size(); // Tương đương length()
bool empty = str1.empty(); // Kiểm tra chuỗi rỗng🔬 Phân tích & Giải thích chi tiết
So sánh C-style vs C++ string
| Đặc điểm | C-style | C++ string |
|---|---|---|
| Khai báo | char str[100] | string str |
| Kích thước | Cố định | Động |
| An toàn | Dễ buffer overflow | An toàn hơn |
| Phép toán | Phức tạp (strcpy, strcat) | Đơn giản (+, ==) |
| Hiệu suất | Nhanh hơn | Chậm hơn một chút |
Các phương thức string class quan trọng
cpp
string str = "Hello World";
// Độ dài và kích thước
str.length() // 11
str.size() // 11
str.capacity() // Dung lượng hiện tại
// Truy cập
str[0] // 'H'
str.at(1) // 'e' (có kiểm tra bounds)
str.front() // 'H'
str.back() // 'd'
// Tìm kiếm
str.find("World") // 6 (vị trí tìm thấy)
str.find('o') // 4 (ký tự 'o' đầu tiên)
str.rfind('o') // 7 (ký tự 'o' cuối cùng)
// Cắt chuỗi
str.substr(0, 5) // "Hello"
str.substr(6) // "World"
// Thay đổi
str.insert(5, " C++") // "Hello C++ World"
str.erase(5, 4) // "Hello World"
str.replace(6, 5, "C++") // "Hello C++"
// Biến đổi
str.clear() // Xóa toàn bộ
str.empty() // true nếu rỗng
str.push_back('!') // Thêm ký tự cuối
str.pop_back() // Xóa ký tự cuốiKhi nào sử dụng loại nào
Sử dụng C-style strings khi:
- Làm việc với C libraries
- Cần hiệu suất tối đa
- Memory-constrained environments
Sử dụng C++ strings khi:
- Phát triển ứng dụng thông thường
- Cần thao tác chuỗi phức tạp
- An toàn và dễ bảo trì là ưu tiên
💻 Ví dụ minh họa
Ví dụ 1: Hệ thống đăng ký tài khoản
cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
bool isValidEmail(const string& email) {
size_t atPos = email.find('@');
size_t dotPos = email.find('.', atPos);
return (atPos != string::npos &&
dotPos != string::npos &&
atPos > 0 &&
dotPos > atPos + 1 &&
dotPos < email.length() - 1);
}
bool isStrongPassword(const string& password) {
if (password.length() < 8) return false;
bool hasUpper = false, hasLower = false, hasDigit = false;
for (char c : password) {
if (isupper(c)) hasUpper = true;
if (islower(c)) hasLower = true;
if (isdigit(c)) hasDigit = true;
}
return hasUpper && hasLower && hasDigit;
}
string maskPassword(const string& password) {
return string(password.length(), '*');
}
int main() {
string username, email, password, confirmPassword;
cout << "=== DANG KY TAI KHOAN ===" << endl;
// Nhập username
do {
cout << "Ten dang nhap: ";
getline(cin, username);
if (username.length() < 3) {
cout << "Ten dang nhap phai co it nhat 3 ky tu!" << endl;
}
else if (username.find(' ') != string::npos) {
cout << "Ten dang nhap khong duoc chua khoang trang!" << endl;
}
} while (username.length() < 3 || username.find(' ') != string::npos);
// Nhập email
do {
cout << "Email: ";
getline(cin, email);
if (!isValidEmail(email)) {
cout << "Email khong hop le! Vui long nhap lai." << endl;
}
} while (!isValidEmail(email));
// Nhập mật khẩu
do {
cout << "Mat khau: ";
getline(cin, password);
if (!isStrongPassword(password)) {
cout << "Mat khau phai co it nhat 8 ky tu, bao gom:" << endl;
cout << "- Chu hoa, chu thuong va so" << endl;
}
} while (!isStrongPassword(password));
// Xác nhận mật khẩu
do {
cout << "Xac nhan mat khau: ";
getline(cin, confirmPassword);
if (password != confirmPassword) {
cout << "Mat khau xac nhan khong khop! Vui long nhap lai." << endl;
}
} while (password != confirmPassword);
// Hiển thị thông tin đăng ký
cout << "\n=== THONG TIN DANG KY ===" << endl;
cout << "Ten dang nhap: " << username << endl;
cout << "Email: " << email << endl;
cout << "Mat khau: " << maskPassword(password) << endl;
cout << "\nDang ky thanh cong!" << endl;
return 0;
}Kết quả mẫu:
=== DANG KY TAI KHOAN ===
Ten dang nhap: user123
Email: user@example.com
Mat khau: MyPassword123
Xac nhan mat khau: MyPassword123
=== THONG TIN DANG KY ===
Ten dang nhap: user123
Email: user@example.com
Mat khau: *************
Dang ky thanh cong!Ví dụ 2: Xử lý văn bản - Đếm từ và phân tích
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
#include <cctype>
using namespace std;
// Chuyển đổi thành chữ thường
string toLowerCase(string str) {
transform(str.begin(), str.end(), str.begin(), ::tolower);
return str;
}
// Loại bỏ dấu câu
string removePunctuation(const string& word) {
string result;
for (char c : word) {
if (isalnum(c)) {
result += c;
}
}
return result;
}
// Tách câu thành từ
vector<string> splitIntoWords(const string& text) {
vector<string> words;
stringstream ss(text);
string word;
while (ss >> word) {
word = removePunctuation(word);
if (!word.empty()) {
words.push_back(toLowerCase(word));
}
}
return words;
}
// Đếm tần suất từ
void countWordFrequency(const vector<string>& words) {
vector<pair<string, int>> frequency;
for (const string& word : words) {
bool found = false;
for (auto& pair : frequency) {
if (pair.first == word) {
pair.second++;
found = true;
break;
}
}
if (!found) {
frequency.push_back({word, 1});
}
}
// Sắp xếp theo tần suất giảm dần
sort(frequency.begin(), frequency.end(),
[](const pair<string, int>& a, const pair<string, int>& b) {
return a.second > b.second;
});
cout << "\n=== TAN SUAT TU ===" << endl;
for (const auto& pair : frequency) {
cout << pair.first << ": " << pair.second << " lan" << endl;
}
}
// Tìm từ dài nhất
string findLongestWord(const vector<string>& words) {
string longest = "";
for (const string& word : words) {
if (word.length() > longest.length()) {
longest = word;
}
}
return longest;
}
int main() {
string text;
cout << "=== PHAN TICH VAN BAN ===" << endl;
cout << "Nhap doan van ban: ";
getline(cin, text);
if (text.empty()) {
cout << "Khong co du lieu dau vao!" << endl;
return 1;
}
// Phân tích cơ bản
cout << "\n=== THONG KE CO BAN ===" << endl;
cout << "So ky tu: " << text.length() << endl;
cout << "So ky tu (khong khoang trang): "
<< count_if(text.begin(), text.end(), [](char c) { return c != ' '; }) << endl;
// Đếm khoảng trắng và dòng
int spaces = count(text.begin(), text.end(), ' ');
int sentences = count(text.begin(), text.end(), '.') +
count(text.begin(), text.end(), '!') +
count(text.begin(), text.end(), '?');
cout << "So khoang trang: " << spaces << endl;
cout << "So cau (uoc tinh): " << sentences << endl;
// Phân tích từ
vector<string> words = splitIntoWords(text);
cout << "So tu: " << words.size() << endl;
if (!words.empty()) {
cout << "Tu dai nhat: " << findLongestWord(words) << endl;
cout << "Do dai trung binh cua tu: "
<< fixed << setprecision(1)
<< (double)text.length() / words.size() << " ky tu" << endl;
countWordFrequency(words);
}
return 0;
}Ví dụ 3: Trò chơi đoán từ (Hangman)
cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <cctype>
using namespace std;
class HangmanGame {
private:
vector<string> words = {
"VIETNAM", "HANOI", "SAIGON", "PROGRAMMING",
"COMPUTER", "STUDENT", "TEACHER", "SCHOOL",
"LANGUAGE", "ENGLISH", "MATHEMATICS", "SCIENCE"
};
string secretWord;
string guessedWord;
vector<char> wrongGuesses;
int maxWrongGuesses;
public:
HangmanGame() : maxWrongGuesses(6) {
srand(time(0));
selectRandomWord();
}
void selectRandomWord() {
secretWord = words[rand() % words.size()];
guessedWord = string(secretWord.length(), '_');
}
void displayGame() {
cout << "\n=== TRANG THAI GAME ===" << endl;
cout << "Tu can doan: ";
for (int i = 0; i < guessedWord.length(); i++) {
cout << guessedWord[i] << " ";
}
cout << endl;
cout << "Sai: " << wrongGuesses.size() << "/" << maxWrongGuesses << endl;
if (!wrongGuesses.empty()) {
cout << "Chu cai da doan sai: ";
for (char c : wrongGuesses) {
cout << c << " ";
}
cout << endl;
}
drawHangman();
}
void drawHangman() {
cout << "\n";
switch (wrongGuesses.size()) {
case 0:
cout << " +---+" << endl;
cout << " | |" << endl;
cout << " |" << endl;
cout << " |" << endl;
cout << " |" << endl;
cout << " |" << endl;
cout << "=========" << endl;
break;
case 1:
cout << " +---+" << endl;
cout << " | |" << endl;
cout << " O |" << endl;
cout << " |" << endl;
cout << " |" << endl;
cout << " |" << endl;
cout << "=========" << endl;
break;
case 2:
cout << " +---+" << endl;
cout << " | |" << endl;
cout << " O |" << endl;
cout << " | |" << endl;
cout << " |" << endl;
cout << " |" << endl;
cout << "=========" << endl;
break;
case 3:
cout << " +---+" << endl;
cout << " | |" << endl;
cout << " O |" << endl;
cout << " /| |" << endl;
cout << " |" << endl;
cout << " |" << endl;
cout << "=========" << endl;
break;
case 4:
cout << " +---+" << endl;
cout << " | |" << endl;
cout << " O |" << endl;
cout << " /|\\ |" << endl;
cout << " |" << endl;
cout << " |" << endl;
cout << "=========" << endl;
break;
case 5:
cout << " +---+" << endl;
cout << " | |" << endl;
cout << " O |" << endl;
cout << " /|\\ |" << endl;
cout << " / |" << endl;
cout << " |" << endl;
cout << "=========" << endl;
break;
case 6:
cout << " +---+" << endl;
cout << " | |" << endl;
cout << " O |" << endl;
cout << " /|\\ |" << endl;
cout << " / \\ |" << endl;
cout << " |" << endl;
cout << "=========" << endl;
break;
}
}
bool makeGuess(char letter) {
letter = toupper(letter);
// Kiểm tra đã đoán chưa
if (guessedWord.find(letter) != string::npos ||
find(wrongGuesses.begin(), wrongGuesses.end(), letter) != wrongGuesses.end()) {
cout << "Ban da doan chu cai nay roi!" << endl;
return false;
}
// Kiểm tra có trong từ không
bool found = false;
for (int i = 0; i < secretWord.length(); i++) {
if (secretWord[i] == letter) {
guessedWord[i] = letter;
found = true;
}
}
if (!found) {
wrongGuesses.push_back(letter);
cout << "Sai! Chu cai '" << letter << "' khong co trong tu." << endl;
} else {
cout << "Dung! Chu cai '" << letter << "' co trong tu." << endl;
}
return true;
}
bool isGameWon() {
return guessedWord == secretWord;
}
bool isGameLost() {
return wrongGuesses.size() >= maxWrongGuesses;
}
string getSecretWord() {
return secretWord;
}
};
int main() {
HangmanGame game;
char guess;
cout << "=== TRO CHOI DOAN TU ===" << endl;
cout << "Quy tac: Doan tung chu cai de tim ra tu bi an!" << endl;
cout << "Ban co toi da 6 lan doan sai." << endl;
while (!game.isGameWon() && !game.isGameLost()) {
game.displayGame();
cout << "\nNhap chu cai ban muon doan: ";
cin >> guess;
if (!isalpha(guess)) {
cout << "Vui long chi nhap chu cai!" << endl;
continue;
}
game.makeGuess(guess);
}
game.displayGame();
if (game.isGameWon()) {
cout << "\n🎉 Chuc mung! Ban da doan dung tu: " << game.getSecretWord() << endl;
} else {
cout << "\n💀 Game Over! Tu can tim la: " << game.getSecretWord() << endl;
}
return 0;
}🏋️ Thực hành
Bài tập 1: Đảo ngược chuỗi
cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
string reverseString(const string& str) {
string reversed = str;
reverse(reversed.begin(), reversed.end());
return reversed;
}
string reverseWords(const string& str) {
string result = str;
size_t start = 0;
while (start < result.length()) {
size_t end = result.find(' ', start);
if (end == string::npos) end = result.length();
reverse(result.begin() + start, result.begin() + end);
start = end + 1;
}
return result;
}
int main() {
string text;
cout << "Nhap chuoi: ";
getline(cin, text);
cout << "Chuoi goc: " << text << endl;
cout << "Dao nguoc toan bo: " << reverseString(text) << endl;
cout << "Dao nguoc tung tu: " << reverseWords(text) << endl;
return 0;
}Bài tập 2: Kiểm tra Palindrome
cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
string cleanString(const string& str) {
string clean;
for (char c : str) {
if (isalnum(c)) {
clean += tolower(c);
}
}
return clean;
}
bool isPalindrome(const string& str) {
string clean = cleanString(str);
string reversed = clean;
reverse(reversed.begin(), reversed.end());
return clean == reversed;
}
int main() {
string text;
cout << "Nhap chuoi de kiem tra Palindrome: ";
getline(cin, text);
if (isPalindrome(text)) {
cout << "\"" << text << "\" la Palindrome!" << endl;
} else {
cout << "\"" << text << "\" khong phai Palindrome." << endl;
}
return 0;
}Bài tập 3: Chuyển đổi kiểu chữ
cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
string toUpperCase(const string& str) {
string result = str;
for (char& c : result) {
c = toupper(c);
}
return result;
}
string toLowerCase(const string& str) {
string result = str;
for (char& c : result) {
c = tolower(c);
}
return result;
}
string toTitleCase(const string& str) {
string result = str;
bool capitalizeNext = true;
for (char& c : result) {
if (isalpha(c)) {
if (capitalizeNext) {
c = toupper(c);
capitalizeNext = false;
} else {
c = tolower(c);
}
} else {
capitalizeNext = true;
}
}
return result;
}
string toggleCase(const string& str) {
string result = str;
for (char& c : result) {
if (islower(c)) {
c = toupper(c);
} else if (isupper(c)) {
c = tolower(c);
}
}
return result;
}
int main() {
string text;
cout << "Nhap chuoi: ";
getline(cin, text);
cout << "\nCac kieu chuyen doi:" << endl;
cout << "1. Chu hoa: " << toUpperCase(text) << endl;
cout << "2. Chu thuong: " << toLowerCase(text) << endl;
cout << "3. Chu hoa dau tu: " << toTitleCase(text) << endl;
cout << "4. Dao chu: " << toggleCase(text) << endl;
return 0;
}Bài tập 4: Tìm và thay thế
cpp
#include <iostream>
#include <string>
using namespace std;
string replaceAll(string str, const string& from, const string& to) {
size_t pos = 0;
while ((pos = str.find(from, pos)) != string::npos) {
str.replace(pos, from.length(), to);
pos += to.length();
}
return str;
}
int countOccurrences(const string& text, const string& word) {
int count = 0;
size_t pos = 0;
while ((pos = text.find(word, pos)) != string::npos) {
count++;
pos += word.length();
}
return count;
}
void findAllPositions(const string& text, const string& word) {
size_t pos = 0;
bool found = false;
cout << "Vi tri tim thay \"" << word << "\": ";
while ((pos = text.find(word, pos)) != string::npos) {
cout << pos << " ";
found = true;
pos += word.length();
}
if (!found) {
cout << "Khong tim thay";
}
cout << endl;
}
int main() {
string text, searchWord, replaceWord;
cout << "Nhap doan van: ";
getline(cin, text);
cout << "Nhap tu can tim: ";
getline(cin, searchWord);
// Tìm kiếm
int count = countOccurrences(text, searchWord);
cout << "\nKet qua tim kiem:" << endl;
cout << "So lan xuat hien: " << count << endl;
if (count > 0) {
findAllPositions(text, searchWord);
// Thay thế
cout << "\nNhap tu thay the: ";
getline(cin, replaceWord);
string newText = replaceAll(text, searchWord, replaceWord);
cout << "\nVan ban sau khi thay the:" << endl;
cout << newText << endl;
}
return 0;
}Bài tập 5: Xử lý CSV đơn giản
cpp
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
using namespace std;
vector<string> splitCSV(const string& line) {
vector<string> result;
stringstream ss(line);
string cell;
while (getline(ss, cell, ',')) {
// Loại bỏ khoảng trắng đầu/cuối
size_t start = cell.find_first_not_of(" \t");
if (start == string::npos) {
result.push_back("");
} else {
size_t end = cell.find_last_not_of(" \t");
result.push_back(cell.substr(start, end - start + 1));
}
}
return result;
}
void printTable(const vector<vector<string>>& data) {
if (data.empty()) return;
// Tính độ rộng cột
vector<int> colWidths(data[0].size(), 0);
for (const auto& row : data) {
for (int i = 0; i < row.size(); i++) {
colWidths[i] = max(colWidths[i], (int)row[i].length());
}
}
// In bảng
for (int i = 0; i < data.size(); i++) {
cout << "|";
for (int j = 0; j < data[i].size(); j++) {
cout << " " << setw(colWidths[j]) << left << data[i][j] << " |";
}
cout << endl;
// In dòng phân cách sau header
if (i == 0) {
cout << "|";
for (int j = 0; j < data[i].size(); j++) {
cout << string(colWidths[j] + 2, '-') << "|";
}
cout << endl;
}
}
}
int main() {
vector<vector<string>> csvData;
string line;
cout << "=== XU LY DU LIEU CSV ===" << endl;
cout << "Nhap du lieu CSV (Enter trang de ket thuc):" << endl;
cout << "Vi du: Ten,Tuoi,Lop,Diem" << endl;
while (true) {
getline(cin, line);
if (line.empty()) break;
vector<string> row = splitCSV(line);
csvData.push_back(row);
}
if (csvData.empty()) {
cout << "Khong co du lieu!" << endl;
return 1;
}
cout << "\n=== DU LIEU DUOI DANG BANG ===" << endl;
printTable(csvData);
// Thống kê
cout << "\n=== THONG KE ===" << endl;
cout << "So dong: " << csvData.size() << endl;
cout << "So cot: " << (csvData.empty() ? 0 : csvData[0].size()) << endl;
// Tìm kiếm
if (csvData.size() > 1) {
string searchTerm;
cout << "\nNhap tu khoa tim kiem: ";
getline(cin, searchTerm);
cout << "\nKet qua tim kiem cho \"" << searchTerm << "\":" << endl;
bool found = false;
for (int i = 1; i < csvData.size(); i++) { // Bỏ qua header
for (const string& cell : csvData[i]) {
if (cell.find(searchTerm) != string::npos) {
cout << "Dong " << i << ": ";
for (int j = 0; j < csvData[i].size(); j++) {
cout << csvData[i][j];
if (j < csvData[i].size() - 1) cout << ", ";
}
cout << endl;
found = true;
break;
}
}
}
if (!found) {
cout << "Khong tim thay ket qua nao!" << endl;
}
}
return 0;
}📋 Tóm tắt
Các điểm quan trọng
Hai loại chuỗi:
- C-style:
char str[100]- Nhanh nhưng phức tạp - C++ string:
string str- Dễ sử dụng, an toàn hơn
- C-style:
Thao tác cơ bản:
- Nối:
str1 + str2hoặcstr1 += str2 - So sánh:
str1 == str2,str1 < str2 - Truy cập:
str[i],str.at(i) - Tìm kiếm:
find(),rfind()
- Nối:
Phương thức quan trọng:
length(),size(): Độ dàisubstr(): Cắt chuỗi conreplace(): Thay thếinsert(),erase(): Thêm/xóa
Best Practices
- Sử dụng string class: Trừ khi có lý do đặc biệt
- Validate input: Kiểm tra dữ liệu đầu vào
- Sử dụng const reference:
const string&cho parameters - Xử lý encoding: Chú ý Unicode/UTF-8 cho tiếng Việt
- Memory safety: String class tự động quản lý bộ nhớ
Chuẩn bị cho bài tiếp theo
Bài tiếp theo chúng ta sẽ mở rộng phần Functions với các chủ đề:
- Parameters và Return Values: Truyền tham số và trả về giá trị
- Function Overloading: Nạp chồng hàm
- Recursion: Đệ quy và ứng dụng
- Scope và Lifetime: Phạm vi và vòng đời biến
Chuỗi ký tự là nền tảng quan trọng cho xử lý dữ liệu text trong các ứng dụng thực tế. Hãy luyện tập nhiều để thành thạo các thao tác chuỗi!