Έλεγχος ροής & βρόχων
Κατακτήστε όλες τις δομές ροής ελέγχου C++, συμπεριλαμβανομένων των if/else, switch, όλων των μορφών βρόχου, βάσει εύρους για, αρχικοποιητές C++17 και δομημένες συνδέσεις.
Έλεγχος ροής στο C++
Οι δηλώσεις ροής ελέγχου καθορίζουν τη σειρά με την οποία εκτελείται το πρόγραμμά σας. Το C++ παρέχει ένα πλούσιο σύνολο κατασκευών ροής ελέγχου που ξεπερνούν πολύ τα βασικά που κληρονομήθηκαν από το C. Το σύγχρονο C++ (C++17 και πέρα) προσθέτει χαρακτηριστικά όπως τοifμε αρχικοποιητές και τοconstexpr ifπου κάνουν τη ροή ελέγχου πιο εκφραστική και ασφαλέστερη.
Μια βασική αρχή: ελαχιστοποιήστε το εύρος των μεταβλητών. Κάθε μεταβλητή θα πρέπει να δηλώνεται όσο το δυνατόν πιο κοντά στην πρώτη της χρήση και στο στενότερο δυνατό πεδίο εφαρμογής. Οι δυνατότητες του C++17 όπως τοifμε αρχικοποιητές σάς βοηθούν να ακολουθήσετε αυτήν την αρχή.
if/else και αλλάξτε
Οι δηλώσειςifκαιswitchείναι οι κύριοι μηχανισμοί διακλάδωσης. Το C++17 προσθέτει ρήτρες αρχικοποιητή και στα δύο:
#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";
}
}Πατήστε Εκτέλεση για να μεταγλωττίσετε και να εκτελέσετε αυτό το παράδειγμα.
Loops: for, while, do-while, and range-based for
C++ offers multiple loop forms. The range-based for loop (C++11) is preferred when iterating over containers because it eliminates off-by-one errors and is clearer:
#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);
}Πατήστε Εκτέλεση για να μεταγλωττίσετε και να εκτελέσετε αυτό το παράδειγμα.
Structured Bindings (C++17)
Structured bindings (C++17) allow you to decompose an object into its constituent parts. They work with arrays, structs with all public members,std::pair,std::tuple, and any type that supports the structured bindings protocol.
The syntaxauto [a, b] = expr;creates named references to the components ofexpr. You can addconstand&qualifiers:const auto& [a, b] = expr;creates const references without copying.
Structured bindings are especially powerful withstd::mapiteration (decomposingstd::pair), function returns that usestd::pairorstd::tuple, and theinsertmethod's return value on associative containers.
Usebreakto exit a loop early when you have found what you need. Usecontinueto skip the rest of the current iteration and move to the next one. Both make loops easier to understand when used judiciously.
Prefer range-based for over index-based for when you do not need the index. It eliminates entire classes of bugs: off-by-one errors, using the wrong variable as index, and signed/unsigned comparison warnings.
When you do need the index, consider usingstd::size_tas the loop variable type to match the return type of.size(), or usestd::ssize()(C++20) which returns a signed size.
Avoid deeply nested loops — if you find yourself nesting three or more loops, extract the inner loops into functions. This improves readability and testability.
- Προτιμήστε τους βρόχους βάσει εύρους έναντι των βρόχων που βασίζονται σε ευρετήριο όταν το ευρετήριο δεν χρειάζεται
- Use const auto& in range-based for to avoid unnecessary copies
- Χρησιμοποιήστε δομημένες συνδέσεις για να αποσυνθέσετε ζεύγη και πλειάδες σε μεταβλητές βρόχου
- Εξαγάγετε τους βαθιά ένθετους βρόχους σε επώνυμες συναρτήσεις για σαφήνεια
constexpr if (C++17): Διακλάδωση μεταγλώττισης-χρόνου
Το if constexpr αξιολογεί τη συνθήκη κατά το χρόνο μεταγλώττισης και απορρίπτει τον κλάδο που δεν έχει ληφθεί. Αυτό είναι απαραίτητο στον προγραμματισμό προτύπων όπου διαφορετικοί κλάδοι μπορεί να μην ισχύουν καν για όλους τους τύπους:
#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)
}Πατήστε Εκτέλεση για να μεταγλωττίσετε και να εκτελέσετε αυτό το παράδειγμα.
- C++17 if/switch με αρχικοποιητές περιορίζει το εύρος μεταβλητών στη δήλωση — χρησιμοποιήστε τα για να διατηρήσετε τον κώδικα καθαρό
- Οι βρόχοι βάσει εύρους εξαλείφουν τα τυχαία σφάλματα και τα προβλήματα σύγκρισης με υπογραφή/μη υπογραφή
- Οι δομημένες δεσμεύσεις (C++17) αποσυνθέτουν ζεύγη, πλειάδες και δομές σε ονομασμένες μεταβλητές
- Χρησιμοποιήστε το [[fallthrough]] για να τεκμηριώσετε σκόπιμη πτώση θήκης
- εάν το constexpr (C++17) αξιολογεί τις συνθήκες κατά το χρόνο μεταγλώττισης — ο κλάδος που απορρίφθηκε δεν έχει μεταγλωττιστεί
- Ελαχιστοποίηση εμβέλειας μεταβλητής: δηλώστε τις μεταβλητές όσο το δυνατόν πιο κοντά στην πρώτη χρήση
Κουίζ — Δοκιμάστε τις γνώσεις σας
(15 XP)1. Ποιο είναι το πλεονέκτημα του`if`του C++17 με αρχικοποιητή:`if (auto x = f(); x > 0)`;
2. Τι κάνει το`for (const auto& [key, val] : my_map)`;
3. Τι συμβαίνει με τον κλάδο που απορρίφθηκε μιας πρότασης`if constexpr`;