
Chào các chiến thần code tương lai, anh Creyt đây!
Hôm nay chúng ta sẽ "mổ xẻ" một khái niệm "hot hit" mà đứa nào làm C++ cũng phải biết: std::vector. Nghe tên thì có vẻ "khoa học viễn tưởng" nhưng thực ra nó là "người hùng thầm lặng" giúp code của tụi em linh hoạt hơn rất nhiều đấy.
1. std::vector là gì mà "ghê gớm" vậy?
Thôi, nói thẳng toẹt ra cho Gen Z dễ hiểu: Tụi em cứ hình dung std::vector nó giống như cái "tủ quần áo thần kỳ" của tụi mình vậy.
- Mảng truyền thống (C-style array) thì giống cái tủ đóng sẵn, kích thước cố định. Mua thêm đồ là hết chỗ, phải đi mua cái tủ mới to hơn, rồi bê hết đồ sang tủ mới. Phiền phức không?
- Còn
std::vector? Nó là cái tủ "biến hình" được! Lúc đầu chỉ có vài ngăn, nhưng khi em mua thêm áo quần, nó tự động nới rộng ra, thêm ngăn mới mà em không cần phải lo nghĩ gì cả. Nó "tự động co giãn" theo nhu cầu của em. Quá tiện đúng không?
Tóm lại: std::vector là một mảng động (dynamic array) trong C++. Nó cho phép em lưu trữ một danh sách các phần tử cùng kiểu dữ liệu (ví dụ: một danh sách các số nguyên, các chuỗi, hoặc các đối tượng của em), mà quan trọng nhất là kích thước của nó có thể thay đổi trong quá trình chạy chương trình. Nó tự động quản lý bộ nhớ cho em, không cần em phải new hay delete thủ công như mảng C truyền thống.
2. Dùng để làm gì? "Khi nào thì cần cái tủ biến hình này?"
Em sẽ cần std::vector khi:
- Không biết trước số lượng phần tử: Em muốn lưu danh sách bạn bè nhưng không biết mình có bao nhiêu đứa bạn.
vectorcân tất! - Cần thêm/bớt phần tử liên tục: Giỏ hàng online của em, lúc thêm món, lúc bớt món.
vectorxử lý ngon ơ. - Truy cập nhanh theo chỉ số: Em muốn lấy phần tử thứ 5 trong danh sách.
vectorcho phép truy cập ngẫu nhiên (random access) cực nhanh, giống như mảng truyền thống vậy. - Cần các phần tử được lưu trữ "sát vách" nhau: Điều này cực kỳ quan trọng cho hiệu suất khi xử lý dữ liệu lớn, vì nó tối ưu việc truy cập bộ nhớ.

