Skip to content

🔄 Modern Loops — Range-based For

C++11 giới thiệu range-based for loop — cách duyệt collections hiện đại, an toàn, và dễ đọc hơn C-style loops.

C-style Loop vs Range-based Loop

C-style Loop (Old Way)

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {10, 20, 30, 40, 50};
    
    // C-style: Dùng index
    for (size_t i = 0; i < numbers.size(); ++i) {
        std::cout << numbers[i] << " ";
    }
    std::cout << std::endl;
    
    // Iterator style (pre-C++11)
    for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Vấn đề với C-style:

  • 🔴 Dài dòng, dễ typo
  • 🔴 Off-by-one errors (< vs <=)
  • 🔴 Phải quản lý index/iterator manually
  • 🔴 Có thể truy cập out-of-bounds

Range-based For Loop (Modern Way)

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {10, 20, 30, 40, 50};
    
    // Range-based for (C++11)
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Ưu điểm:

  • ✅ Ngắn gọn, dễ đọc
  • ✅ Không cần quản lý index
  • ✅ Không thể out-of-bounds
  • ✅ Works với mọi container có begin()/end()

Các dạng Range-based For

1. By Value — Tạo copy

cpp
for (int num : numbers) {
    num *= 2;  // Chỉ modify bản copy!
    std::cout << num << " ";  // 20 40 60 80 100
}
// numbers vẫn là: 10 20 30 40 50 (unchanged!)

2. By Reference — Modify trực tiếp

cpp
for (int& num : numbers) {
    num *= 2;  // Modify element gốc!
}
// numbers bây giờ: 20 40 60 80 100

3. By Const Reference — Read-only, không copy

cpp
for (const int& num : numbers) {
    std::cout << num << " ";
    // num = 0;  // ❌ Error: cannot assign to const
}

📌 HPN Standard: Khi nào dùng gì?

SituationSyntaxReason
Read primitive (int, char, double)for (int x : arr)Copy nhỏ, không overhead
Read object lớn (string, struct)for (const auto& x : arr)Tránh copy tốn kém
Modify elementsfor (auto& x : arr)Cần thay đổi giá trị gốc

Rule of Thumb: Luôn dùng const auto& làm default cho read-only, auto& cho modification.


auto với Range-based For

Tự động suy luận type

cpp
#include <iostream>
#include <vector>
#include <string>
#include <map>

int main() {
    // Với vector<string>
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
    
    for (const auto& name : names) {  // auto = std::string
        std::cout << name << std::endl;
    }
    
    // Với map (pair<const Key, Value>)
    std::map<std::string, int> ages = {{"Alice", 25}, {"Bob", 30}};
    
    for (const auto& pair : ages) {  // auto = std::pair<const std::string, int>
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    
    return 0;
}

Structured Bindings (C++17)

cpp
#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> ages = {{"Alice", 25}, {"Bob", 30}};
    
    // C++17: Structured bindings — unpack pair automatically
    for (const auto& [name, age] : ages) {
        std::cout << name << " is " << age << " years old" << std::endl;
    }
    
    return 0;
}

Hoạt động với các Container khác

std::array

cpp
#include <iostream>
#include <array>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};
    
    for (const auto& x : arr) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

C-style Array

cpp
#include <iostream>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    
    // ✅ Works với C-style arrays (kích thước biết compile-time)
    for (int x : arr) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

std::string (iterate characters)

cpp
#include <iostream>
#include <string>

int main() {
    std::string text = "PENALGO";
    
    for (char c : text) {
        std::cout << c << "-";
    }
    std::cout << std::endl;  // P-E-N-A-L-G-O-
    
    return 0;
}

Initializer List

cpp
#include <iostream>

int main() {
    // Trực tiếp với initializer list!
    for (int x : {1, 2, 3, 4, 5}) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Khi nào KHÔNG nên dùng Range-based For?

1. Cần index

cpp
std::vector<int> v = {10, 20, 30};

// ❌ Range-based không có index
for (const auto& x : v) {
    // Không biết đang ở vị trí nào!
}

// ✅ Dùng C-style khi cần index
for (size_t i = 0; i < v.size(); ++i) {
    std::cout << "v[" << i << "] = " << v[i] << std::endl;
}

// ✅ Hoặc C++20: std::views::enumerate (nếu có)

2. Cần modify container structure

cpp
std::vector<int> v = {1, 2, 3, 4, 5};

// ❌ NGUY HIỂM: Xóa phần tử trong range-based for
for (auto& x : v) {
    // KHÔNG được erase/insert ở đây!
    // Invalidates iterators → Undefined Behavior
}

// ✅ Dùng iterator với erase-remove idiom
v.erase(std::remove_if(v.begin(), v.end(), 
    [](int x) { return x % 2 == 0; }), v.end());

3. Cần đi ngược hoặc nhảy bước

cpp
std::vector<int> v = {1, 2, 3, 4, 5};

// ❌ Range-based không hỗ trợ reverse/step
// for (auto& x : reverse(v)) { ... }  // Không có sẵn

// ✅ Dùng C-style hoặc reverse iterators
for (auto it = v.rbegin(); it != v.rend(); ++it) {
    std::cout << *it << " ";  // 5 4 3 2 1
}

Performance: Range-based vs C-style

cpp
#include <vector>

void c_style(std::vector<int>& v) {
    for (size_t i = 0; i < v.size(); ++i) {
        v[i] *= 2;
    }
}

void range_based(std::vector<int>& v) {
    for (auto& x : v) {
        x *= 2;
    }
}

// Kết quả: TƯƠNG ĐƯƠNG!
// Compiler optimize cả hai thành code giống nhau.
// Dùng range-based vì readable hơn, không sacrifice performance.

🎓 Professor Tom's Note

Range-based for được compiler translate thành iterator loop. Về mặt performance, không có sự khác biệt. Chọn style dựa trên readabilityintent.


📚 Tổng kết

PatternUsage
for (auto x : collection)Copy mỗi element (OK cho primitives nhỏ)
for (const auto& x : collection)Read-only, tránh copy objects lớn
for (auto& x : collection)Modify elements trực tiếp
for (const auto& [k, v] : map)C++17 structured bindings

Golden Rule: Mặc định dùng const auto&. Chỉ bỏ const khi cần modify.


➡️ Tiếp theo

Tiếp theo: Functions — Declaration, Definition, và Pass-by-value vs Pass-by-reference.