Bài 28.2: Các phương thức xử lý chuỗi ký tự trong C++

Trong lập trình C++, việc làm việc với văn bản, hay còn gọi là chuỗi ký tự, là một kỹ năng cốt lõi. Lớp string trong Thư viện Chuẩn C++ (STL) cung cấp một bộ công cụ mạnh mẽ để tạo, chỉnh sửa, tìm kiếm và thao tác với chuỗi một cách hiệu quả và an toàn hơn nhiều so với mảng ký tự kiểu C truyền thống.

Bài viết này sẽ giới thiệu và đi sâu vào các phương thức (methods) chính mà string mang lại, giúp bạn khai thác tối đa khả năng xử lý chuỗi trong các dự án C++ của mình. Hãy cùng khám phá sức mạnh thực sự của string!

Để sử dụng string, bạn cần include header <string>.

#include <iostream>
#include <string>

int main() {
    string s = "Chào mừng đến với thế giới chuỗi trong C++!";
    cout << s << endl;
    return 0;
}

Output:

Chào mừng đến với thế giới chuỗi trong C++!

Giờ thì, hãy đi vào chi tiết các phương thức hữu ích nhé!

1. Truy cập và Lấy Thông tin Cơ bản về Chuỗi

Chúng ta thường cần biết chuỗi dài bao nhiêu hoặc truy cập vào một ký tự cụ thể.

Phương thức length()size()

Hai phương thức này đều trả về số lượng ký tự trong chuỗi. Chúng thường có cùng chức năng trong hầu hết các trường hợp sử dụng string.

#include <iostream>
#include <string>

int main() {
    string s = "Xin chào!";
    cout << "Chuỗi: " << s << endl;
    cout << "Độ dài: " << s.length() << endl;
    cout << "Kích thước: " << s.size() << endl;
    return 0;
}

Output:

Chuỗi: Xin chào!
Độ dài: 9
Kích thước: 9

Giải thích: Code trên khai báo một chuỗi greeting và sử dụng length()size() để in ra số ký tự của chuỗi đó. Lưu ý rằng "Xin chào!" có 9 ký tự (bao gồm cả dấu chấm than).

Truy cập ký tự bằng toán tử [] và phương thức at()

Bạn có thể truy cập từng ký tự riêng lẻ trong chuỗi bằng cách sử dụng chỉ số (index). Chỉ số bắt đầu từ 0.

  • Toán tử []: Cung cấp cách truy cập nhanh nhưng không kiểm tra biên (out-of-bounds checking). Nếu bạn truy cập vào một chỉ số không hợp lệ, chương trình có thể gặp lỗi hoặc có hành vi không xác định.
  • Phương thức at(): Tương tự như [] nhưng có kiểm tra biên. Nếu chỉ số không hợp lệ, nó sẽ ném ra một ngoại lệ (out_of_range), giúp bạn phát hiện lỗi dễ dàng hơn.
#include <iostream>
#include <string>
#include <stdexcept>

int main() {
    using namespace std;
    string s = "Hello";
    cout << "Chuỗi: " << s << endl;
    cout << "Ký tự đầu tiên (dùng []): " << s[0] << endl;
    cout << "Ký tự thứ ba (dùng at()): " << s.at(2) << endl;

    try {
        cout << "Thử truy cập index 10 dùng at(): ";
        cout << s.at(10) << endl;
    } catch (const out_of_range& oor) {
        cerr << "Bắt được lỗi: Truy cập ngoài biên: " << oor.what() << endl;
    }
    return 0;
}

Output:

Chuỗi: Hello
Ký tự đầu tiên (dùng []): H
Ký tự thứ ba (dùng at()): l
Thử truy cập index 10 dùng at(): Bắt được lỗi: Truy cập ngoài biên: basic_string::at: __n (which is 10) >= this->size() (which is 5)

Giải thích: Ví dụ cho thấy cách truy cập ký tự đầu tiên (H ở index 0) và ký tự thứ ba (l ở index 2). Phần try-catch minh họa cách at() xử lý truy cập index không hợp lệ bằng cách ném ngoại lệ out_of_range.

Phương thức empty()

Kiểm tra xem chuỗi có rỗng hay không (không chứa ký tự nào). Trả về true nếu chuỗi rỗng, false nếu ngược lại.

#include <iostream>
#include <string>

int main() {
    string s1 = "";
    string s2 = "Không rỗng";

    if (s1.empty()) {
        cout << "s1 là chuỗi rỗng." << endl;
    }

    if (!s2.empty()) {
        cout << "s2 không phải là chuỗi rỗng." << endl;
    }
    return 0;
}

Output:

s1 là chuỗi rỗng.
s2 không phải là chuỗi rỗng.

Giải thích: s1 được khởi tạo là chuỗi rỗng, nên s1.empty() trả về true. s2 có nội dung, nên s2.empty() trả về false.

2. Thay đổi Nội dung Chuỗi

Một trong những sức mạnh của string là khả năng dễ dàng thay đổi nội dung của nó.

Nối chuỗi (Concatenation)

Có nhiều cách để nối các chuỗi lại với nhau.

  • Toán tử ++=: Cách phổ biến và trực quan nhất.
  • Phương thức append(): Linh hoạt hơn, cho phép nối toàn bộ hoặc một phần của chuỗi khác, hoặc lặp lại một ký tự nhiều lần.
#include <iostream>
#include <string>

int main() {
    using namespace std;
    string s1 = "Xin ";
    string s2 = "chào";
    string s3 = " các bạn!";

    string kq1 = s1 + s2 + s3;
    cout << "Kết hợp dùng +: " << kq1 << endl;

    string kq2 = s1;
    kq2 += s2;
    kq2 += s3;
    cout << "Kết hợp dùng +=: " << kq2 << endl;

    string kq3 = "Hello";
    kq3.append(" World");
    kq3.append("!");
    cout << "Kết hợp dùng append(): " << kq3 << endl;

    return 0;
}

Output:

Kết hợp dùng +: Xin chào các bạn!
Kết hợp dùng +=: Xin chào các bạn!
Kết hợp dùng append(): Hello World!

Giải thích: Ví dụ minh họa ba cách thông dụng để nối các chuỗi. Toán tử + tạo ra chuỗi mới, +=append() thay đổi trực tiếp chuỗi gốc (hoặc chuỗi được gán ban đầu).

Chèn chuỗi (Insertion)

Phương thức insert() cho phép bạn chèn một chuỗi khác hoặc một ký tự vào một vị trí cụ thể trong chuỗi hiện tại.

#include <iostream>
#include <string>

int main() {
    string s = "Học C++ rất.";
    string chen = " hay và thú vị";

    s.insert(s.find("rất") + 3, chen);

    cout << "Sau khi chèn: " << s << endl;
    return 0;
}

Output:

Sau khi chèn: Học C++ rất hay và thú vị.

Giải thích: Code tìm vị trí của từ "rất" trong chuỗi, sau đó dùng insert() để chèn chuỗi to_insert vào ngay sau từ "rất" (cộng thêm 3 ký tự để vượt qua "rất.").

Xóa ký tự/chuỗi con (Erasing)

Phương thức erase() cho phép xóa một hoặc nhiều ký tự khỏi chuỗi, bắt đầu từ một vị trí cụ thể và với số lượng ký tự nhất định.

#include <iostream>
#include <string>

int main() {
    string s = "Đây là một câu thừa từ cần xóa.";

    size_t pos = s.find(" thừa");
    if (pos != string::npos) {
        s.erase(pos, 5);
    }

    cout << "Sau khi xóa: " << s << endl;
    return 0;
}

Output:

Sau khi xóa: Đây là một câu từ cần xóa.

Giải thích: Chúng ta tìm vị trí của chuỗi con " thừa". Nếu tìm thấy (kiểm tra != string::npos), ta dùng erase() để xóa 5 ký tự bắt đầu từ vị trí đó, loại bỏ " thừa". string::npos là một giá trị đặc biệt biểu thị "không tìm thấy".

Thay thế chuỗi con (Replacing)

Phương thức replace() cho phép thay thế một phần của chuỗi bằng một chuỗi khác.

#include <iostream>
#include <string>

