التحكم في التدفق والحلقات
إتقان جميع بنيات تدفق التحكم في C++ بما في ذلك if/else، والتبديل، وجميع أشكال الحلقات، والمستندة إلى النطاق، ومهيئات C++17، والارتباطات المنظمة.
Control Flow in 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.
إذا / آخر والتبديل
تعد عبارات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";
}
}اضغط على تشغيل لترجمة هذا المثال وتشغيله.
الحلقات: for و while و do-while و range-based for
يقدم C++ أشكال حلقات متعددة. تُفضل حلقة for ** المستندة إلى النطاق (C++11) عند التكرار على الحاويات لأنها تزيل الأخطاء المتتابعة وتكون أكثر وضوحًا:
#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);
}اضغط على تشغيل لترجمة هذا المثال وتشغيله.
الارتباطات المنظمة (C++17)
الارتباطات الهيكلية (C++17) تسمح لك بتحليل الكائن إلى الأجزاء المكونة له. إنهم يعملون مع المصفوفات والبنيات مع جميع الأعضاء العامين وstd::pairوstd::tupleوأي نوع يدعم بروتوكول الارتباطات المنظمة.
يقوم بناء الجملةauto [a, b] = expr;بإنشاء مراجع مسماة لمكوناتexpr. يمكنك إضافة مؤهلاتconstو&: يقومconst auto& [a, b] = expr;بإنشاء مراجع ثابتة بدون نسخ.
تعتبر الارتباطات المنظمة قوية بشكل خاص مع تكرارstd::map(تحليلstd::pair)، وإرجاع الوظائف التي تستخدمstd::pairأوstd::tuple، وقيمة الإرجاع لأسلوبinsertعلى الحاويات الترابطية.
استخدمbreakللخروج من الحلقة مبكرًا عندما تجد ما تحتاجه. استخدمcontinueلتخطي بقية التكرار الحالي والانتقال إلى التكرار التالي. كلاهما يجعل الحلقات أسهل في الفهم عند استخدامها بحكمة.
تفضل المعتمد على النطاق على المستند إلى الفهرس عندما لا تحتاج إلى الفهرس. فهو يزيل فئات كاملة من الأخطاء: الأخطاء الفردية، واستخدام المتغير الخاطئ كمؤشر، وتحذيرات المقارنة الموقعة/غير الموقعة.
عندما تحتاج إلى الفهرس، فكر في استخدامstd::size_tكنوع متغير الحلقة لمطابقة نوع الإرجاع.size()، أو استخدمstd::ssize()(C++20) الذي يُرجع حجمًا موقّعًا.
تجنب الحلقات المتداخلة بعمق - إذا وجدت نفسك تتداخل مع ثلاث حلقات أو أكثر، فاستخرج الحلقات الداخلية إلى وظائف. وهذا يحسن قابلية القراءة وقابلية الاختبار.
- تفضل الحلقات المستندة إلى النطاق على الحلقات المستندة إلى الفهرس عندما لا تكون هناك حاجة إلى الفهرس
- استخدم const auto& في النطاق المستند إلى تجنب النسخ غير الضرورية
- استخدم الارتباطات المنظمة لتحليل الأزواج والصفوف في متغيرات الحلقة
- قم باستخراج الحلقات المتداخلة بعمق إلى وظائف محددة من أجل الوضوح
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 مع أدوات التهيئة يحد من النطاق المتغير للبيان - استخدمها للحفاظ على نظافة التعليمات البرمجية
- تعمل الحلقات المستندة إلى النطاق على التخلص من الأخطاء الفردية ومشكلات المقارنة الموقعة/غير الموقعة
- Structured bindings (C++17) decompose pairs, tuples, and structs into named variables
- استخدم [[fallthrough]] لتوثيق حالة التبديل المتعمدة
- إذا قام constexpr (C++17) بتقييم الظروف في وقت الترجمة - لم يتم تجميع الفرع المهمل
- تصغير نطاق المتغير: أعلن عن أن المتغيرات قريبة من الاستخدام الأول قدر الإمكان
اختبار — اختبر معرفتك
(15 نقطة خبرة)1. ما هي ميزة`if`الخاص بـ C++17 مع المُهيئ:`if (auto x = f(); x > 0)`؟
2. ماذا يفعل`for (const auto& [key, val] : my_map)`؟
3. ماذا يحدث للفرع المهمل لبيان`if constexpr`؟