Bài 28.3: Kết hợp chuỗi ký tự với mảng và vector trong C++

Chào mừng trở lại với loạt bài viết về C++! Sau khi đã làm quen với chuỗi ký tự cơ bản và các cấu trúc dữ liệu như mảng và vector, đã đến lúc chúng ta khám phá một chủ đề vô cùng quan trọng và thực tế: làm thế nào để lưu trữ và làm việc với nhiều chuỗi ký tự cùng một lúc. Đây là tình huống thường gặp khi bạn cần quản lý danh sách tên, danh sách sản phẩm, các dòng văn bản từ một tệp, và vô vàn các ứng dụng khác.

Trong C++, chúng ta có thể tận dụng sức mạnh của mảng (arrays) và vector (vector) để làm điều này. Mỗi cấu trúc này đều có những ưu điểm riêng và phù hợp với các trường hợp sử dụng khác nhau. Hãy cùng đi sâu vào chi tiết nhé!

Lưu trữ Chuỗi ký tự trong Mảng (Arrays of Strings)

Mảng trong C++ là một tập hợp các phần tử cùng kiểu dữ liệu được lưu trữ liên tục trong bộ nhớ. Khi làm việc với chuỗi ký tự, chúng ta có hai cách tiếp cận chính với mảng:

1. Mảng các Chuỗi ký tự kiểu C (C-style String Arrays)

Theo truyền thống, chuỗi ký tự trong C/C++ được biểu diễn bằng mảng các ký tự (char) kết thúc bằng ký tự null (\0). Do đó, một "mảng chuỗi" có thể được hiểu là một mảng hai chiều của các ký tự, nơi mỗi hàng là một chuỗi ký tự riêng biệt.

Ví dụ:

#include <iostream>
#include <cstring>

int main() {
    using namespace std;
    char dsTen[3][20];

    strcpy(dsTen[0], "Alice");
    strcpy(dsTen[1], "Bob");
    strcpy(dsTen[2], "Charlie");

    cout << "Danh sach ten (C-style array):\n";
    for (int i = 0; i < 3; ++i) {
        cout << dsTen[i] << endl;
    }

    char tenKetHop[50];
    strcpy(tenKetHop, dsTen[0]);
    strcat(tenKetHop, " va ");
    strcat(tenKetHop, dsTen[1]);
    cout << "Ket hop (C-style): " << tenKetHop << endl;

    return 0;
}

Output:

Danh sach ten (C-style array):
Alice
Bob
Charlie
Ket hop (C-style): Alice va Bob

Giải thích code:

  • Chúng ta khai báo char danhSachTen[3][20] để tạo ra một mảng có 3 hàng và 20 cột. Mỗi hàng danhSachTen[i] có thể chứa một chuỗi ký tự C-style có độ dài tối đa 19 ký tự, còn chỗ cho ký tự \0.
  • Để gán giá trị cho các chuỗi, chúng ta không thể dùng toán tử =. Thay vào đó, chúng ta phải dùng hàm strcpy (hoặc strncpy an toàn hơn) từ thư viện <cstring> để sao chép nội dung chuỗi.
  • Truy cập từng chuỗi con rất đơn giản, chỉ cần dùng danhSachTen[i].
  • Kết hợp các chuỗi con cũng yêu cầu sử dụng các hàm của <cstring> như strcat.

Hạn chế: Cách tiếp cận này khá cồng kềnh và tiềm ẩn rủi ro tràn bộ đệm (buffer overflow) nếu bạn không cẩn thận với kích thước của mảng và độ dài chuỗi.

2. Mảng các Đối tượng string

Trong C++ hiện đại, cách tốt hơn và an toàn hơn là sử dụng lớp string. Khi đó, một "mảng chuỗi" đơn giản là một mảng mà mỗi phần tử của nó là một đối tượng string.

Ví dụ:

#include <iostream>
#include <string>

int main() {
    using namespace std;
    string dsTen[3];

    dsTen[0] = "Alice Wonderland";
    dsTen[1] = "Bob";
    dsTen[2] = "Charlie Day La Mot Cai Ten Rat Dai Ma Khong Can Lo Lo Tran Bo Dem";

    cout << "Danh sach ten (string array):\n";
    for (int i = 0; i < 3; ++i) {
        cout << dsTen[i] << endl;
    }

    string tenKetHop = dsTen[0] + " va " + dsTen[1];
    cout << "Ket hop (string): " << tenKetHop << endl;

    return 0;
}

