I. Giới thiệu về thư viện chuẩn C++ (STL)
1. Lời mở đầu
Thư viện Template chuẩn (Standard Template Library - STL) của C++ là một phần quan trọng mà các bạn học lập trình C++ thường nghe đến. Như đã giới thiệu trong các bài trước, Template là khái niệm về khuôn mẫu hàm. STL chính là một thư viện chứa các template cho cấu trúc dữ liệu và thuật toán được xây dựng theo cách tổng quát nhất, nhằm hỗ trợ người dùng trong quá trình lập trình. Có thể nói, sức mạnh của ngôn ngữ C++ chủ yếu nằm trong STL, thư viện này giúp lập trình có tính khái quát cao và đơn giản hơn nhiều.
Thư viện STL giúp người dùng thực hiện các công việc như nhập xuất dữ liệu, quản lý mảng động, xây dựng sẵn 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, thậm chí cả 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 quan tâm đến việc tham gia kỳ thi HSG Tin học hoặc nghiên cứu về thuật toán trên ngôn ngữ C++.
Trong chuyên đề này, tôi sẽ chia sẻ những điều cơ bản nhất về STL cùng với một số template hữu ích nhất của nó đố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 trình bày cụ thể trong từng chuyên đề về cấu trúc dữ liệu và giải thuật.
2. Các thành phần của thư viện STL
Thư viện STL (Standard Template Library) rất lớn và bao gồm nhiều template khác nhau. Ta có thể chia STL thành 4 phần chính như sau:
- Containers Library: Thư viện chứa các cấu trúc dữ liệu mẫu như vector, stack, queue, deque, set, map,...
- Algorithm Library: Chứa các thuật toán đã được viết sẵn để thao tác với dữ liệu.
- Iterator Library: Là các biến lặp, sử dụng để truy cập và duyệt các phần tử dữ liệu của các containers. Đơn giản mà hiểu, iterator giống như biến chạy trên dữ liệu, nhưng nó truy cập vào địa chỉ của dữ liệu. Cách dễ hình dung nhất của iterator là con trỏ, nhưng ta đã bỏ qua con trỏ và sẽ đề cập đến iterator trong từng containers.
- Numeric Library: Chứa các hàm toán học.
Trong các buổi học trước, chúng ta đã sử dụng nhiều thành phần thuộc thư viện STL như các hàm nhập xuất của thư viện <iostream> như cin, cout, và thư viện <string> để xử lý chuỗi kí tự. Bài học này sẽ giới thiệu thêm một số thư viện con hữu ích khác của STL, sẽ hỗ trợ rất tốt cho quá trình học lập trình C++ của bạn.
Để sử dụng thư viện STL, bạn cần khai báo không gian tên using namespace std; và sau đó sử dụng cú pháp #include <tên_thư_viện> để khai báo thư viện cần sử dụng. Mỗi phiên bản C++ có thể bổ sung thêm các template mới vào STL.
II. Sử dụng thư viện STL
1. Thư viện <vector>
(Mảng động)
Trong thư viện STL, <vector> là một trong số các cấu trúc lưu trữ (Containers Library). Tuy nhiên, trong bài học cơ bản, chúng ta chỉ giới thiệu vector vì nó sẽ đi kèm với các bạn trong quá trình học ngôn ngữ C++. Các cấu trúc lưu trữ khác sẽ được giới thiệu khi chúng ta tiếp cận các bài học cụ thể liên quan đến chúng.
1.1. Khai báo và truy cập phần tử
Vector là một kiểu dữ liệu mảng động trong STL, hỗ trợ người dùng lưu trữ các phần tử cùng kiểu dữ liệu. Tuy nhiên, vector khác với mảng thông thường vì nó rất linh hoạt và cung cấp nhiều phương thức hỗ trợ. Trong khi mảng thông thường phải khai báo trước số lượng phần tử cố định (gọi là mảng tĩnh), vector tự động cập nhật các vùng nhớ mới để chứa dữ liệu, giảm thiểu sự lãng phí bộ nhớ. Cú pháp khai báo vector như sau:
"{Kiểu phần tử}" trong trường hợp này là kiểu dữ liệu bất kỳ mà bạn muốn sử dụng, trong ví dụ dưới đây là số nguyên. "{Tên_vector}" là một định danh mà người dùng tự đặt. Dưới đây là một ví dụ về việc khai báo một vector chứa số nguyên:
Khi khai báo một vector mới, mặc định vector sẽ không chứa bất kỳ phần tử nào, trừ khi bạn khởi tạo giá trị cho nó trước. Bạn có thể khởi tạo số lượng vị trí cho vector và thiết lập giá trị ban đầu cho tất cả các vị trí đó bằng cách sử dụng cú pháp sau:
Các phần tử trong vector cũng được đánh số từ vị trí 0 trở đi. Dưới đây là ví dụ về việc khởi tạo một vector gồm 10 số 1:
Để truy cập vào một phần tử trong vector, ta sử dụng toán tử [] tương tự như mảng (tuy nhiên, phải đảm bảo rằng vị trí đó đã tồn tại trong vector). Ví dụ, cú pháp integer_list[5] sẽ truy cập vào phần tử ở vị trí thứ 5 trong vector.
1.2. Các hàm cung cấp sẵn của vector
Thư viện vector cung cấp một số hàm có sẵn rất hữu ích. Để sử dụng hàm này, ta sử dụng cú pháp {Tên_vector}.{Tên_hàm}. Có những hàm đứng riêng lẻ, và có những hàm được sử dụng cùng với các câu lệnh cụ thể. Bảng dưới đây liệt kê một số hàm phổ biến của vector và chức năng của chúng, kèm theo ví dụ về cách sử dụng:
1.3. Duyệt vector bằng chỉ số phần tử
Để duyệt qua các phần tử của vector, ta có thể sử dụng vòng lặp tương tự như khi làm việc với mảng. Giả sử vector a đã được khởi tạo với kích thước N. Vì vector được đánh số từ 0, các phần tử sẽ có chỉ số từ 0 đến N-1. Dựa vào điều đó, ta có thể duyệt qua các phần tử của vector bằng một vòng lặp qua các chỉ số như sau:
Ví dụ, để in ra các phần tử của vector a = {1, 2, 3, 4}, ta có thể sử dụng vòng lặp để duyệt qua từng phần tử và in giá trị của chúng. Dưới đây là một cách để làm điều đó:
Kết quả chạy chương trình:
1.4. Duyệt và truy cập vector bằng biến lặp (iterator)
Mỗi container trong STL đều hỗ trợ iterator (biến lặp) để duyệt qua và truy cập các phần tử, và đôi khi sử dụng để thao tác với các hàm thành viên của container. Cú pháp khai báo iterator như sau:
Ví dụ:
Khi đã khai báo, biến lặp chỉ có thể duyệt qua các phần tử có kiểu dữ liệu tương ứng đã khai báo. Cách khai báo biến lặp cho các containers khác trong STL hoàn toàn tương tự. Sau khi đã khai báo, ta có thể sử dụng vòng lặp để duyệt qua và in ra các phần tử bằng biến lặp, như sau:
Ngoài lệnh cout, biến lặp iterator cũng có thể được sử dụng kết hợp với các câu lệnh khác. Biến lặp iterator có thể sử dụng các phép toán như ++, --, !=, ==, = và +, - 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.
Dưới đây là một ví dụ cụ thể, chương trình sử dụng biến lặp iterator để duyệt qua tất cả các phần tử của một vector và in ra các phần tử đó:
Biên dịch và chạy chương trình trên sẽ cho ra kết quả:
1.5. 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 đã làm cho việc duyệt qua các phần tử trong vector trở nên đơn giản hơn rất nhiều. Ta có thể sử dụng một biến auto để duyệt qua toàn bộ các phần tử từ đầu đến cuối của vector bằng cú pháp:
Ví dụ:
Kết quả chạy chương trình:
Tuy nhiên, cách duyệt này có một nhược điểm là chỉ duyệt được từ đầu đến cuối của vector và không thể duyệt được một đoạn phần tử cụ thể trong vector. Vì vậy, cách duyệt này ít khi được sử dụng trong các trường hợp cần truy cập vào một đoạn phần tử cụ thể trong vector.
2. Kiểu pair
Thư viện <utility> trong STL cung cấp một kiểu dữ liệu rất hữu dụng là pair, cho phép ghép hai biến hoặc hằng thành một biến gồm hai trường giá trị, có thể có kiểu dữ liệu khác nhau. Để sử dụng, ta khai báo như sau:
Ví dụ, có thể khai báo một biến kiểu pair
lưu trữ hai thông tin về một sinh viên là mã số và tên bằng cách khai báo:
Sau khi khai báo, ta có thể truy cập vào hai trường giá trị của biến pair thông qua hai từ khóa first và second. Cú pháp truy cập như sau:
Sau khi truy cập vào các trường, ta có thể sử dụng kết hợp các câu lệnh với từng trường giống như một biến đơn.
Ngoài ra, chúng ta cũng có thể sử dụng pair như một kiểu dữ liệu cho các phần tử trong mảng hay vector. Pair có rất nhiều ứng dụng và hữu ích trong các bài toán khác nhau. Ở phần bài tập, chúng ta sẽ được tìm hiểu thêm về cách sử dụng pair và các vấn đề liên quan!
3. Algorithm Library (Thư viện thuật toán)
Thư viện thuật toán của STL chứa rất nhiều thuật toán viết sẵn từ đơn giản tới phức tạp. Để sử dụng thư viện này, trước tiên ta phải khai báo tên thư viện và không gian tên std:
Dưới đây mình sẽ giới thiệu vài thuật toán hữu ích và dễ sử dụng cho các bạn mới học C++.
3.1. Hàm tìm min - max giữa hai số
Cú pháp:
Hai hàm này vì trả về giá trị nên phải được sử dụng kết hợp với phép gán hoặc nằm trong một biểu thức hoặc câu lệnh khác. Đặc biệt, hai biến a và b phải có cùng kiểu dữ liệu để có thể sử dụng hai hàm này.
Ví dụ: