0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Vector trong C++: Mảng động thông minh

Đăng vào 1 tháng trước

• 6 phút đọc

Khi lập trình trong C++, một trong những cấu trúc dữ liệu phổ biến nhất bạn sẽ gặp là vector. Nó hoạt động giống như một mảng nhưng với sự linh hoạt tại thời gian chạy — có thể tăng hoặc giảm kích thước một cách động, khiến nó trở thành một trong những công cụ mạnh mẽ nhất trong Thư viện Mẫu Chuẩn (STL).

Trong hướng dẫn này, chúng ta sẽ đề cập đến:

  • Các khái niệm cơ bản về Vector và STL
  • Các phương pháp khởi tạo
  • Các hàm và thao tác phổ biến
  • Phân bổ bộ nhớ tĩnh và động
  • Sự khác biệt giữa mảng và vector
  • Cách vector quản lý bộ nhớ
  • Iterators
  • Vector 2D
  • Các yếu tố hiệu suất
  • So sánh với các container khác
  • Tự động dọn dẹp bộ nhớ

1. STL và Vectors

Thư viện Mẫu Chuẩn (STL) cung cấp các container và thuật toán sẵn có. Một số container thường được sử dụng:

  • vector → Mảng động
  • queue → FIFO
  • stack → LIFO
  • set → Các phần tử duy nhất, có thứ tự

👉 Lưu ý biên dịch: luôn sử dụng ít nhất C++11:

cpp Copy
 g++ -std=c++11 code.cpp -o runfile && ./runfile

Nếu bạn quên -std=c++11, bạn có thể gặp lỗi biên dịch hoặc các vấn đề không mong muốn trong thời gian chạy.

2. Khởi tạo Vector

Có nhiều cách để khởi tạo std::vector:

cpp Copy
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // Khởi tạo trực tiếp (C++11)
    vector<int> v1 = {1, 2, 3};

    // Khởi tạo bằng constructor (kích thước, giá trị khởi tạo)
    vector<int> v2(3, 0); // [0, 0, 0]

    // Khởi tạo trống rồi thêm phần tử
    vector<int> v3;
    v3.push_back(10);
    v3.push_back(20);

    // In ra sử dụng vòng lặp range-for
    for (int x : v1) cout << x << " ";
    cout << endl;
}

Kết quả:

plaintext Copy
1 2 3

3. Các hàm và thao tác của Vector

Các thao tác phổ biến với vector:

  • Thêm / Xóa

    • push_back(x) → thêm vào cuối
    • pop_back() → xóa phần tử cuối
  • Truy cập

    • v[i] → phần tử theo chỉ số (không kiểm tra giới hạn)
    • v.at(i) → truy cập có kiểm tra giới hạn (ném out_of_range)
    • front() / back()
  • Thông tin

    • size() → số lượng phần tử
    • capacity() → kích thước bộ nhớ đã cấp phát
    • empty() → kiểm tra xem có rỗng hay không

Ví dụ:

cpp Copy
vector<int> v = {10, 20, 30};
v.push_back(40);  // [10,20,30,40]
v.pop_back();     // [10,20,30]

cout << v.front() << endl;    // 10
cout << v.back() << endl;     // 30
cout << v.at(1) << endl;      // 20
cout << v.size() << endl;     // 3
cout << v.capacity() << endl; // capacity >= 3

4. Phân bổ bộ nhớ tĩnh vs động

Mảng tĩnh

cpp Copy
int arr[5]; // kích thước cố định tại thời gian biên dịch
  • Thường được lưu trữ trên stack
  • Kích thước cứng nhắc, không thể thay đổi

Mảng động (thủ công)

cpp Copy
int* arr = new int[5];
delete[] arr;
  • Được lưu trữ trên heap
  • Cần quản lý bộ nhớ thủ công

Vectors sử dụng bộ nhớ động nhưng quản lý tự động, làm cho chúng an toàn và linh hoạt hơn so với các mảng động thô.

5. Sự khác biệt giữa Mảng và Vector

Tính năng Mảng (Tĩnh) Vector (Động)
Kích thước Cố định tại thời gian biên dịch Có thể tăng/giảm tại thời gian chạy
Vị trí bộ nhớ Stack (thường) Heap (quản lý nội bộ)
Quản lý bộ nhớ Thủ công (nếu động) Tự động
Các hàm có sẵn Không (chỉ truy cập thô) API phong phú: push_back, at, size...
Khởi tạo Hình thức hạn chế Constructor linh hoạt
An toàn Không kiểm tra giới hạn at() cung cấp truy cập có kiểm tra giới hạn

6. Cách Vectors hoạt động trong bộ nhớ

vector duy trì hai khái niệm cốt lõi:

  • size → số phần tử đã lưu
  • capacity → không gian lưu trữ đã cấp phát (có thể lớn hơn hoặc bằng kích thước)

Khi capacity bị vượt quá, các triển khai thường tăng capacity (thường bằng cách gấp đôi), điều này giúp giảm chi phí thêm.

Ví dụ:

cpp Copy
vector<int> v;
for (int i = 0; i < 10; i++) {
    v.push_back(i);
    cout << "Kích thước: " << v.size() << "
" " Capacity: " << v.capacity() << endl;
}

Kết quả điển hình (tùy thuộc vào triển khai):

plaintext Copy
Kích thước: 1 Capacity: 1
Kích thước: 2 Capacity: 2
Kích thước: 3 Capacity: 4
Kích thước: 5 Capacity: 8
Kích thước: 9 Capacity: 16

Sử dụng reserve(n) để cấp phát bộ nhớ trước nếu bạn biết kích thước dự kiến — điều này giúp tránh việc cấp phát lại nhiều lần.

7. Iterators

Iterators hoạt động giống như con trỏ và được sử dụng để duyệt qua các container:

cpp Copy
vector<int> v = {10,20,30,40};

// Duyệt tới trước
for (auto it = v.begin(); it != v.end(); ++it) cout << *it << " ";

// Duyệt ngược
for (auto it = v.rbegin(); it != v.rend(); ++it) cout << *it << " ";

Kết quả:

plaintext Copy
10 20 30 40
40 30 20 10

Iterators là cách phổ biến để sử dụng các thuật toán STL như std::sort, std::find, v.v.

8. Vector 2D

Các vector lồng nhau rất hữu ích cho ma trận, đồ thị và lưu trữ 2D động:

cpp Copy
vector<vector<int>> matrix(3, vector<int>(3, 0));
matrix[0][1] = 5;
matrix[2][2] = 7;

for (auto& row : matrix) {
    for (auto val : row) cout << val << " ";
    cout << endl;
}

Kết quả:

plaintext Copy
0 5 0
0 0 0
0 0 7

9. Mẹo hiệu suất

  • size() là O(1). Sử dụng nó một cách thoải mái.
  • capacity() cho thấy bộ nhớ đã được dự trữ; sử dụng reserve(n) khi bạn biết kích thước dự kiến.
  • Tránh sao chép quá mức — ưu tiên emplace_back() để xây dựng tại chỗ.
  • Truy cập ngẫu nhiên là O(1); việc thêm/xóa ở giữa là O(n).
  • Đối với việc thêm vào đầu thường xuyên, hãy xem xét deque; đối với việc thêm/xóa giữa thường xuyên, hãy xem xét list (nhưng danh sách có độ địa phương bộ nhớ kém).

10. So sánh Vector với các Container khác

  • Vector → tốt nhất cho truy cập ngẫu nhiên, việc thêm vào cuối nhanh chóng.
  • Deque → nhanh chóng thêm vào cả hai đầu.
  • List → nhanh chóng thêm/xóa ở giữa (không có truy cập ngẫu nhiên).
  • Set → các phần tử duy nhất, có thứ tự (hoạt động logarithmic).

👉 Lựa chọn mặc định: vector trừ khi bạn có lý do cụ thể để chọn container khác.

11. Tự động dọn dẹp bộ nhớ

Vectors tự động giải phóng bộ nhớ đã quản lý khi chúng ra khỏi phạm vi:

cpp Copy
void func() {
    vector<int> v(1000, 1);
} // bộ nhớ được giải phóng khi hàm trả về

✅ Kết luận

std::vector giống như mảng nhưng linh hoạt, an toàn (với at()) và hiệu quả cho hầu hết các nhu cầu sử dụng chung. Ưu tiên sử dụng vector thay vì các mảng thô trừ khi bạn cần một bộ đệm có kích thước cố định, được cấp phát trên stack hoặc có các yêu cầu hiệu suất rất cụ thể.

Câu hỏi thường gặp

1. Vector có thể chứa các kiểu dữ liệu khác nhau không?
Không, vector trong C++ chỉ có thể chứa các phần tử cùng kiểu dữ liệu.

2. Làm thế nào để xóa một phần tử khỏi vector?
Sử dụng phương thức erase() để xóa một phần tử tại chỉ số cụ thể.

3. Vector có thể chứa các vector khác không?
Có, bạn có thể tạo vector 2D bằng cách sử dụng vector<vector<T>>.

4. Làm thế nào để kiểm tra xem một vector có rỗng không?
Sử dụng phương thức empty() để kiểm tra.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào