शर्तें और चुनी गई शाखाएँ
bonus को कौन-सी शाखा बदलेगी?
नीचे के प्रोग्राम में शुरुआत में score=12 है। क्या वह पहली सही शर्त का बोनस देगा, या उसके बाद और बड़ा बोनस ढूँढ़ता रहेगा? ट्रेस पढ़ने से पहले छपने वाली दोनों संख्याओं का अनुमान लगाइए।
#include <stdio.h>
int main(void)
{
int score = 12;
int bonus = 0;
if (score >= 10) {
bonus = 2;
} else if (score >= 12) {
bonus = 5;
} else {
bonus = 1;
}
printf("%d %d\n", bonus, score + bonus);
return 0;
}पिछले पाठ में आपने देखा कि असाइनमेंट वर्तमान अवस्था (state) कैसे बदलता है। अब शर्त (condition) तय करेगी कि किसी असाइनमेंट तक पहुँचना है या नहीं। यहाँ C11 और छोटे signed int मानों का उपयोग है। पढ़े जाने से पहले हर चर को मान दिया गया है, और मूल्यांकित गणनाओं के सभी मान −32767…32767 में रहते हैं। प्रत्येक पूरा प्रोग्राम अलग संकलित किया जा सकता है। मूल अभ्यास प्रश्न 3 जानबूझकर असुरक्षित है; उसे केवल पढ़ना है, चलाना नहीं।
शर्त का भी एक मान होता है
शर्त एक व्यंजक (expression) है जिससे अगला कदम तय होता है। इस पाठ के पूर्णांकों में शून्य का अर्थ गलत और हर गैर-शून्य मान का अर्थ सही है। इसलिए 0 गलत है, लेकिन 5 और −3 दोनों सही हैं। ऋणात्मक संख्या अपने आप गलत नहीं होती। किसी चर को शर्त की तरह जाँचने से उसका संग्रहित मान नहीं बदलता: x में −3 हो तो x की जाँच उसे 1 में नहीं बदलती।
तुलना का परिणाम int होता है: संबंध सही हो तो 1, गलत हो तो 0। सख्त तुलना के लिए < और >, बराबरी को भी शामिल करने के लिए <= और >=, बराबरी जाँचने के लिए ==, और बराबर न होने की जाँच के लिए != लिखते हैं। score=12 पर score>=10 का मान 1, score==10 का मान 0, और score!=10 का मान 1 है। ये जाँच score को पढ़ती हैं; कोई नया score नहीं रखतीं।
असाइनमेंट और बराबरी को अलग रखें। x = 5, x में 5 रखता है। x == 5 पूछता है कि x का वर्तमान मान 5 है या नहीं। C की शर्त में असाइनमेंट लिखना वैध है, पर वह तब भी असाइनमेंट ही करता है: शर्त x = 5 हो तो x में 5 रखा जाता है और शर्त सही होती है। यह बराबरी की जाँच नहीं है। बराबरी पूछनी हो तो x == 5 लिखें, और सामान्य अपडेट को पहले के पाठों की तरह अलग कथन में रखें।
स्रोत-नियम: तुलना के परिणाम और पूर्णांक शर्तों के लिए C11 का N1570 मसौदा, §§6.5.8p6, 6.5.9p3 और 6.8.4.1p2 देखें। असाइनमेंट के मान का नियम §6.5.16p3 में है।
if, else if और else से एक रास्ता चुनना
उदाहरण 1 को इस क्रम में पढ़ें। गोल कोष्ठकों में शर्त है। बड़े कोष्ठकों में उस शाखा के कथन हैं।
- score>=10 जाँचें। सही हो तो पहले बड़े कोष्ठकों वाली body चलाएँ और पूरा else भाग छोड़ दें।
- पहली जाँच गलत होने पर ही else के अंदर वाले दूसरे if तक पहुँचें। तभी score>=12 जाँचें।
- दोनों तक पहुँची हुई जाँच गलत हों तो अंतिम else की body चलाएँ। अंतिम else की अपनी कोई शर्त नहीं होती।
- पूरी श्रृंखला के बाद printf पर आगे बढ़ें।
else-if की श्रृंखला आपस में जुड़ा एक चुनाव है। वह शर्तों की रैंकिंग नहीं करती और सबसे बड़ा असाइन होने वाला मान नहीं चुनती। इस विशेष क्रम में score>=12 की शाखा कभी नहीं पहुँच सकती: वहाँ तक पहुँचने वाला score पहले ही score>=10 में असफल हुआ है, इसलिए वह 10 से छोटा होगा। यदि नियम में बड़े threshold को प्राथमिकता देनी हो तो score>=12 को पहले जाँचें। पहले वास्तव में लिखे प्रोग्राम का ट्रेस करें; उसके बाद तय करें कि लिखा नियम इच्छित नियम है या नहीं।
हल किया हुआ उदाहरण 1: पहली सही शाखा चुनी जाती है
- घोषणाओं के बाद: score=12, bonus=0।
- पहली जाँच: 12>=10 से 1 मिलता है, इसलिए पहली body में प्रवेश होता है।
- bonus=2 चलता है। अवस्था: score=12, bonus=2।
- पूरा else भाग छूट जाता है। इस बार score>=12 का मूल्यांकन नहीं होता और बाद वाले दोनों असाइनमेंट भी नहीं चलते।
- printf, bonus=2 पढ़ता है और score+bonus को 12+2=14 निकालता है। आउटपुट: 2 14, फिर नई पंक्ति।
आउटपुट 5 17 नहीं है: बाद की शर्त गणित की दृष्टि से सही हो, तब भी उस तक पहुँचे बिना उसकी जाँच नहीं होती। 1 13 भी नहीं है: else गलत शर्त का वैकल्पिक रास्ता है, अंत में हमेशा चलने वाला कथन नहीं।
इसके विपरीत, दो अलग if कथन दो अलग निर्णय हैं। पहला पूरा होने के बाद दूसरे तक पहुँचकर वर्तमान अवस्था से उसकी जाँच की जाती है। दोनों की body चल सकती है। पहली body उस चर को बदल दे जिसे दूसरी शर्त पढ़ती है, तो दूसरी जाँच में नया मान लें। नीचे अभ्यास प्रश्न 1 में जाँचे जा रहे चर को बदले बिना यही अंतर दिखता है।
!, && और || से शर्त बनाना
तार्किक ऑपरेटरों का परिणाम भी int मान 0 या 1 होता है। वे किसी भी गैर-शून्य operand को सीधे वापस नहीं देते। उदाहरण के लिए (-3) && 5 तार्किक रूप से सही है और 1 देता है, 5 या -3 नहीं।
- ! तार्किक NOT है। !0 से 1, जबकि !5 और !(-3) से 0 मिलता है। यह सत्यता बदलता है, संख्या का चिह्न नहीं।
- left && right में पहले बायाँ operand जाँचें। वह शून्य हो तो पूरा परिणाम 0 है और दाएँ operand का मूल्यांकन न करें। बायाँ गैर-शून्य हो तभी दायाँ जाँचें; दायाँ भी गैर-शून्य हो तो परिणाम 1 है, अन्यथा 0।
- left || right में पहले बायाँ operand जाँचें। वह गैर-शून्य हो तो पूरा परिणाम 1 है और दाएँ operand का मूल्यांकन न करें। बायाँ शून्य हो तभी दायाँ जाँचें; दायाँ गैर-शून्य हो तो परिणाम 1 है, अन्यथा 0।
इस तरह अनावश्यक भाग छोड़ने को short-circuit evaluation कहते हैं। “छोड़ दिया” का अर्थ “गणना करके परिणाम अनदेखा किया” नहीं है: उस व्यंजक की गणना होती ही नहीं। && और || में, दाएँ operand की जरूरत हो तो भी बाएँ operand का मूल्यांकन उसके पहले क्रमित होता है। यह नियम इन तार्किक ऑपरेटरों का है; इससे यह निष्कर्ष न निकालें कि C का हर व्यंजक बाएँ से दाएँ मूल्यांकित होता है। N1570, §6.5.3.3p5 और §§6.5.13p3–4, 6.5.14p3–4 देखें।
हल किया हुआ उदाहरण 2: कौन-से भाग छूटे, यह दिखाएँ
#include <stdio.h>
int main(void)
{
int divisor = 0;
int value = 15;
int all = (divisor != 0) && (value / divisor > 2);
int any = (value > 10) || (value / divisor > 2);
printf("%d %d\n", all, any);
return 0;
}- शुरुआती अवस्था: divisor=0, value=15।
- all को मान देने के लिए पहले divisor!=0 निकालें। 0!=0 की तुलना से 0 मिलता है।
- && का बायाँ operand गलत है, इसलिए value/divisor>2 छूट जाता है। कोई भाग नहीं होता। all=0 रखें।
- any को मान देने के लिए पहले value>10 निकालें। 15>10 की तुलना से 1 मिलता है।
- || का बायाँ operand सही है, इसलिए यहाँ भी value/divisor>2 छूट जाता है। any=1 रखें।
- printf से 0 1 और फिर नई पंक्ति छपती है। divisor और value के मान अभी भी 0 और 15 हैं।
divisor में शून्य होने मात्र से इस बार undefined behavior नहीं होता: वास्तव में मूल्यांकित किसी गणना में उससे भाग नहीं हुआ। इनमें से किसी भी operand जोड़ी का क्रम उलटने पर इन शुरुआती मानों के लिए भाग पहले होगा और सुरक्षा खत्म हो जाएगी। && वाला divisor!=0 गार्ड शुरुआती divisor बदलने पर भी भाग की रक्षा करता है: शून्य हो तो भाग छूटता है; गैर-शून्य हो तो अंश 15 वाले इस भाग का परिणाम int में निरूपित हो सकता है। दिखाया गया || व्यंजक value=15 पर सुरक्षित है क्योंकि उसका बायाँ operand सही है; यह शून्य divisor से बचाने वाला सामान्य गार्ड नहीं है। value=3 और divisor=0 पर इसी || का बायाँ operand गलत होगा और दाएँ में शून्य से भाग होगा। एक बार सही परिणाम आना सभी इनपुट के लिए सुरक्षा का प्रमाण नहीं है।
दोनों सिरों सहित अंतराल के लिए दो तुलना करें
m, 2 से 5 तक है और दोनों सिरे शामिल हैं, यह पूछने के लिए दोनों बातें चाहिए: m>=2 और m<=5। लिखें (m>=2)&&(m<=5)। m=2 पर दोनों तुलना सही हैं; m=5 पर भी दोनों सही हैं; m=1 पर पहली गलत है और m=6 पर दूसरी गलत है। दोनों तुलना साफ दिखाने के लिए कोष्ठक लगाएँ।
गणित के छोटे रूप 2<=m<=5 को C में वैसे ही न लिखें। C उसे (2<=m)<=5 की तरह समूहित करता है। m=8 पर पहली तुलना से 1 मिलता है, फिर 1<=5 से 1 मिलता है, इसलिए 8 गलत ढंग से स्वीकार हो जाता है। वास्तव में पहली तुलना का 0 या 1, दोनों ही <=5 हैं। पूरे chained व्यंजक के बाहर कोष्ठक लगाने से उसका अर्थ नहीं सुधरता। सही रूप में दोनों तुलना को && से जोड़ना जरूरी है। (m>2)&&(m<5) अलग अंतराल है, क्योंकि वह 2 और 5 को छोड़ता है।
अभ्यास: हल देखने से पहले चारों प्रश्न करें
इन प्रश्नों में हल किए ट्रेस से अलग मान या अलग नियंत्रण-प्रवाह का अभ्यास है। इनके अंक नहीं हैं।
अभ्यास प्रश्न 1: दो स्वतंत्र निर्णय
flags का अंतिम मान निकालिए। बताइए कि हर body चलती है या नहीं, और यह else-if की श्रृंखला से अलग क्यों है।
#include <stdio.h>
int main(void)
{
int m=9,flags=0;
if(m%3==0){flags+=1;}
if(m>5){flags+=2;}
printf("%d\n",flags);
return 0;
}अभ्यास प्रश्न 2: NOT के बाद AND
z का मान निकालिए। !a का मान बताइए, && का दायाँ operand जाँचा जाता है या नहीं बताइए, और अंतिम तार्किक परिणाम दीजिए।
#include <stdio.h>
int main(void)
{
int a=0,b=5;
int z=(!a)&&(b>2);
printf("%d\n",z);
return 0;
}अभ्यास प्रश्न 3: गार्ड का क्रम सुधारें
d=0 और n=14 के लिए नीचे की शर्त सुरक्षित है या नहीं? उसके केवल दोनों operands का क्रम बदलकर भाग को सुरक्षित करने वाला गार्ड बनाइए। आउटपुट का अनुमान लगाने के लिए मूल प्रोग्राम न चलाएँ; यहाँ व्यवहार का वर्गीकरण और सुधार करना है।
#include <stdio.h>
int main(void)
{
int d=0,n=14;
int result=(n/d>1)&&(d!=0);
return 0;
}अभ्यास प्रश्न 4: सिरों को शामिल रखें
सामान्य int m के लिए कौन-सा व्यंजक दोनों सिरों सहित [2,5] अंतराल जाँचता है?
- A: (m>=2)&&(m<=5)
- B: (m>2)&&(m<5)
A या B चुनिए, फिर m=5 वाली जाँच में छपने वाले दोनों परिणाम बताइए।
#include <stdio.h>
int main(void)
{
int m=5;
printf("%d %d\n",(m>=2)&&(m<=5),(m>2)&&(m<5));
return 0;
}अभ्यास के पूरे हल और गलत उत्तरों का कारण
अभ्यास प्रश्न 1 का हल
- शुरुआत में m=9, flags=0।
- m%3 से 9%3=0 मिलता है; 0==0 से 1 मिलता है। पहली body, 1 जोड़ती है और flags=1 हो जाता है।
- दूसरे स्वतंत्र if तक पहुँचते हैं। 9>5 से 1 मिलता है, इसलिए उसकी body वर्तमान flags=1 में 2 जोड़ती है।
- अंत में flags=3 है; आउटपुट 3 और नई पंक्ति है। दोनों जाँच हुईं और दोनों body चलीं।
1 उत्तर देने पर दूसरे if को else-if की तरह गलत ढंग से छोड़ दिया गया है। 2 उत्तर केवल दूसरा बदलाव रखता है, जबकि += वर्तमान flags में जोड़ता है, उसे 2 से बदलता नहीं। 0 उत्तर में दोनों तुलना सही होने की बात छूट गई है। पूरे समय m का मान 9 रहता है।
अभ्यास प्रश्न 2 का हल
- a=0 और b=5 हैं।
- !a अर्थात !0 से 1 मिलता है।
- && का बायाँ operand गैर-शून्य है, इसलिए b>2 जाँचा जाता है। 5>2 से 1 मिलता है।
- दोनों operands सही हैं, इसलिए z=1 है और आउटपुट 1, फिर नई पंक्ति है।
0 उत्तर में !0 को गलती से 0 माना गया है। 5 उत्तर में तार्किक परिणाम और b के संग्रहित मान को एक माना गया है: && केवल 0 या 1 देता है। a और b में कोई बदलाव नहीं होता, और इस बार दायाँ operand छोड़ा नहीं जाता।
अभ्यास प्रश्न 3 का हल
मूल में undefined behavior है। उसका बायाँ operand d की जाँच से पहले n/d निकालता है; यहाँ वह 14/0 है। C11 इसके लिए कोई निश्चित आउटपुट नहीं बताता, और crash होना भी निश्चित परिणाम नहीं है। दाईं तुलना बाद में गलत होती, इससे पहले हो चुका अमान्य भाग रद्द नहीं हो जाता।
सुधरी हुई शर्त (d!=0)&&(n/d>1) है। इसकी पूरी सुरक्षित जाँच:
#include <stdio.h>
int main(void)
{
int d=0,n=14;
int result=(d!=0)&&(n/d>1);
printf("%d\n",result);
return 0;
}- d!=0 अर्थात 0!=0 से 0 मिलता है।
- &&, n/d>1 को छोड़ देता है; इसलिए कोई भाग नहीं होता।
- result में 0 रखा जाता है; सुधरा प्रोग्राम 0 और नई पंक्ति छापता है।
मूल शर्त के चारों ओर केवल कोष्ठक लगाने से operands का क्रम नहीं बदलता। && को || में बदल देने से भी सबसे बाएँ वाला भाग नहीं सुधरता। “d शून्य है इसलिए मूल 0 लौटाता है” भी गलत है: गार्ड बहुत बाद में आता है। असुरक्षित व्यवहार का नियम N1570, §6.5.5p5 में है; सुधार §6.5.13p4 के नियम पर आधारित है।
अभ्यास प्रश्न 4 का हल
A सही है। m=5 पर पहला व्यंजक 5>=2 और 5<=5 जाँचता है; दोनों सही हैं, इसलिए 1 मिलता है। दूसरा 5>2 को सही और फिर 5<5 को गलत पाता है, इसलिए 0 मिलता है। छपने वाली जोड़ी 1 0, फिर नई पंक्ति है।
B, m=2 को भी अस्वीकार करेगा, इसलिए वह दोनों सिरों को शामिल करने वाला अंतराल नहीं है। 1 1 उत्तर में सख्त ऊपरी तुलना छूट गई है; 0 0 उत्तर में <= को बराबरी अस्वीकार करने वाला मान लिया गया है। निचले सिरे के बाहर की जाँच में m=1 पर A की पहली तुलना गलत है, इसलिए उसकी दाईं तुलना छूटती है; फिर भी सही परिणाम 0 मिलता है।
अगली जाँच में वर्तमान अवस्था लें
आगे बढ़ने से पहले उदाहरण 1 की छूटी दूसरी जाँच और उदाहरण 2 के छूटे भाग को बिना मूल्यांकित किए समझाइए। शाखा तय करती है कि body एक बार चलनी है या नहीं। अगले पाठ में शर्त बार-बार जाँची जाएगी, ताकि body शून्य, एक या कई बार चले और हर बार नई अवस्था का उपयोग करे।
उदाहरण से समझें
मान लें कोई कर्मचारी लिखे हुए निर्देश से पार्सल पर स्टिकर लगाता है: “वजन कम-से-कम 10 इकाई हो तो नीला स्टिकर लगाएँ; नहीं तो अगला नियम जाँचें।” पहला रास्ता लेने के बाद वह वैकल्पिक रास्ते पर भी नहीं चलता। इसके बदले दो अलग पर्चियों पर अलग-अलग “यदि” निर्देश हों तो दो निर्णय करने होंगे और दोनों काम हो सकते हैं। यही पहली-सही-शाखा वाली श्रृंखला और स्वतंत्र if कथनों का अंतर है।
गार्ड ऐसा है: “क्या डिब्बों की संख्या गैर-शून्य है? तभी प्रति डिब्बा वस्तुओं की संख्या निकालें।” भाग करने से पहले डिब्बों की संख्या जाँचना जरूरी है। तुलना छिपकर भागफल नहीं निकालती। यह चित्र && समझने में मदद करता है, लेकिन C का सटीक नियम यही है कि बायाँ operand शून्य हो तो दायाँ छूटता है। || में बायाँ गैर-शून्य हो तो दायाँ छूटता है। इस उपमा से जाँचों का क्रम बदलने की अनुमति नहीं मिलती।
सूत्र और नियम
- int शर्त 0 पर गलत और हर गैर-शून्य मान पर सही है; ऋणात्मक मान भी सही है।
- <, >, <=, >=, == और != से int मान 0 या 1 मिलता है। x=5 असाइन करता है; x==5 तुलना करता है।
- !0 से 1; गैर-शून्य मान पर ! से 0 मिलता है।
- left && right: बायाँ पहले जाँचें; वह शून्य हो तो दायाँ छोड़ें।
- left || right: बायाँ पहले जाँचें; वह गैर-शून्य हो तो दायाँ छोड़ें।
- && और ||, 0 या 1 देते हैं। उनका क्रम सभी ऑपरेटरों के लिए सामान्य बाएँ-से-दाएँ नियम नहीं है।
- if/else-if/else श्रृंखला में पहली पहुँची हुई सही शाखा लें और उसके विकल्प छोड़ें। अलग if कथन अलग निर्णय हैं।
- दोनों सिरों सहित [2,5]: (m>=2)&&(m<=5)। 2<=m<=5 का chained रूप C में यह अंतराल नहीं बताता।
- d से भाग करने से पहले सुरक्षा देने वाला d!=0, && के बाएँ रखें। छोड़ा गया भाग मूल्यांकित नहीं होता; पहले हो चुके शून्य से भाग को बाद की जाँच नहीं सुधारती।
- ट्रेस में लिखें: वर्तमान अवस्था, हर पहुँची शर्त, चुनी body, छूटे operands या शाखाएँ, और उसके बाद की अवस्था।
इस पाठ के नोट्स
प्रगति सहेजने के लिए साइन इन करें। साइन इन