Giao diện
🔄 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 1003. 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ì?
| Situation | Syntax | Reason |
|---|---|---|
| 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 elements | for (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 readability và intent.
📚 Tổng kết
| Pattern | Usage |
|---|---|
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.