Output:

Danh sach ten (string array):
Alice Wonderland
Bob
Charlie Day La Mot Cai Ten Rat Dai Ma Khong Can Lo Lo Tran Bo Dem
Ket hop (string): Alice Wonderland va Bob

Giải thích code:

  • Chúng ta khai báo string danhSachTen[3]; để tạo ra một mảng chứa 3 đối tượng string.
  • Việc gán giá trị trở nên cực kỳ đơn giản bằng cách sử dụng toán tử gán =. string tự động quản lý bộ nhớ cần thiết cho chuỗi, bất kể độ dài của nó (trong giới hạn bộ nhớ hệ thống).
  • Truy cập và in ra cũng tương tự như mảng thông thường: danhSachTen[i].
  • Việc kết hợp (nối) các chuỗi cũng rất trực quan, chỉ cần sử dụng toán tử + giữa các đối tượng string.

Ưu điểm: An toàn hơn, dễ sử dụng hơn, tự động quản lý bộ nhớ.

Hạn chế của cả hai loại mảng: Kích thước của mảng phải được xác định tại thời điểm biên dịch. Bạn không thể thêm hoặc bớt các chuỗi một cách linh hoạt sau khi mảng đã được tạo. Đây là lúc vector phát huy tác dụng!

Lưu trữ Chuỗi ký tự trong Vector (vector<string>)

vector là một container (vùng chứa) động trong Thư viện Chuẩn C++ (STL - Standard Template Library). Nó hoạt động giống như một mảng, nhưng kích thước của nó có thể thay đổi trong quá trình thực thi chương trình. Khi kết hợp với string, chúng ta có một giải pháp mạnh mẽ và linh hoạt để quản lý bộ sưu tập các chuỗi ký tự.

Khai báo một vector chứa các chuỗi string có dạng vector<string>.

Ví dụ:

#include <iostream>
#include <string>
#include <vector>

int main() {
    using namespace std;
    vector<string> dsMon;

    dsMon.push_back("Toan roi rac");
    dsMon.push_back("Lap trinh C++ nang cao");
    dsMon.push_back("Cau truc du lieu va Giai thuat");

    cout << "So luong mon hoc ban dau: " << dsMon.size() << endl;

    cout << "Danh sach mon hoc:\n";
    for (int i = 0; i < dsMon.size(); ++i) {
        cout << "- " << dsMon[i] << endl;
    }

    dsMon.push_back("He dieu hanh");
    cout << "\nSo luong mon hoc sau khi them: " << dsMon.size() << endl;

    cout << "Danh sach mon hoc moi:\n";
    for (const string& m : dsMon) {
        cout << "- " << m << endl;
    }

    if (dsMon.size() > 1) {
        string monChinh = dsMon[1] + " va " + dsMon[2];
         cout << "\nMon hoc chinh trong danh sach: " << monChinh << endl;
    }

    cout << "\nMon hoc co chua 'C++':\n";
    for (const string& m : dsMon) {
        if (m.find("C++") != string::npos) {
            cout << "- " << m << endl;
        }
    }

    return 0;
}

Output:

So luong mon hoc ban dau: 3
Danh sach mon hoc:
- Toan roi rac
- Lap trinh C++ nang cao
- Cau truc du lieu va Giai thuat

So luong mon hoc sau khi them: 4
Danh sach mon hoc moi:
- Toan roi rac
- Lap trinh C++ nang cao
- Cau truc du lieu va Giai thuat
- He dieu hanh

Mon hoc chinh trong danh sach: Lap trinh C++ nang cao va Cau truc du lieu va Giai thuat

Mon hoc co chua 'C++':
- Lap trinh C++ nang cao

Giải thích code:

  • vector<string> danhSachMonHoc; khai báo một vector rỗng có thể chứa các đối tượng string.
  • Chúng ta sử dụng phương thức push_back() để thêm các chuỗi mới vào cuối vector. Vector sẽ tự động điều chỉnh kích thước.
  • size() trả về số lượng phần tử hiện có trong vector.
  • Truy cập các phần tử tương tự như mảng, sử dụng toán tử [] hoặc phương thức at() (phương thức at() kiểm tra lỗi ngoài phạm vi và ném ngoại lệ, an toàn hơn).
  • Chúng ta có thể lặp qua vector bằng vòng lặp for truyền thống với chỉ số hoặc sử dụng range-based for loop rất tiện lợi cho việc duyệt qua tất cả các phần tử.
  • Việc kết hợp chuỗi từ các phần tử vector cũng đơn giản như với mảng string, sử dụng toán tử +.
  • Chúng ta cũng có thể dễ dàng áp dụng các phương thức của string lên từng phần tử của vector, ví dụ như sử dụng find() để tìm kiếm chuỗi con.

Ưu điểm của vector<string>:

  • Linh hoạt: Kích thước có thể thay đổi dynamically (thêm/bớt phần tử).
  • An toàn: Tự động quản lý bộ nhớ.
  • Tiện lợi: Cung cấp nhiều phương thức hữu ích như push_back, pop_back, insert, erase, size, empty, v.v.
  • Tương thích: Kết hợp tốt với các thuật toán và iterator trong STL.

Khi nào sử dụng Mảng, khi nào sử dụng Vector?

  • Sử dụng mảng string khi bạn biết chính xác số lượng chuỗi cần lưu trữ tại thời điểm biên dịch và số lượng này sẽ không thay đổi. Nó có thể hơi hiệu quả hơn về mặt bộ nhớ và tốc độ truy cập nếu kích thước rất lớn và cố định, nhưng sự khác biệt thường không đáng kể với kích thước vừa phải.
  • Sử dụng vector<string> trong hầu hết các trường hợp khác. Khi bạn không biết trước số lượng chuỗi, số lượng có thể thay đổi, hoặc bạn cần thêm/bớt các chuỗi một cách linh hoạt trong quá trình chạy chương trình. vector là lựa chọn linh hoạt và hiện đại hơn.

Nên tránh sử dụng mảng C-style (char[][]) cho các bộ sưu tập chuỗi trừ khi bạn có lý do đặc biệt (ví dụ: làm việc với mã cũ) do những rủi ro và sự bất tiện mà nó mang lại.

Kết hợp và Thao tác nâng cao hơn

Với cả mảng stringvector<string>, bạn có thể áp dụng tất cả các kiến thức đã học về string lên từng phần tử.

Ví dụ:

  • Truy cập và sửa đổi: danhSachTen[0] = "Alice moi"; hoặc danhSachMonHoc[1] += " va hon the nua";
  • Truy cập ký tự cụ thể trong một chuỗi: char kyTuDau = danhSachTen[0][0]; hoặc char kyTuCuoi = danhSachMonHoc[2].back();
  • Tìm kiếm: size_t viTri = danhSachMonHoc[1].find("C++");
  • Trích xuất chuỗi con: string phanDau = danhSachMonHoc[2].substr(0, 10);

Sức mạnh nằm ở việc bạn coi mỗi phần tử của mảng hoặc vector như một đối tượng string độc lập và áp dụng các phương thức của nó.

#include <iostream>
#include <string>
#include <vector>

int main() {
    using namespace std;
    vector<string> dsTP = {"Ha Noi", "TP Ho Chi Minh", "Da Nang", "Hue"};

    if (dsTP.size() > 1) {
        cout << "Thanh pho thu 2: " << dsTP[1]
                  << " (Do dai: " << dsTP[1].length() << ")" << endl;
    }

    if (!dsTP.empty()) {
        dsTP.back() = "Can Tho";
    }

    cout << "\nDanh sach thanh pho sau khi thay doi:\n";
    for (const string& t : dsTP) {
        cout << "- " << t << endl;
    }

    string timTP = "Da Nang";
    bool f = false;
    for (const string& t : dsTP) {
        if (t == timTP) {
            f = true;
            break;
        }
    }
    if (f) {
        cout << "\nDa tim thay thanh pho: " << timTP << endl;
    } else {
        cout << "\nKhong tim thay thanh pho: " << timTP << endl;
    }

    return 0;
}

Output:

Thanh pho thu 2: TP Ho Chi Minh (Do dai: 14)

Danh sach thanh pho sau khi thay doi:
- Ha Noi
- TP Ho Chi Minh
- Da Nang
- Can Tho

Da tim thay thanh pho: Da Nang

Giải thích code:

  • Ví dụ này minh họa cách truy cập một phần tử cụ thể trong vector (danhSachThanhPho[1]), sử dụng phương thức .length() của string để lấy độ dài của chuỗi đó.
  • Sử dụng .back() để truy cập (và ở đây là sửa đổi) phần tử cuối cùng của vector.
  • Thực hiện một vòng lặp đơn giản để tìm kiếm một chuỗi cụ thể trong vector bằng cách so sánh trực tiếp các đối tượng string sử dụng toán tử ==.