int main() {
    string s = "Tôi thích táo.";
    string cu = "táo";
    string moi = "cam";

    size_t pos = s.find(cu);
    if (pos != string::npos) {
        s.replace(pos, cu.length(), moi);
    }

    cout << "Sau khi thay thế: " << s << endl;
    return 0;
}

Output:

Sau khi thay thế: Tôi thích cam.

Giải thích: Code tìm vị trí của "táo". Nếu tìm thấy, nó dùng replace() để thay thế một đoạn chuỗi bắt đầu từ vị trí đó, có độ dài bằng độ dài của "táo" (old_word.length()), bằng chuỗi new_word ("cam").

Xóa toàn bộ nội dung (Clearing)

Phương thức clear() sẽ xóa tất cả các ký tự trong chuỗi, làm cho chuỗi trở nên rỗng.

#include <iostream>
#include <string>

int main() {
    string s = "Chuỗi này sẽ bị xóa.";
    cout << "Chuỗi ban đầu: '" << s << "'" << endl;

    s.clear();

    cout << "Sau khi clear: '" << s << "'" << endl;
    if (s.empty()) {
        cout << "Chuỗi đã rỗng." << endl;
    }
    return 0;
}

Output:

Chuỗi ban đầu: 'Chuỗi này sẽ bị xóa.'
Sau khi clear: ''
Chuỗi đã rỗng.

Giải thích: clear() làm cho chuỗi my_string không còn ký tự nào. Sau khi gọi clear(), my_string.empty() sẽ trả về true.

3. Tìm kiếm trong Chuỗi

string cung cấp các phương thức mạnh mẽ để tìm kiếm sự xuất hiện của các chuỗi con.

Phương thức find()rfind()
  • find(): Tìm vị trí xuất hiện đầu tiên của một chuỗi con hoặc ký tự, bắt đầu từ đầu chuỗi (hoặc từ một vị trí cụ thể nếu được chỉ định).
  • rfind(): Tìm vị trí xuất hiện cuối cùng của một chuỗi con hoặc ký tự, bắt đầu từ cuối chuỗi (hoặc từ một vị trí cụ thể nếu được chỉ định).

Cả hai phương thức trả về chỉ số (index) của lần xuất hiện đầu tiên/cuối cùng. Nếu không tìm thấy, chúng trả về string::npos.

#include <iostream>
#include <string>

int main() {
    using namespace std;
    string s = "Tìm kiếm từ trong chuỗi này, tìm từ khóa.";
    string tu_khoa = "tìm";
    string tu_khong_co = "Python";

    size_t pos1 = s.find(tu_khoa);
    if (pos1 != string::npos) {
        cout << "Tìm thấy '" << tu_khoa << "' lần đầu ở vị trí: " << pos1 << endl;
    } else {
        cout << "Không tìm thấy '" << tu_khoa << "' lần đầu." << endl;
    }

    size_t pos2 = s.rfind(tu_khoa);
    if (pos2 != string::npos) {
        cout << "Tìm thấy '" << tu_khoa << "' lần cuối ở vị trí: " << pos2 << endl;
    } else {
        cout << "Không tìm thấy '" << tu_khoa << "' lần cuối." << endl;
    }

    size_t pos3 = s.find(tu_khong_co);
     if (pos3 != string::npos) {
        cout << "Tìm thấy '" << tu_khong_co << "' ở vị trí: " << pos3 << endl;
    } else {
        cout << "Không tìm thấy '" << tu_khong_co << "'." << endl;
    }

    return 0;
}

Output:

Tìm thấy 'tìm' lần đầu ở vị trí: 0
Tìm thấy 'tìm' lần cuối ở vị trí: 28
Không tìm thấy 'Python'.

Giải thích: find("tìm") trả về 0 vì "Tìm" (không phân biệt hoa thường) là từ đầu tiên. rfind("tìm") trả về 28, vị trí của "tìm" trong "tìm từ khóa". Khi tìm "Python", find() trả về string::npos, dẫn đến thông báo không tìm thấy.

4. Trích xuất Chuỗi con (Substring)

Phương thức substr() cho phép bạn tạo một chuỗi mới chứa một phần của chuỗi hiện tại.

