Bài tập

Bài 15: Thư viện STL C++ (phần 1) - Gợi ý một số phần cơ bản

Huy Erick

Giới thiệu các thành phần của STL C++ và các tiện ích cơ bản Thư viện Template chuẩn (STL) của ngôn ngữ lập trình C++ là một trong những thứ mà chúng ta nghe tới...

Giới thiệu các thành phần của STL C++ và các tiện ích cơ bản

Thư viện Template chuẩn (STL) của ngôn ngữ lập trình C++ là một trong những thứ mà chúng ta nghe tới rất nhiều khi học lập trình C++. STL chứa những template (khuôn mẫu) của cấu trúc dữ liệu và thuật toán, được xây dựng một cách tổng quát nhất để hỗ trợ trong quá trình lập trình. Thư viện này giúp việc lập trình trở nên dễ dàng và linh hoạt hơn.

STL giúp người dùng thực hiện nhiều công việc như nhập xuất dữ liệu, quản lý mảng động, xây dựng các cấu trúc dữ liệu cơ bản (ngăn xếp, hàng đợi, tập hợp,...) và cung cấp các giải thuật cơ bản như sắp xếp, tìm min - max, tính tổng và tìm ước chung lớn nhất. Việc sử dụng STL rất quan trọng đối với những bạn muốn tham gia các kỳ thi HSG Tin học hoặc nghiên cứu về thuật toán trên ngôn ngữ C++.

Trong bài viết này, chúng ta sẽ chỉ giới thiệu những phần cơ bản nhất của STL cùng với một số template hữu ích nhất đối với các bạn đang học lập trình C++ cơ bản. Các ứng dụng tiếp theo của STL sẽ được đề cập cụ thể trong các bài viết khác về cấu trúc dữ liệu và giải thuật.

Các thành phần của thư viện STL

Thư viện STL rất rộng lớn và bao gồm rất nhiều template khác nhau. Tuy nhiên, chúng ta có thể chia STL thành 4 phần chính:

Containers Library (Thư viện lưu trữ)

Thư viện này chứa các cấu trúc dữ liệu mẫu như vector, stack, queue, deque, set, map,...

Algorithm Library (Thư viện thuật toán)

Chứa các thuật toán viết sẵn để thao tác với dữ liệu.

Iterator Library (Thư viện biến lặp)

Là các biến lặp, sử dụng để truy cập và duyệt các phần tử dữ liệu trong các containers. Đây là một phần quan trọng của STL và sẽ được đề cập chi tiết trong các bài viết về từng containers.

Numeric Library (Thư viện số học)

Chứa các hàm toán học.

Từ những buổi học trước, các bạn cũng đã sử dụng nhiều thứ thuộc thư viện STL như các hàm nhập xuất của thư viện iostreamcin, cout hay thư viện string để xử lý chuỗi kí tự. Bài viết này sẽ giới thiệu thêm một vài thư viện con hữu ích của STL, sẽ hỗ trợ rất tốt cho các bạn trong quá trình học lập trình C++.

Để sử dụng thư viện STL, các bạn cần khai báo không gian tên là using namespace std;, sau đó khai báo thư viện cần dùng bằng cú pháp #include {Tên_thư_viện}>. Hãy tham khảo thêm về thư viện này trên trang web cplusplus.com để nắm rõ hơn về các template cụ thể.

Thư viện là một trong những thành phần quan trọng của STL. Ngoài ra, trong thư viện này còn có rất nhiều containers khác nhưng đối với những bạn đang học cơ bản, việc giới thiệu các cấu trúc dữ liệu khác không có tác dụng gì, vì bạn sẽ nhanh chóng quên do chưa sử dụng đến chúng. Do đó, tôi chỉ giới thiệu vì nó sẽ đi cùng với bạn trong suốt quá trình học tập ngôn ngữ C++. Các containers khác sẽ được giới thiệu khi học tới các bài học cụ thể liên quan.

Khai báo và truy cập phần tử

vector là kiểu dữ liệu mảng động - hỗ trợ người dùng lưu trữ các phần tử có cùng kiểu. Khác với mảng thông thường, vector rất linh hoạt và có nhiều phương thức hỗ trợ. Để khai báo một vector, ta sử dụng cú pháp sau:

#include  // Khai báo thư viện chứa vector.
using namespace std;
vector {Kiểu_phần_tử} > {Tên_vector};

Trong đó, {Kiểu phần tử} là một kiểu dữ liệu bất kỳ, còn {Tên_vector} là một định danh của người dùng. Ví dụ dưới đây khai báo một vector chứa số nguyên:

#include 
using namespace std;
vector integer_list;

Khi mới khai báo, mặc định trong vector sẽ không có gì cả, trừ khi bạn khởi tạo trước giá trị cho nó. Ta có thể khởi tạo trước cho vector có bao nhiêu vị trí, thậm chí khởi tạo đồng loạt giá trị ban đầu cho tất cả các vị trí đó theo cách sau:

vector {Kiểu_phần_tử} > {Tên_vector}({Số_vị_trí}, {Giá_trị});

Các phần tử trong vector cũng sẽ mặc định đánh số từ vị trí 0. Ví dụ dưới đây khởi tạo một vector gồm 10 số 1:

vector integer_list(10, 1);

Để truy cập một phần tử trong vector, ta sử dụng toán tử [] giống như mảng (tuy nhiên phải đảm bảo vị trí tồn tại trong vector). Ví dụ, cú pháp integer_list[5] sẽ truy cập tới phần tử ở vị trí thứ 5 của vector.

