कंट्रोल फ़्लो और लूप
if/else, switch, सभी लूप रूप, range-based for, C++17 इनिशियलाइज़र और स्ट्रक्चर्ड बाइंडिंग सहित C++ के सभी कंट्रोल फ़्लो कंस्ट्रक्ट में महारत हासिल करें।
C++ में कंट्रोल फ़्लो
कंट्रोल फ़्लो स्टेटमेंट तय करते हैं कि आपका प्रोग्राम किस क्रम में चलेगा। C++ कंट्रोल फ़्लो कंस्ट्रक्ट का एक समृद्ध समूह देती है जो C से विरासत में मिली बुनियादी बातों से काफ़ी आगे जाता है। आधुनिक C++ (C++17 और उसके बाद) इनिशियलाइज़र वाले if और constexpr if जैसे फ़ीचर जोड़ती है, जो कंट्रोल फ़्लो को ज़्यादा अभिव्यंजक और सुरक्षित बनाते हैं।
एक मुख्य सिद्धांत: वेरिएबल का स्कोप न्यूनतम रखें। हर वेरिएबल को उसके पहले उपयोग के जितना संभव हो उतना करीब, और जितने संभव हो उतने संकीर्ण स्कोप में घोषित करना चाहिए। इनिशियलाइज़र वाले if जैसे C++17 फ़ीचर इस सिद्धांत का पालन करने में आपकी मदद करते हैं।
if/else और switch
if और switch स्टेटमेंट शाखाकरण (branching) के प्राथमिक तंत्र हैं। 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++ कई लूप रूप देती है। कंटेनर पर इटरेट करते समय range-based for लूप (C++11) को प्राथमिकता दी जाती है, क्योंकि यह off-by-one एरर खत्म कर देता है और ज़्यादा स्पष्ट है:
#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)
स्ट्रक्चर्ड बाइंडिंग (Structured bindings) (C++17) आपको किसी ऑब्जेक्ट को उसके घटक भागों में तोड़ने देती हैं। ये ऐरे, उन स्ट्रक्ट के साथ जिनके सभी मेंबर public हों, std::pair, std::tuple, और स्ट्रक्चर्ड बाइंडिंग प्रोटोकॉल को सपोर्ट करने वाले किसी भी टाइप के साथ काम करती हैं।
सिंटैक्स auto [a, b] = expr; एक छिपा हुआ ऑब्जेक्ट बनाकर उसे expr से इनिशियलाइज़ करता है, और a, b नाम उसके घटकों से बँध जाते हैं (ये नाम खुद रेफ़रेंस नहीं होते)। आप const और & क्वालिफ़ायर जोड़ सकते हैं: const auto& [a, b] = expr; कॉपी किए बिना मूल ऑब्जेक्ट के घटकों को const रेफ़रेंस के ज़रिए बाँधता है।
स्ट्रक्चर्ड बाइंडिंग खास तौर पर std::map इटरेशन (std::pair को तोड़ना), std::pair या std::tuple इस्तेमाल करने वाले फ़ंक्शन रिटर्न, और एसोसिएटिव कंटेनर पर insert मेथड की रिटर्न वैल्यू के साथ बहुत उपयोगी हैं।
जब आपको जो चाहिए वह मिल जाए, तो लूप से जल्दी बाहर निकलने के लिए break का उपयोग करें। मौजूदा इटरेशन का बाकी हिस्सा छोड़कर अगले पर जाने के लिए continue का उपयोग करें। समझदारी से उपयोग करने पर दोनों लूप को समझना आसान बनाते हैं।
जब आपको इंडेक्स की ज़रूरत न हो, तो इंडेक्स-आधारित for के बजाय range-based for को प्राथमिकता दें। यह बगों की पूरी-पूरी श्रेणियाँ खत्म कर देता है: off-by-one एरर, इंडेक्स के रूप में गलत वेरिएबल का उपयोग, और साइन्ड/अनसाइन्ड तुलना की वार्निंग।
जब आपको इंडेक्स चाहिए ही हो, तो .size() के रिटर्न टाइप से मेल खाने के लिए लूप वेरिएबल के टाइप के रूप में std::size_t का उपयोग करने पर विचार करें, या std::ssize() (C++20) का उपयोग करें जो साइन्ड साइज़ लौटाता है।
गहराई से नेस्टेड लूप से बचें — अगर आप तीन या उससे ज़्यादा लूप नेस्ट कर रहे हैं, तो भीतरी लूप को फ़ंक्शन में निकाल लें। इससे पठनीयता और टेस्ट करने की क्षमता बेहतर होती है।
- जब इंडेक्स की ज़रूरत न हो, तो इंडेक्स-आधारित लूप के बजाय range-based for लूप को प्राथमिकता दें
- अनावश्यक कॉपी से बचने के लिए range-based for में const auto& का उपयोग करें
- लूप वेरिएबल में pair और tuple को तोड़ने के लिए स्ट्रक्चर्ड बाइंडिंग का उपयोग करें
- स्पष्टता के लिए गहराई से नेस्टेड लूप को नामित फ़ंक्शन में निकाल लें
if constexpr (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 वेरिएबल का स्कोप उस स्टेटमेंट तक सीमित रखते हैं — कोड को साफ़ रखने के लिए इनका उपयोग करें
- Range-based for लूप off-by-one एरर और साइन्ड/अनसाइन्ड तुलना की समस्याएँ खत्म कर देते हैं
- स्ट्रक्चर्ड बाइंडिंग (C++17) pair, tuple और स्ट्रक्ट को नामित वेरिएबल में तोड़ देती हैं
- switch case के जान-बूझकर किए गए fallthrough को दर्ज करने के लिए [[fallthrough]] का उपयोग करें
- if constexpr (C++17) कंडीशन को कंपाइल टाइम पर इवैल्यूएट करता है — टेम्पलेट के भीतर हटाई गई ब्रांच इंस्टैंशिएट नहीं होती
- वेरिएबल का स्कोप न्यूनतम रखें: वेरिएबल को पहले उपयोग के जितना संभव हो उतना करीब घोषित करें
क्विज़ — अपना ज्ञान परखें
(15 XP)1. C++17 के इनिशियलाइज़र वाले `if`, यानी `if (auto x = f(); x > 0)`, का क्या फ़ायदा है?
2. `for (const auto& [key, val] : my_map)` क्या करता है?
3. `if constexpr` स्टेटमेंट की हटाई गई ब्रांच का क्या होता है?