#include <iostream>
#include <string>

int main() {
    string u = "https://fullhousetesting.web.app/bai-viet/cpp-strings";

    string mien = u.substr(8, 17);
    cout << "Domain: " << mien << endl;

    string duong_dan = u.substr(26);
    cout << "Path: " << duong_dan << endl;
    return 0;
}

Output:

Domain: fullhousetesting.web.app
Path: /bai-viet/cpp-strings

Giải thích: substr() có hai tham số: vị trí bắt đầu và độ dài (tùy chọn). Nếu không truyền độ dài, nó sẽ lấy từ vị trí bắt đầu đến hết chuỗi.

5. So sánh Chuỗi (Comparison)

Bạn có thể so sánh các chuỗi bằng các toán tử so sánh thông thường (==, !=, <, >, <=, >=) hoặc bằng phương thức compare(). So sánh dựa trên thứ tự từ điển (lexicographical order).

  • Toán tử so sánh: Trả về true hoặc false. Dễ đọc và sử dụng cho các phép so sánh bằng/khác và so sánh thứ tự đơn giản.
  • Phương thức compare(): Trả về một số nguyên:
    • 0 nếu hai chuỗi bằng nhau.
    • Một số âm nếu chuỗi gọi phương thức "nhỏ hơn" chuỗi đối số.
    • Một số dương nếu chuỗi gọi phương thức "lớn hơn" chuỗi đối số. compare() hữu ích khi bạn cần biết mối quan hệ thứ tự cụ thể giữa hai chuỗi.
#include <iostream>
#include <string>

int main() {
    using namespace std;
    string s1 = "apple";
    string s2 = "banana";
    string s3 = "apple";

    if (s1 == s3) {
        cout << s1 << " bằng " << s3 << endl;
    }
    if (s1 != s2) {
        cout << s1 << " khác " << s2 << endl;
    }
    if (s1 < s2) {
        cout << s1 << " nhỏ hơn " << s2 << " (theo thứ tự từ điển)" << endl;
    }

    cout << s1 << ".compare(" << s3 << ") = " << s1.compare(s3) << endl;
    cout << s1 << ".compare(" << s2 << ") = " << s1.compare(s2) << endl;
    cout << s2 << ".compare(" << s1 << ") = " << s2.compare(s1) << endl;

    return 0;
}

Output:

apple bằng apple
apple khác banana
apple nhỏ hơn banana (theo thứ tự từ điển)
apple.compare(apple) = 0
apple.compare(banana) = -1
banana.compare(apple) = 1

Giải thích: Ví dụ cho thấy cả hai cách so sánh. Toán tử dễ dùng cho ==, !=, <... compare() cung cấp thông tin chi tiết hơn về mối quan hệ thứ tự (bằng, nhỏ hơn, lớn hơn).

Bài tập ví dụ: C++ Bài 16.A2: Trò chơi khối

Trò chơi khối

FullHouse Dev đang chơi một trò chơi phổ biến ở Byteland. Họ có các khối, mỗi khối biểu thị một số nguyên từ 0 đến 9. Các khối này được sắp xếp ngẫu nhiên để tạo thành các số khác nhau, với điều kiện khối đầu tiên không bao giờ là 0. Sau khi tạo thành một số, họ đọc theo thứ tự ngược lại để kiểm tra xem số đó và số đảo ngược của nó có giống nhau không. Nếu giống nhau, người chơi thắng. Những số như vậy được gọi là số đối xứng.

Yêu cầu

Viết một chương trình để:

  1. Nhận đầu vào từ người dùng.
  2. Kiểm tra xem số đó có phải là số đối xứng hay không.
  3. Thông báo kết quả (thắng hoặc thua).

Input

  • Dòng đầu tiên chứa T, số lượng test case.
  • T dòng tiếp theo, mỗi dòng chứa một số nguyên N.

Output

  • Với mỗi test case, in ra "wins" nếu số đó là số đối xứng, ngược lại in ra "loses" trên một dòng mới.

Ràng buộc

  • 1 ≤ T ≤ 20
  • 1 ≤ N ≤ 20000

Ví dụ

Input:

3
331
666
343

Output:

loses
wins
wins

Giải thích

  • 331 không phải số đối xứng (331 ≠ 133), nên kết quả là "loses".
  • 666 là số đối xứng, nên kết quả là "wins".
  • 343 là số đối xứng, nên kết quả là "wins". Chào bạn,

Đây là hướng dẫn giải bài "Trò chơi khối" bằng C++ sử dụng các thư viện chuẩn (std) để kiểm tra xem một số nguyên dương có phải là số đối xứng hay không.

Ý tưởng chính:

Một số nguyên là số đối xứng nếu nó đọc giống nhau khi đọc từ trái sang phải và từ phải sang trái. Cách đơn giản nhất để kiểm tra điều này là so sánh số ban đầu với số đảo ngược của nó.

Đối với số nguyên, việc đảo ngược trực tiếp có thể hơi phức tạp (phải bóc tách từng chữ số). Một phương pháp phổ biến và khá tiện lợi khi làm việc với các chữ số của một số là chuyển số đó thành dạng chuỗi ký tự. Sau đó, đảo ngược chuỗi ký tự đó và so sánh với chuỗi ban đầu.

Các bước thực hiện:

  1. Đọc số lượng test case: Đầu tiên, chương trình cần đọc số lượng test case T từ đầu vào.
  2. Vòng lặp cho từng test case: Sử dụng một vòng lặp (ví dụ: while hoặc for) để xử lý T lần.
  3. Đọc số N: Bên trong vòng lặp, đọc số nguyên N cho mỗi test case.
  4. Chuyển số N thành chuỗi: Sử dụng hàm có sẵn trong thư viện chuẩn C++ để chuyển đổi số nguyên N thành một đối tượng chuỗi (ví dụ: string). Đây là bước quan trọng để có thể dễ dàng thao tác với các chữ số dưới dạng ký tự.
  5. Tạo một chuỗi đảo ngược: Tạo một bản sao của chuỗi vừa thu được. Sử dụng một hàm hoặc thuật toán có sẵn trong thư viện chuẩn C++ để đảo ngược chuỗi bản sao này.
  6. So sánh chuỗi ban đầu và chuỗi đảo ngược: So sánh chuỗi ban đầu (biểu diễn số N) với chuỗi đã bị đảo ngược.
  7. In kết quả:
    • Nếu hai chuỗi giống hệt nhau, tức là số N là số đối xứng, in ra "wins".
    • Ngược lại, nếu hai chuỗi khác nhau, in ra "loses".
  8. Kết thúc vòng lặp: Lặp lại các bước 3-7 cho đến khi xử lý hết T test case.

Gợi ý sử dụng thư viện std:

  • Để đọc và ghi dữ liệu: Sử dụng cincout từ header <iostream>.
  • Để làm việc với chuỗi: Sử dụng string từ header <string>.
  • Để chuyển số thành chuỗi: Sử dụng to_string() từ header <string>.
  • Để đảo ngược chuỗi: Sử dụng hàm reverse() từ header <algorithm>. Hàm này hoạt động trên các bộ lặp (iterator), bạn có thể dùng chuoi.begin()chuoi.end() để đảo ngược toàn bộ chuỗi.
#include <iostream>
#include <string>
#include <algorithm> // For reverse

int main() {
    ios_base::sync_with_stdio(false); // Tối ưu I/O
    cin.tie(NULL); // Bỏ ràng buộc cin với cout

    int t;
    cin >> t; // Đọc số lượng test case

    while (t--) { // Lặp t lần
        int n;
        cin >> n; // Đọc số nguyên N

        string s = to_string(n); // Chuyển N thành chuỗi
        string rs = s; // Tạo bản sao để đảo ngược
        reverse(rs.begin(), rs.end()); // Đảo ngược chuỗi

        if (s == rs) { // So sánh chuỗi gốc và chuỗi đảo ngược
            cout << "wins\n";
        } else {
            cout << "loses\n";
        }
    }
    return 0;
}

Output cho input ví dụ:

loses
wins
wins

Làm thêm nhiều bài tập miễn phí tại đây


Comments

There are no comments at the moment.