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> // Rất quan trọng!

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

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 greeting = "Xin chào!";
    cout << "Chuỗi: " << greeting << endl;
    cout << "Độ dài (length): " << greeting.length() << endl;
    cout << "Kích thước (size): " << greeting.size() << endl; // Thường giống length
    return 0;
}

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> // Cần cho out_of_range

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

    // Minh họa sự khác biệt khi truy cập index không hợp lệ
    // cout << word[10] << endl; // KHÔNG NÊN làm thế này nếu không chắc chắn!

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

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;
}

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() {
    string s1 = "Xin ";
    string s2 = "chào";
    string s3 = " các bạn!";

    // Dùng +
    string combined_plus = s1 + s2 + s3;
    cout << "Kết hợp dùng +: " << combined_plus << endl; // Output: Xin chào các bạn!

    // Dùng +=
    string modified_plus_equal = s1; // Bắt đầu với s1
    modified_plus_equal += s2; // Nối s2 vào cuối
    modified_plus_equal += s3; // Nối s3 vào cuối
    cout << "Kết hợp dùng +=: " << modified_plus_equal << endl; // Output: Xin chào các bạn!

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

    return 0;
}

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 text = "Học C++ rất.";
    string to_insert = " hay và thú vị";

    // Chèn " hay và thú vị" vào vị trí sau "rất."
    // text.find("rất") tìm vị trí của từ "rất".
    // + 3 để chèn sau chữ "rất" và dấu chấm.
    text.insert(text.find("rất") + 3, to_insert);

    cout << "Sau khi chèn: " << text << endl; // Output: Học C++ rất hay và thú vị.
    return 0;
}

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 sentence = "Đây là một câu thừa từ cần xóa.";

    // Xóa 5 ký tự bắt đầu từ vị trí tìm thấy " thừa"
    size_t pos_thua = sentence.find(" thừa");
    if (pos_thua != string::npos) { // Kiểm tra xem có tìm thấy không
        sentence.erase(pos_thua, 5);
    }

    cout << "Sau khi xóa: " << sentence << endl; // Output: Đây là một câu từ cần xóa.
    return 0;
}

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 sentence = "Tôi thích táo.";
    string old_word = "táo";
    string new_word = "cam";

    // Tìm vị trí của "táo" và thay thế 3 ký tự đó bằng "cam"
    size_t pos_tao = sentence.find(old_word);
    if (pos_tao != string::npos) {
        sentence.replace(pos_tao, old_word.length(), new_word);
    }

    cout << "Sau khi thay thế: " << sentence << endl; // Output: Tôi thích cam.
    return 0;
}

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 my_string = "Chuỗi này sẽ bị xóa.";
    cout << "Chuỗi ban đầu: '" << my_string << "'" << endl;

    my_string.clear(); // Xóa hết nội dung

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

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() {
    string text = "Tìm kiếm từ trong chuỗi này, tìm từ khóa.";
    string keyword = "tìm";
    string missing_keyword = "Python";

    // Tìm kiếm lần đầu tiên
    size_t pos_first = text.find(keyword);
    if (pos_first != string::npos) {
        cout << "Tìm thấy '" << keyword << "' lần đầu ở vị trí: " << pos_first << endl; // Output: 0
    } else {
        cout << "Không tìm thấy '" << keyword << "' lần đầu." << endl;
    }

    // Tìm kiếm lần cuối cùng
    size_t pos_last = text.rfind(keyword);
    if (pos_last != string::npos) {
        cout << "Tìm thấy '" << keyword << "' lần cuối ở vị trí: " << pos_last << endl; // Output: 28
    } else {
        cout << "Không tìm thấy '" << keyword << "' lần cuối." << endl;
    }

    // Tìm kiếm từ không tồn tại
    size_t pos_missing = text.find(missing_keyword);
     if (pos_missing != string::npos) {
        cout << "Tìm thấy '" << missing_keyword << "' ở vị trí: " << pos_missing << endl;
    } else {
        cout << "Không tìm thấy '" << missing_keyword << "'." << endl; // Output: Không tìm thấy 'Python'.
    }

    return 0;
}

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 url = "https://fullhousetesting.web.app/bai-viet/cpp-strings";

    // Lấy domain: bắt đầu từ index 8, lấy 17 ký tự ("fullhousetesting.web.app")
    string domain = url.substr(8, 17);
    cout << "Domain: " << domain << endl; // Output: fullhousetesting.web.app

    // Lấy path: bắt đầu từ index 26 đến hết chuỗi ("/bai-viet/cpp-strings")
    string path = url.substr(26);
     cout << "Path: " << path << endl; // Output: /bai-viet/cpp-strings
    return 0;
}

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() {
    string s1 = "apple";
    string s2 = "banana";
    string s3 = "apple";

    // So sánh dùng toán tử
    if (s1 == s3) {
        cout << s1 << " bằng " << s3 << endl; // Output: apple bằng apple
    }
    if (s1 != s2) {
        cout << s1 << " khác " << s2 << endl; // Output: apple khác banana
    }
    if (s1 < s2) {
        cout << s1 << " nhỏ hơn " << s2 << " (theo thứ tự từ điển)" << endl; // Output: apple nhỏ hơn banana (theo thứ tự từ điển)
    }

    // So sánh dùng compare()
    cout << s1 << ".compare(" << s3 << ") = " << s1.compare(s3) << endl; // Output: 0
    cout << s1 << ".compare(" << s2 << ") = " << s1.compare(s2) << endl; // Output: Số âm (ví dụ: -1)
    cout << s2 << ".compare(" << s1 << ") = " << s2.compare(s1) << endl; // Output: Số dương (ví dụ: 1)

    return 0;
}

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.

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

Comments

There are no comments at the moment.