Kiểm soát luồng & vòng lặp
Nắm vững tất cả các cấu trúc luồng điều khiển C++ bao gồm if/else, switch, tất cả các dạng vòng lặp, dựa trên phạm vi cho, bộ khởi tạo C++17 và các liên kết có cấu trúc.
Luồng điều khiển trong C++
Control flow statements determine the order in which your program executes. C++ provides a rich set of control flow constructs that go well beyond the basics inherited from C. Modern C++ (C++17 and beyond) adds features likeifwith initializers andconstexpr ifthat make control flow more expressive and safer.
A key principle: minimize the scope of variables. Every variable should be declared as close to its first use as possible, and in the narrowest scope possible. C++17 features likeifwith initializers help you follow this principle.
nếu/khác và chuyển đổi
Các câu lệnhifvàswitchlà các cơ chế phân nhánh chính. C++17 thêm mệnh đề khởi tạo cho cả hai:
#include <iostream>
#include <map>
#include <string>
#include <optional>
std::optional<int> find_value(const std::string& key) {
std::map<std::string, int> data = {{"alpha", 1}, {"beta", 2}, {"gamma", 3}};
if (auto it = data.find(key); it != data.end()) { // C++17 if with initializer
return it->second;
}
return std::nullopt;
}
int main() {
// C++17: if with initializer — the variable's scope is limited to the if/else
if (auto val = find_value("beta"); val.has_value()) {
std::cout << "Found: " << *val << '\n';
} else {
std::cout << "Not found\n";
}
// 'val' is no longer in scope here — cleaner!
// switch with C++17 initializer
enum class Color { Red, Green, Blue };
Color c = Color::Green;
switch (auto name = "unknown"; c) { // C++17 switch with initializer
case Color::Red: name = "red"; std::cout << name << '\n'; break;
case Color::Green: name = "green"; std::cout << name << '\n'; break;
case Color::Blue: name = "blue"; std::cout << name << '\n'; break;
}
// [[fallthrough]] attribute (C++17) — document intentional fallthrough
int priority = 2;
switch (priority) {
case 1:
std::cout << "Critical: ";
[[fallthrough]]; // Tells the compiler this is intentional
case 2:
std::cout << "Important: ";
[[fallthrough]];
case 3:
std::cout << "Handle it\n";
break;
default:
std::cout << "Unknown priority\n";
}
}Nhấn Chạy để biên dịch và chạy ví dụ này.
Vòng lặp: for, while, do-while và dựa trên phạm vi cho
C++ cung cấp nhiều dạng vòng lặp. Vòng lặp dựa trên phạm vi (C++11) được ưu tiên khi lặp qua các vùng chứa vì nó loại bỏ từng lỗi một và rõ ràng hơn:
#include <iostream>
#include <vector>
#include <string>
#include <map>
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// Traditional for loop — use when you need the index
for (std::size_t i = 0; i < names.size(); ++i) {
std::cout << i << ": " << names[i] << '\n';
}
// Range-based for loop (C++11) — preferred for iteration
for (const auto& name : names) { // const auto& avoids copying
std::cout << name << '\n';
}
// Range-based for with structured bindings (C++17)
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};
for (const auto& [name, score] : scores) { // Destructure the pair
std::cout << name << " scored " << score << '\n';
}
// C++20: range-based for with init-statement
for (auto v = std::vector{1, 2, 3, 4, 5}; auto elem : v) {
std::cout << elem << ' ';
}
std::cout << '\n';
// while loop — use when the number of iterations is unknown
int n = 1;
while (n <= 1024) {
std::cout << n << ' ';
n *= 2;
}
std::cout << '\n';
// do-while — body executes at least once
int input;
do {
std::cout << "Enter a positive number: ";
input = 42; // Simulated input for example
} while (input <= 0);
}Nhấn Chạy để biên dịch và chạy ví dụ này.
Ràng buộc có cấu trúc (C++17)
Các liên kết có cấu trúc (C++17) cho phép bạn phân tách một đối tượng thành các phần cấu thành của nó. Chúng hoạt động với mảng, cấu trúc với tất cả thành viên công cộng,std::pair,std::tuplevà bất kỳ loại nào hỗ trợ giao thức liên kết có cấu trúc.
Cú phápauto [a, b] = expr;tạo các tham chiếu được đặt tên cho các thành phần củaexpr. Bạn có thể thêm bộ định tínhconstvà&:const auto& [a, b] = expr;tạo tham chiếu const mà không cần sao chép.
Các liên kết có cấu trúc đặc biệt mạnh mẽ với phép lặpstd::map(phân táchstd::pair), hàm trả về sử dụngstd::pairhoặcstd::tuplevà giá trị trả về của phương thứcinserttrên các vùng chứa kết hợp.
Sử dụngbreakđể sớm thoát khỏi vòng lặp khi bạn đã tìm thấy thứ mình cần. Sử dụngcontinueđể bỏ qua phần còn lại của lần lặp hiện tại và chuyển sang lần lặp tiếp theo. Cả hai đều làm cho vòng lặp dễ hiểu hơn khi được sử dụng một cách thận trọng.
Ưu tiên dựa trên phạm vi hơn dựa trên chỉ mục khi bạn không cần chỉ mục. Nó loại bỏ toàn bộ các loại lỗi: từng lỗi một, sử dụng sai biến làm chỉ mục và các cảnh báo so sánh có dấu/không dấu.
Khi bạn thực sự cần chỉ mục, hãy cân nhắc sử dụngstd::size_tlàm loại biến vòng lặp để khớp với kiểu trả về của.size()hoặc sử dụngstd::ssize()(C++20) trả về kích thước đã ký.
Tránh các vòng lặp lồng nhau sâu — nếu bạn thấy mình lồng ba vòng lặp trở lên, hãy tách các vòng lặp bên trong thành các hàm. Điều này cải thiện khả năng đọc và kiểm tra.
- Ưu tiên các vòng lặp for dựa trên phạm vi hơn các vòng lặp dựa trên chỉ mục khi không cần chỉ mục
- Sử dụng const auto& trong phạm vi dựa trên để tránh các bản sao không cần thiết
- Sử dụng các liên kết có cấu trúc để phân tách các cặp và bộ dữ liệu trong các biến vòng lặp
- Trích xuất các vòng lặp lồng nhau sâu vào các hàm được đặt tên để rõ ràng
constexpr if (C++17): Phân nhánh thời gian biên dịch
if constexpr đánh giá điều kiện tại thời điểm biên dịch và loại bỏ nhánh không được lấy. Điều này rất cần thiết trong lập trình mẫu trong đó các nhánh khác nhau thậm chí có thể không hợp lệ cho tất cả các loại:
#include <iostream>
#include <type_traits>
#include <string>
template <typename T>
void describe(T value) {
std::cout << "Value: " << value;
if constexpr (std::is_integral_v<T>) {
// This branch only compiles when T is an integer type
std::cout << " (integer, " << (value % 2 == 0 ? "even" : "odd") << ")";
} else if constexpr (std::is_floating_point_v<T>) {
// This branch only compiles when T is a floating-point type
std::cout << " (floating-point)";
} else {
std::cout << " (other type)";
}
std::cout << '\n';
}
int main() {
describe(42); // Value: 42 (integer, even)
describe(3.14); // Value: 3.14 (floating-point)
describe("hello"); // Value: hello (other type)
}Nhấn Chạy để biên dịch và chạy ví dụ này.
- C++17 if/switch với bộ khởi tạo giới hạn phạm vi biến trong câu lệnh - sử dụng chúng để giữ mã sạch
- Các vòng lặp for dựa trên phạm vi loại bỏ từng lỗi một và các vấn đề so sánh có chữ ký/không dấu
- Các liên kết có cấu trúc (C++17) phân tách các cặp, bộ dữ liệu và cấu trúc thành các biến được đặt tên
- Sử dụng [[fallthrough]] để ghi lại dự phòng trường hợp chuyển đổi có chủ ý
- nếu constexpr (C++17) đánh giá các điều kiện tại thời điểm biên dịch - nhánh bị loại bỏ không được biên dịch
- Giảm thiểu phạm vi biến: khai báo các biến càng gần lần sử dụng đầu tiên càng tốt
Câu đố — Kiểm tra kiến thức
(15 XP)1. Ưu điểm của`if`của C++17 với trình khởi tạo:`if (auto x = f(); x > 0)`là gì?
2. `for (const auto& [key, val] : my_map)`làm gì?
3. Điều gì xảy ra với nhánh bị loại bỏ của câu lệnh`if constexpr`?