3. Code Ví Dụ Minh Họa (Thực Chiến Luôn!)
Anh em mình cùng xem vector hoạt động như thế nào qua mấy ví dụ "chuẩn chỉ" sau đây nhé:
#include <iostream> // Để dùng cout
#include <vector> // Đừng quên include thư viện vector nha!
#include <string> // Để dùng string
#include <algorithm> // Để dùng sort (ví dụ thêm)
int main() {
// Khởi tạo một vector rỗng chứa các số nguyên
std::vector<int> diemSo;
std::cout << "Kich thuoc ban dau cua diemSo: " << diemSo.size() << std::endl; // Output: 0
// Thêm phần tử vào cuối vector (như thêm quần áo vào tủ)
diemSo.push_back(90);
diemSo.push_back(85);
diemSo.push_back(95);
diemSo.push_back(70);
std::cout << "Kich thuoc sau khi them: " << diemSo.size() << std::endl; // Output: 4
// Truy cập phần tử theo chỉ số (giống mảng truyền thống)
// Chỉ số bắt đầu từ 0
std::cout << "Diem so dau tien: " << diemSo[0] << std::endl; // Output: 90
std::cout << "Diem so thu ba: " << diemSo.at(2) << std::endl; // Output: 95 (at() an toàn hơn, kiểm tra lỗi out of bounds)
// Duyệt qua các phần tử của vector (như xem từng món đồ trong tủ)
std::cout << "\nTat ca diem so: ";
for (int diem : diemSo) { // Range-based for loop - phong cách Gen Z
std::cout << diem << " ";
}
std::cout << std::endl;
// Xóa phần tử cuối cùng (bỏ bớt món đồ không thích)
diemSo.pop_back(); // Xóa 70
std::cout << "Kich thuoc sau khi xoa cuoi: " << diemSo.size() << std::endl; // Output: 3
// Xóa một phần tử bất kỳ (khó hơn một chút)
// Muốn xóa phần tử thứ hai (giá trị 85, chỉ số 1)
diemSo.erase(diemSo.begin() + 1); // begin() là iterator trỏ đến phần tử đầu tiên
std::cout << "Diem so sau khi xoa phan tu thu hai: ";
for (int diem : diemSo) {
std::cout << diem << " ";
}
std::cout << std::endl; // Output: 90 95
// Sắp xếp vector (nếu cần)
std::sort(diemSo.begin(), diemSo.end());
std::cout << "Diem so sau khi sap xep: ";
for (int diem : diemSo) {
std::cout << diem << " ";
}
std::cout << std::endl; // Output: 90 95
// Xóa tất cả phần tử (dọn sạch tủ)
diemSo.clear();
std::cout << "Kich thuoc sau khi clear: " << diemSo.size() << std::endl; // Output: 0
// Vector chứa các chuỗi (string)
std::vector<std::string> tenMonHoc = {"Toan", "Ly", "Hoa"};
tenMonHoc.push_back("Tin Hoc");
std::cout << "\nCac mon hoc: ";
for (const std::string& mon : tenMonHoc) {
std::cout << mon << " ";
}
std::cout << std::endl;
return 0;
}
4. Mẹo (Best Practices) để "thuần hóa" vector như dân chuyên
Để dùng vector hiệu quả như một pro-gamer, nhớ mấy "chiêu" này nhé:
- Dùng
reserve()để tránh "tái cấu trúc tủ" liên tục: Khivectorhết chỗ, nó phải tạo một vùng nhớ mới lớn hơn, copy toàn bộ dữ liệu cũ sang rồi xóa vùng nhớ cũ. Việc này tốn thời gian. Nếu em biết trước (hoặc ước lượng được) số lượng phần tử tối đa, hãy dùngvector.reserve(so_luong_uoc_tinh);ngay từ đầu. Nó sẽ cấp phát sẵn bộ nhớ, giúppush_backchạy nhanh hơn nhiều, tránh được các lần tái cấp phát không cần thiết. Giống như mua cái tủ to ngay từ đầu đỡ phải đổi tủ vậy. push_backlà "best friend" của em: Thêm phần tử vào cuốivectorlà thao tác hiệu quả nhất (thường là O(1) trung bình, còn gọi là "amortized constant time"). Hạn chế dùnginsert()ở giữavectorvì nó phải "xê dịch" tất cả các phần tử phía sau, rất tốn kém (O(n)).- Duyệt bằng
range-based for loop: Như ví dụ trên, nó vừa ngắn gọn, vừa dễ đọc, lại ít sai sót hơn so với vòng lặpfortruyền thống với chỉ số. - Cẩn thận với
iterator invalidation: Khivectorbị tái cấp phát (dopush_backlàm đầy hoặcerase/insertở giữa), cáciterator(con trỏ đặc biệt dùng để duyệt) mà em đang giữ có thể bị "hỏng" (trỏ vào vùng nhớ không còn hợp lệ). Luôn lấy lạiiteratorsau các thao tác thay đổi cấu trúcvector(nhưpush_backkhi đầy,erase,insert). - Kiểm tra
empty()trước khi truy cập: Tránh lỗi truy cập vàovectorrỗng bằngif (!myVector.empty())hoặcif (myVector.size() > 0). Dùngat()thay vì[]nếu em muốn hệ thống tự động kiểm tra lỗiout of boundsvà ném ra ngoại lệ.
5. vector xuất hiện ở đâu trong thế giới thực?
std::vector không chỉ là lý thuyết suông đâu, nó "phủ sóng" khắp mọi nơi trong các ứng dụng thực tế:
- Mạng xã hội: Danh sách bạn bè, danh sách các bài đăng (posts) trên feed của em.
- Trình duyệt web: Lịch sử duyệt web (các URL em đã truy cập).
- Thương mại điện tử: Giỏ hàng của em, danh sách sản phẩm gợi ý.
- Game: Danh sách các đối tượng trong màn chơi (kẻ thù, item, đạn), danh sách các điểm ảnh (pixels) trong một hình ảnh.
- Hệ thống quản lý dữ liệu: Lưu trữ các bản ghi tạm thời trước khi ghi vào database.
6. Thử nghiệm và Nên dùng cho Case nào?
Với kinh nghiệm "chinh chiến" qua bao dự án, anh Creyt đúc kết được thế này:
- Khi nào nên dùng:
std::vectorlà lựa chọn mặc định "an toàn" và hiệu quả nhất cho hầu hết các trường hợp cần một tập hợp các phần tử có thứ tự và khả năng thay đổi kích thước. Đặc biệt khi em cần truy cập phần tử nhanh chóng theo chỉ số (O(1)) và thêm/bớt ở cuối (O(1) trung bình). - Khi nào nên cân nhắc alternatives (thay thế khác):
- Nếu em cần thêm/bớt phần tử rất thường xuyên ở đầu hoặc giữa danh sách (mà không phải ở cuối),
std::listhoặcstd::dequecó thể là lựa chọn tốt hơn vì chúng hiệu quả hơn cho các thao tác này (O(1) thay vì O(n) củavector). Nhưng hãy nhớ, chúng không hỗ trợ truy cập ngẫu nhiên nhanh nhưvector. - Nếu kích thước danh sách không bao giờ thay đổi sau khi khởi tạo và em biết chính xác số lượng phần tử,
std::array(cho kích thước cố định compile-time) hoặc mảng C truyền thống có thể có hiệu suất tốt hơn một chút (ít overhead hơnvector).
- Nếu em cần thêm/bớt phần tử rất thường xuyên ở đầu hoặc giữa danh sách (mà không phải ở cuối),
Nói chung, std::vector là một công cụ cực kỳ mạnh mẽ và linh hoạt. Hãy nắm vững nó, và em sẽ có trong tay một "trợ thủ đắc lực" để giải quyết vô vàn bài toán lập trình. Cứ thực hành nhiều vào, rồi em sẽ thấy nó "ngon" như thế nào!
Chúc các em code vui vẻ và luôn "biến hình" linh hoạt như vector nhé!
Thuộc Series: C++
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!