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ọngthườ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>
    
    int main() {
        string s;
        cout << "Chuoi rong: '" << s << "'" << endl;
        return 0;
    }
    

    Output:

    Chuoi rong: ''

    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 loiChao = "Xin chao C++!";
        cout << "Loi chao: " << loiChao << endl;
    
        string s2("Day la mot chuoi khac.");
        cout << "Chuoi khac: " << s2 << endl;
        return 0;
    }
    

    Output:

    Loi chao: Xin chao C++!
    Chuoi khac: Day la mot chuoi khac.

    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ến string.

  • Sao chép từ một chuỗi khác:

    #include <iostream>
    #include <string>
    
    int main() {
        string goc = "Chuoi goc";
        string banSao = goc;
        cout << "Chuoi sao chep: " << banSao << endl;
    
        string banSao2(goc);
        cout << "Mot ban sao khac: " << banSao2 << endl;
        return 0;
    }
    

    Output:

    Chuoi sao chep: Chuoi goc
    Mot ban sao khac: Chuoi goc

    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 tu;
        cout << "Nhap mot tu: ";
        cin >> tu;
        cout << "Tu da nhap: " << tu << endl;
        return 0;
    }
    

    Output (với ví dụ nhập "Hello World"):

    Nhap mot tu: Hello World
    Tu da nhap: Hello

    Giải thích: Toán tử >> cho string đọ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>
    
    int main() {
        string dong;
        cout << "Nhap mot dong van ban (bao gom ca khoang trang): ";
        getline(cin, dong);
        cout << "Dong da nhap: " << dong << endl;
        return 0;
    }
    

    Output (với ví dụ nhập "Day la mot dong day du."):

    Nhap mot dong van ban (bao gom ca khoang trang): Day la mot dong day du.
    Dong da nhap: Day la mot dong day du.

    Giải thích: getline(luong_nhap, bien_chuoi) đọc tất cả các ký tự từ luồng luong_nhap (ở đây là cin) và lưu vào bien_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ùng cin >>, ký tự xuống dòng cuối cùng có thể còn lại trong bộ đệm, khiến getline đọc ngay lập tức và coi là đã đọc xong dòng rỗng. Sử dụng cin.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 chuoi = "Lap trinh C++";
        cout << "Ky tu dau tien (index 0): " << chuoi[0] << endl;
        cout << "Ky tu thu 5 (index 4): " << chuoi[4] << endl;
        cout << "Ky tu cuoi cung: " << chuoi[chuoi.length() - 1] << endl;
        return 0;
    }
    

    Output:

    Ky tu dau tien (index 0): L
    Ky tu thu 5 (index 4): t
    Ky tu cuoi cung: +

    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>
    
    int main() {
        string s = "Hello";
    
        try {
            cout << "Ky tu tai index 1: " << s.at(1) << endl;
            cout << "Ky tu tai index 4: " << s.at(4) << endl;
            cout << s.at(10) << endl;
        } catch (const out_of_range& e) {
            cerr << "Loi: Truy cap ngoai pham vi: " << e.what() << endl;
        }
        return 0;
    }
    

    Output:

    Ky tu tai index 1: e
    Ky tu tai index 4: o
    Loi: Truy cap ngoai pham vi: basic_string::at: __n (which is 10) >= this->size() (which is 5)

    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 s = "Lap trinh C++ that tuyet!";
    cout << "Do dai chuoi: " << s.length() << endl;
    cout << "Kich thuoc chuoi: " << s.size() << endl;

    string rong;
    cout << "Do dai chuoi rong: " << rong.length() << endl;
    return 0;
}

Output:

Do dai chuoi: 25
Kich thuoc chuoi: 25
Do dai chuoi rong: 0

Giải thích: .length().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 p1 = "Hello";
        string p2 = " ";
        string p3 = "World!";
    
        string dayDu = p1 + p2 + p3;
        cout << "Chuoi sau khi noi: " << dayDu << endl;
    
        string loiChao = "Chao" + string(" buoi sang");
        cout << loiChao << endl;
        return 0;
    }
    

    Output:

    Chuoi sau khi noi: Hello World!
    Chao buoi sang

    Giải thích: Toán tử + giữa hai chuỗi string 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ỗi string 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 s = "Xin chao";
        s += " cac ban";
        s += "!";
        cout << "Chuoi sau khi +=: " << s << endl;
        return 0;
    }
    

    Output:

    Chuoi sau khi +=: Xin chao cac ban!

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

    if (s1 == s3) {
        cout << s1 << " bang " << s3 << endl;
    }

    if (s1 != s2) {
        cout << s1 << " khac " << s2 << endl;
    }

    if (s1 < s2) {
        cout << s1 << " nho hon " << s2 << " (theo tu dien)" << endl;
    }

     if (s4 < s1) {
        cout << s4 << " nho hon " << s1 << " (phan biet hoa thuong)" << endl;
    }
    return 0;
}

Output:

apple bang apple
apple khac banana
apple nho hon banana (theo tu dien)
Apple nho hon apple (phan biet hoa thuong)

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 s = "Hoc C++ that thu vi, C++ rat tuyet!";
    string timKiem = "C++";
    size_t vitri1 = s.find(timKiem);

    if (vitri1 != string::npos) {
        cout << "Tim thay '" << timKiem << "' lan dau tien tai index: " << vitri1 << endl;
    } else {
        cout << "'" << timKiem << "' khong tim thay trong chuoi." << endl;
    }

    size_t tu = (vitri1 == string::npos) ? 0 : vitri1 + timKiem.length();
    size_t vitri2 = s.find(timKiem, tu);

     if (vitri2 != string::npos) {
        cout << "Tim thay '" << timKiem << "' lan thu hai tai index: " << vitri2 << endl;
    }

    size_t vitriKyTu = s.find('t');
     if (vitriKyTu != string::npos) {
        cout << "Tim thay ky tu 't' lan dau tien tai index: " << vitriKyTu << endl;
    }

    return 0;
}

Output:

Tim thay 'C++' lan dau tien tai index: 4
Tim thay 'C++' lan thu hai tai index: 24
Tim thay ky tu 't' lan dau tien tai index: 7

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 cau = "Hoc lap trinh C++ rat hieu qua.";

    string chuNgu = cau.substr(0, 3);
    string dongTu = cau.substr(4, 8);
    string ngonNgu = cau.substr(13, 3);

    cout << "Subject: " << chuNgu << endl;
    cout << "Verb: " << dongTu << endl;
    cout << "Language: " << ngonNgu << endl;

    string phanConLai = cau.substr(17);
    cout << "Remainder: " << phanConLai << endl;

    return 0;
}

Output:

Subject: Hoc
Verb: lap trin
Language: C++
Remainder: rat hieu qua.

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 pospos + 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");
        cout << "Append: " << s << endl;
        return 0;
    }
    

    Output:

    Append: Hello World
  • 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 ");
        cout << "Insert: " << s << endl;
        return 0;
    }
    

    Output:

    Insert: Hoc toi C++!
  • Xóa (.erase(pos, len)): Xóa len 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);
        cout << "Erase: " << s << endl;
        return 0;
    }
    

    Output:

    Erase: Day la mot chuoi tu.
  • Thay thế (.replace(pos, len, string)): Thay thế len ký tự bắt đầu từ vị trí pos bằng nội dung của string khác.

    #include <iostream>
    #include <string>
    
    int main() {
        string s = "Toi thich Java.";
        s.replace(10, 4, "C++");
        cout << "Replace: " << s << endl;
        return 0;
    }
    

    Output:

    Replace: Toi thich C++.

    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 s = "Hello";
        cout << "Duyet bang range-based for:" << endl;
        for (char c : s) {
            cout << c << " ";
        }
        cout << endl;
        return 0;
    }
    

    Output:

    Duyet bang range-based for:
    H e l l o

    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 s = "World";
        cout << "Duyet bang index:" << endl;
        for (size_t i = 0; i < s.length(); ++i) {
            cout << s[i] << " ";
        }
        cout << endl;
        return 0;
    }
    

    Output:

    Duyet bang index:
    W o r l d

    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ểu size_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 s = "  Khoang trang thua o dau va cuoi  ";
    cout << "Ban dau: '" << s << "'" << endl;

    size_t dau = s.find_first_not_of(" \t\n\r");
    size_t cuoi = s.find_last_not_of(" \t\n\r");

    if (dau != string::npos && cuoi != string::npos) {
         s = s.substr(dau, cuoi - dau + 1);
    } else {
        s = "";
    }
    cout << "Sau khi cat khoang trang (don gian): '" << s << "'" << endl;

    string cu = "thua";
    string moi = "khong can";
    size_t vitriTu = s.find(cu);

    if (vitriTu != string::npos) {
        s.replace(vitriTu, cu.length(), moi);
    }
    cout << "Sau khi thay the: '" << s << "'" << endl;

    s.append("!");
    cout << "Sau khi them: '" << s << "'" << endl;

    return 0;
}

Output:

Ban dau: '  Khoang trang thua o dau va cuoi  '
Sau khi cat khoang trang (don gian): 'Khoang trang thua o dau va cuoi'
Sau khi thay the: 'Khoang trang khong can o dau va cuoi'
Sau khi them: 'Khoang trang khong can o dau va cuoi!'

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.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

There are no comments at the moment.