Các hàm cung cấp sẵn của vector

Thư viện vector đã được viết sẵn rất nhiều hàm hỗ trợ. Để sử dụng hàm có sẵn, ta sử dụng cú pháp {Tên_vector}.{Tên_hàm}. Dưới đây là một số hàm thường sử dụng của vector và tác dụng của chúng, kèm theo ví dụ về cách sử dụng:

Tên hàm Tác dụng Ví dụ
size() Trả về kích thước của vector cout integer_list.size();
push_back(x) Thêm phần tử x vào cuối vector integer_list.push_back(10);
pop_back() Xóa phần tử ở cuối vector integer_list.pop_back();
resize(n, x) Khởi tạo vector với kích thước n và tất cả mọi vị trí đều mang giá trị x integer_list.resize(10, 1);
front() Trả về phần tử ở đầu vector cout integer_list.front();
back() Trả về phần tử ở cuối vector cout integer_list.back();
begin() Trả về địa chỉ của phần tử đầu tiên trong vector. Muốn truy cập vào giá trị của địa chỉ đó, ta dùng toán tử * cout *integer_list.begin();
end() Trả về địa chỉ của phần tử đứng sau phần tử cuối cùng của vector. Phần tử này không mang giá trị, nó chỉ dùng để chốt cuối vector for (auto it = integer_list.begin(); it != integer_list.end(); ++it)
reverse(l, r) Đảo ngược vector từ vị trí l tới vị trí r-1 reverse(integer_list.begin(), integer_list.end());
clear() Xóa toàn bộ phần tử của vector integer_list.clear();
swap() Hoán đổi các giá trị của hai vector cho nhau a.swap(b);

Duyệt vector bằng chỉ số phần tử

Muốn duyệt qua các phần tử của vector rất đơn giản, ta áp dụng vòng lặp giống như mảng. Giả sử vector a đã được khởi tạo với kích thước là N. Do vector được đánh số từ 0, các phần tử sẽ có số thứ tự lần lượt là 0, 1, 2,..., N-1. Dựa vào đó, có thể duyệt các phần tử của vector bằng một vòng lặp qua các chỉ số như sau:

for (int i = {Chỉ_số_đầu}; i = {Chỉ_số_cuối}; ++i) {
    {Các câu lệnh với a[i]};
}

Ví dụ, muốn in ra các phần tử của vector a = {1, 2, 3, 4}, ta viết như sau:

for (int i = 0; i = 3; ++i) {
    cout  a[i]  ' ';
}

Kết quả chạy chương trình:

1 2 3 4

Duyệt và truy cập vector bằng biến lặp (iterator)

Mỗi containers trong STL đều hỗ trợ sẵn iterator (biến lặp) dùng để duyệt qua và truy cập các phần tử, đôi khi cũng để thao tác với các hàm thành viên của containers. Cú pháp khai báo như sau:

vector {Kiểu_phần_tử} >::iterator {Tên_biến_lặp};

Ví dụ:

vector int >::iterator it_1;
vector double >::iterator it_2;

Khi được khai báo, các biến lặp sẽ chỉ duyệt qua được các phần tử có kiểu đúng với kiểu đã khai báo cho biến lặp. Đối với tất cả các containers khác trong STL, cách khai báo biến lặp hoàn toàn tương tự. Sau khi đã khai báo, để duyệt và in ra các phần tử bằng biến lặp, ta sử dụng vòng lặp như sau:

for ({Biến_lặp} = {Địa_chỉ_đầu}; {Biến_lặp} != {Địa_chỉ_cuối}; {Tăng_giảm_biến_lặp}) {
    cout  *{Tên_Biến_Lặp};
}

Ngoài lệnh cout, biến lặp cũng có thể thao tác với các câu lệnh khác. Các biến lặp iterator có thể được sử dụng kèm với các phép toán như ++, -, !=, ==, =+, - với các hằng số. Các phép toán này đã được nạp chồng sẵn trong thư viện STL.

Ví dụ cụ thể: Dưới đây là chương trình sử dụng biến lặp để duyệt qua tất cả các phần tử của một vector và in ra các phần tử đó:

#include 
#include 
using namespace std;
int main() {
    vector int > integers;
    integers.push_back(1);
    integers.push_back(2);
    integers.push_back(3);
    integers.push_back(4); // vector = {1, 2, 3, 4}.
    vector::iterator it;
    for (it = integers.begin(); it != integers.end(); ++it) {
        cout  *it  ' ';
    }
    return 0;
}

Biên dịch và chạy chương trình trên sẽ cho ra kết quả:

1 2 3 4

Duyệt vector bằng biến auto

Từ phiên bản C++11 trở đi, sự xuất hiện của biến auto đã khiến cho việc duyệt phần tử trong vector trở nên đơn giản hơn rất nhiều. Ta có thể dùng một biến auto để duyệt toàn bộ các phần tử từ đầu vector tới cuối vector bằng cú pháp:

for (auto e: {Tên_vector}) {
    {Các_câu_lệnh};
}

Ví dụ:

vector int > integers;
integers.push_back(1);
integers.push_back(2);
integers.push_back(3);
integers.push_back(4); // vector = {1, 2, 3, 4}.
// Duyệt và in ra các phần tử trong vector.
for (auto e: integers) {
    cout  e  ' ';
}

Kết quả chạy chương trình:

1 2 3 4

Tuy nhiên, cách duyệt này chỉ duyệt được các phần tử từ đầu tới cuối vector chứ không thể duyệt được một đoạn phần tử trên vector.

1