Bài tập ví dụ: C++ Bài 16.A3: Lời chào Giáng sinh

Lời chào Giáng sinh

FullHouse Dev đang chuẩn bị cho mùa Giáng sinh. Anh ấy biết rằng Giáng sinh được tổ chức vào ngày 25 tháng 12 hàng năm.

Mô tả

Cho một ngày X trong tháng 12. Hãy xác định xem đó có phải là ngày Giáng sinh hay không.

In ra "CHRISTMAS" nếu đó là ngày Giáng sinh. Ngược lại, in ra "ORDINARY".

Input

  • Dòng đầu tiên chứa số nguyên T - số lượng test case.
  • T dòng tiếp theo, mỗi dòng chứa một số nguyên X, biểu thị một ngày trong tháng 12.

Output

Với mỗi test case, in ra trên một dòng mới:

  • "CHRISTMAS" nếu đó là ngày Giáng sinh
  • "ORDINARY" nếu không phải ngày Giáng sinh

Ràng buộc

  • 1 ≤ T ≤ 100
  • 1 ≤ X ≤ 31

Ví dụ

Input:

2 25 12

Output:

CHRISTMAS ORDINARY

Giải thích:
  • Test case 1: Ngày 25 là Giáng sinh, nên in ra "CHRISTMAS".
  • Test case 2: Ngày 12 không phải Giáng sinh, nên in ra "ORDINARY".

Hướng dẫn

  1. Đọc số lượng test case T.
  2. Với mỗi test case:
    • Đọc ngày X
    • Kiểm tra xem X có bằng 25 không
    • Nếu X = 25, in ra "CHRISTMAS"
    • Ngược lại, in ra "ORDINARY"

Hãy giúp FullHouse Dev xác định ngày Giáng sinh! Chào bạn, đây là hướng dẫn giải bài "Lời chào Giáng sinh" bằng C++ một cách ngắn gọn và sử dụng các thành phần chuẩn (std):

Bài toán rất đơn giản: với mỗi ngày X trong tháng 12, kiểm tra xem nó có phải ngày 25 hay không.

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

  1. Bao gồm thư viện cần thiết: Bạn cần thư viện cho việc nhập xuất dữ liệu chuẩn (standard input/output).
  2. Hàm main: Mọi chương trình C++ bắt đầu từ hàm main.
  3. Đọc số lượng test case: Khai báo một biến kiểu số nguyên (ví dụ int) để lưu số lượng test case T. Đọc giá trị của T từ đầu vào chuẩn (sử dụng cin).
  4. Vòng lặp xử lý test case: Sử dụng một cấu trúc lặp (ví dụ: while hoặc for) để thực hiện các bước xử lý cho mỗi test case. Vòng lặp này sẽ chạy đúng T lần. Một cách ngắn gọn để làm vòng lặp T lần là dùng while (T--).
  5. Đọc ngày trong tháng: Bên trong vòng lặp, khai báo một biến kiểu số nguyên (ví dụ int) để lưu ngày X. Đọc giá trị của X từ đầu vào chuẩn (sử dụng cin).
  6. Kiểm tra điều kiện: Sử dụng cấu trúc điều kiện if để kiểm tra xem giá trị của X có bằng 25 hay không.
  7. In kết quả:
    • Nếu điều kiện X == 25 là đúng, sử dụng cout để in ra chuỗi "CHRISTMAS".
    • Ngược lại (sử dụng else), sử dụng cout để in ra chuỗi "ORDINARY".
    • Sau khi in chuỗi, đảm bảo in thêm ký tự xuống dòng (\n hoặc endl) để mỗi kết quả của test case nằm trên một dòng riêng biệt.
  8. Kết thúc hàm main: Trả về 0 để báo hiệu chương trình kết thúc thành công.
#include <iostream>

int main() {
    using namespace std;
    int t;
    cin >> t;
    while (t--) {
        int x;
        cin >> x;
        if (x == 25) {
            cout << "CHRISTMAS\n";
        } else {
            cout << "ORDINARY\n";
        }
    }
    return 0;
}

Output:

CHRISTMAS
ORDINARY

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


Comments

There are no comments at the moment.