Iterators là một trong những khái niệm nền tảng nhất trong C++ STL. Chúng là những "con trỏ thông minh" cho phép bạn duyệt qua các phần tử của container mà không cần biết cấu trúc bên trong của container đó. Bài học này khám phá chi tiết về các loại iterator, cách chúng hoạt động, khi nào chúng bị vô hiệu, và cách sử dụng range-based loops để viết code sạch hơn.
1. Iterator là gì? (Generalized Pointers)
Iterator là một object hoạt động giống như con trỏ. Nó có thể:
- Trỏ tới một phần tử trong container
- Được dereference để lấy giá trị:
*iter - Di chuyển tới phần tử tiếp theo:
++iterhoặciter++ - So sánh với iterators khác:
iter1 == iter2
Hãy xem sự khác biệt giữa iterator và con trỏ thông thường:
#include <iostream>
#include <vector>
int main() {
// === Con trỏ thường (chỉ hoạt động với mảng thô) ===
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr;
std::cout << "Pointer to arr[0]: " << *ptr << "\n"; // 10
++ptr;
std::cout << "Pointer to arr[1]: " << *ptr << "\n"; // 20
// === Iterator (hoạt động với mọi container) ===
std::vector<int> vec = {10, 20, 30, 40, 50};
std::vector<int>::iterator it = vec.begin();
std::cout << "Iterator to vec[0]: " << *it << "\n"; // 10
++it;
std::cout << "Iterator to vec[1]: " << *it << "\n"; // 20
// === Iterator có thêm tính năng (ví dụ: kiểm tra end) ===
if (it != vec.end()) {
std::cout << "Iterator is valid\n";
}
// === Duyệt với iterator ===
std::cout << "All vector elements: ";
for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
std::cout << *iter << " ";
}
std::cout << "\n";
// === Iterator ngược ===
std::cout << "Reverse iteration: ";
for (auto rit = vec.rbegin(); rit != vec.rend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << "\n";
return 0;
}
Các hàm iterator cơ bản:
begin()- Iterator tới phần tử đầu tiênend()- Iterator tới vị trí sau phần tử cuối cùng (một phần tử quá cuối)rbegin()- Iterator ngược tới phần tử cuốirend()- Iterator ngược tới vị trí trước phần tử đầucbegin(),cend()- Const iterator (C++11)
2. Iterator Categories (5 Loại Iterator)
C++ STL định nghĩa 5 loại iterator theo khả năng của chúng. Mỗi loại hỗ trợ các phép toán khác nhau:
1) Input Iterator (Chỉ đọc, chỉ tiến)
Chỉ có thể đọc phần tử, chỉ có thể tiến. Ví dụ: istream_iterator.
#include <iostream>
#include <sstream>
#include <iterator>
int main() {
std::istringstream input("10 20 30 40 50");
// istream_iterator là input iterator
std::istream_iterator<int> iter(input);
std::istream_iterator<int> end;
std::cout << "Values from input stream: ";
while (iter != end) {
std::cout << *iter << " ";
++iter; // Chỉ có thể tiến, không thể lùi
}
std::cout << "\n";
return 0;
}
2) Output Iterator (Chỉ ghi, chỉ tiến)
Chỉ có thể ghi phần tử, chỉ có thể tiến. Ví dụ: ostream_iterator, back_inserter.
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> dest;
// back_inserter là output iterator
// Thêm từng phần tử vào cuối vector
std::copy(source.begin(), source.end(),
std::back_inserter(dest));
// Ghi sang output stream
std::copy(dest.begin(), dest.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << "\n";
return 0;
}
3) Forward Iterator (Đọc/ghi, chỉ tiến)
Có thể đọc và ghi, chỉ có thể tiến. Ví dụ: forward_list iterator.
#include <iostream>
#include <forward_list>
int main() {
std::forward_list<int> flist = {10, 20, 30, 40, 50};
// Forward iterator: đọc, ghi, chỉ tiến
for (auto it = flist.begin(); it != flist.end(); ++it) {
std::cout << *it << " ";
// ++it; OK - tiến
// --it; LỖI - không thể lùi trên forward_list
}
std::cout << "\n";
return 0;
}
4) Bidirectional Iterator (Đọc/ghi, cả hai hướng)
Có thể đọc, ghi, tiến và lùi. Ví dụ: list, map, set iterators.
5) Random Access Iterator (Đọc/ghi, truy cập tự do)
Có thể làm tất cả, cộng với truy cập O(1) bất kỳ vị trí nào. Ví dụ: vector, deque, string iterators.
Bảng so sánh Iterator Categories:
| Iterator Type | Read | Write | Forward | Backward | Random Access | Containers |
|---|---|---|---|---|---|---|
| Input | ✓ | ✗ | ✓ | ✗ | ✗ | istream_iterator |
| Output | ✗ | ✓ | ✓ | ✗ | ✗ | back_inserter, ostream_iterator |
| Forward | ✓ | ✓ | ✓ | ✗ | ✗ | forward_list |
| Bidirectional | ✓ | ✓ | ✓ | ✓ | ✗ | list, map, set |
| Random Access | ✓ | ✓ | ✓ | ✓ | ✓ | vector, deque, string, array |
Nhận xét: Mỗi loại iterator cao hơn có thể làm được tất cả những gì loại iterator thấp hơn có thể làm.
3. Iterator Invalidation (Khi Iterator bị vô hiệu)
Một trong những "hố tử" phổ biến nhất trong C++ là sử dụng iterator sau khi nó bị vô hiệu. Điều này tùy thuộc vào loại container:
vector: Iterator bị vô hiệu khi reallocate bộ nhớ
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();
std::cout << "Original iterator points to: " << *it << "\n"; // 1
// push_back có thể gây reallocate!
vec.push_back(6);
// Nếu reallocate xảy ra, iterator bây giờ vô hiệu
// Dereference iterator vô hiệu => Undefined Behavior!
// std::cout << *it << "\n"; // ❌ DANGER!
// ✓ Cách an toàn: Lấy iterator mới sau push_back
vec.push_back(7);
it = vec.begin(); // Reassign
std::cout << "New iterator points to: " << *it << "\n"; // 1
return 0;
}
list: Iterator chỉ vô hiệu khi erase element đó
#include <iostream>
#include <list>
int main() {
std::list<int> lst = {1, 2, 3, 4, 5};
auto it = lst.begin();
std::advance(it, 2); // it -> 3
// insert/push_back không làm vô hiệu iterator
lst.push_back(6);
std::cout << "After push_back: " << *it << "\n"; // 3 (vẫn hợp lệ!)
// Nhưng erase element mà iterator chỉ tới WILL invalidate it
auto to_erase = it;
++it; // Tìm element tiếp theo
lst.erase(to_erase);
// Nếu không làm ++it trước, iterator sẽ vô hiệu
std::cout << "After erase: " << *it << "\n"; // 4 (vẫn hợp lệ!)
return 0;
}
Safe Erase Pattern:
// ❌ DANGER - iterator bị vô hiệu sau erase
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it == 3) {
vec.erase(it); // Iterator bây giờ vô hiệu!
}
}
// ✓ SAFE - erase trả về iterator hợp lệ tiếp theo
for (auto it = vec.begin(); it != vec.end(); ) {
if (*it == 3) {
it = vec.erase(it); // erase trả về iterator tiếp theo
} else {
++it;
}
}
// ✓ MODERN (C++20) - std::erase
std::erase(vec, 3); // Xóa tất cả 3 (nếu hỗ trợ)
4. Range-based Loops (C++11+)
C++11 giới thiệu range-based for loops, loại bỏ nhu cầu quản lý iterator thủ công. Chúng tự động xử lý iterator:
#include <iostream>
#include <vector>
#include <map>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// ❌ Cũ: Iterator thủ công
std::cout << "Manual iterator: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
// ✓ Mới: Range-based loop
std::cout << "Range-based loop: ";
for (int val : vec) {
std::cout << val << " ";
}
std::cout << "\n";
// ✓ Thay đổi giá trị bằng reference
for (int& val : vec) {
val *= 2; // Nhân đôi mỗi phần tử
}
std::cout << "After doubling: ";
for (int val : vec) {
std::cout << val << " ";
}
std::cout << "\n";
// ✓ Duyệt map (C++17 Structured Bindings)
std::map<std::string, int> dict = {{"apple", 5}, {"banana", 3}};
for (const auto& [key, value] : dict) {
std::cout << key << " -> " << value << "\n";
}
// ✓ Const duyệt (không sửa)
for (const int& val : vec) {
std::cout << val << " ";
}
std::cout << "\n";
return 0;
}
Lợi ích của Range-based Loops:
- Code sạch hơn, dễ đọc hơn
- Ít bugs iterator hơn
- Tự động hoạt động với mảy container STL
- Hỗ trợ structured bindings (C++17) cho pairs/tuples
5. Advanced Iterator Techniques
std::advance() và std::distance()
Sử dụng std::advance() để di chuyển iterator, và std::distance() để tính
khoảng cách giữa hai iterators:
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
int main() {
std::vector<int> vec = {10, 20, 30, 40, 50};
// ===== std::advance() =====
auto it = vec.begin();
std::advance(it, 2); // Di chuyển 2 vị trí
std::cout << "After advance by 2: " << *it << "\n"; // 30
// ===== std::distance() =====
auto first = vec.begin();
auto last = vec.end();
int dist = std::distance(first, last);
std::cout << "Distance from begin to end: " << dist << "\n"; // 5
// ===== Cẩn thận với list =====
std::list<int> lst = {10, 20, 30, 40, 50};
auto lit = lst.begin();
// Trên vector: O(1). Trên list: O(n)!
std::advance(lit, 4);
std::cout << "List element at 4: " << *lit << "\n"; // 50
return 0;
}
Iterator Adapters: reverse_iterator & back_inserter
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// ===== reverse_iterator =====
std::cout << "Reverse with reverse_iterator: ";
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
// ===== back_inserter =====
std::vector<int> dest;
std::copy(vec.begin(), vec.end(), std::back_inserter(dest));
std::cout << "Copied to dest: ";
for (int x : dest) {
std::cout << x << " ";
}
std::cout << "\n";
// ===== front_inserter (list only) =====
std::list<int> lst;
std::copy(vec.begin(), vec.end(), std::front_inserter(lst));
std::cout << "Inserted to list front: ";
for (int x : lst) {
std::cout << x << " ";
}
std::cout << "\n";
// ===== inserter =====
std::vector<int> vec2 = {100, 200};
std::copy(vec.begin(), vec.begin() + 2,
std::inserter(vec2, vec2.begin() + 1));
std::cout << "After inserter: ";
for (int x : vec2) {
std::cout << x << " ";
}
std::cout << "\n";
return 0;
}
Custom Iterators cho User-Defined Classes
Bạn có thể tạo iterator cho class riêng bằng cách overload các phép toán cần thiết:
#include <iostream>
#include <vector>
class SimpleArray {
private:
std::vector<int> data;
public:
SimpleArray(const std::vector<int>& d) : data(d) {}
// Iterator class
class Iterator {
private:
std::vector<int>::iterator it;
public:
Iterator(std::vector<int>::iterator i) : it(i) {}
// Dereference
int& operator*() { return *it; }
// Increment
Iterator& operator++() { ++it; return *this; }
Iterator operator++(int) {
Iterator temp = *this; ++it; return temp;
}
// Comparison
bool operator!=(const Iterator& other) const {
return it != other.it;
}
bool operator==(const Iterator& other) const {
return it == other.it;
}
};
Iterator begin() { return Iterator(data.begin()); }
Iterator end() { return Iterator(data.end()); }
};
int main() {
SimpleArray arr({10, 20, 30, 40, 50});
std::cout << "Custom iterator: ";
for (int val : arr) {
std::cout << val << " ";
}
std::cout << "\n";
return 0;
}
6. Iterator Debugging & Safety
Common Iterator Bugs và cách tránh:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// ❌ BUG 1: Sử dụng iterator sau erase
auto it = vec.begin();
std::advance(it, 2); // it -> 3
vec.erase(it);
// ++it; // ❌ Undefined behavior
// ✓ FIX: Lấy iterator mới từ erase
vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ) {
if (*it == 3) {
it = vec.erase(it); // ✓ erase trả về iterator tiếp theo
} else {
++it;
}
}
// ❌ BUG 2: Iterator từ container khác
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
auto it1 = vec1.begin();
// vec2.erase(it1); // ❌ CRASH - iterator từ vec1!
// ✓ FIX: Sử dụng iterator từ container đúng
vec2.erase(vec2.begin());
// ❌ BUG 3: Dereference out-of-range iterator
vec = {1, 2, 3};
auto it3 = vec.end();
// std::cout << *it3; // ❌ Undefined - end() không chỉ tới element
// ✓ FIX: Kiểm tra trước khi dereference
if (it3 != vec.begin()) {
--it3;
std::cout << "Last element: " << *it3 << "\n";
}
return 0;
}
7. Comprehensive Example: All Iterator Features
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
std::cout << "=== Original Vector ===\n";
for (int n : numbers) std::cout << n << " ";
std::cout << "\n\n";
// Sort
std::sort(numbers.begin(), numbers.end());
std::cout << "=== After Sorting ===\n";
for (int n : numbers) std::cout << n << " ";
std::cout << "\n\n";
// Find
auto found = std::find(numbers.begin(), numbers.end(), 5);
if (found != numbers.end()) {
std::cout << "Found 5 at position: "
<< std::distance(numbers.begin(), found) << "\n\n";
}
// Transform (nhân đôi mỗi element)
std::vector<int> doubled;
std::transform(numbers.begin(), numbers.end(),
std::back_inserter(doubled),
[](int x) { return x * 2; });
std::cout << "=== Doubled Elements ===\n";
for (int n : doubled) std::cout << n << " ";
std::cout << "\n\n";
// Copy to list với reverse iterator
std::list<int> lst;
std::copy(numbers.rbegin(), numbers.rend(),
std::back_inserter(lst));
std::cout << "=== List (Reversed Copy) ===\n";
for (int n : lst) std::cout << n << " ";
std::cout << "\n";
return 0;
}
8. Quiz: Iterator Invalidation
Câu hỏi: Cho đoạn code sau, khi nào iterator bị vô hiệu?
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();
std::advance(it, 2); // it -> 3
vec.insert(vec.begin() + 1, 99); // Insert 99 at position 1
// Hỏi: *it vẫn hợp lệ không? Nếu vẫn hợp lệ, nó chỉ tới phần tử nào?
Trả lời: Iterator bị vô hiệu! Khi insert vào vector, tất cả iterators (ngoại trừ
begin()) có thể bị vô hiệu nếu reallocate xảy ra. Ngay cả khi không reallocate, iterators chỉ tới hoặc
sau điểm insert cũng bị invalidate.
Cách tránh: Cấp lại iterator sau insert: it = vec.begin();
std::advance(it, 3); // Vị trí mới của 3 sau insert
Tải mã nguồn mẫu bài học
Bạn có thể tải các file mã nguồn C++ mẫu hoàn chỉnh của bài học này để tiến hành thực hành trực tiếp trên máy tính cá nhân.
Tải iterators_demo.cppThis programming guide is only available in Vietnamese. Switch to Vietnamese to read the full article.
Comments
Bình luận