मुख्य सामग्री पर जाएँ

लूप, अपडेट और रुकने की शर्त

पाठ 4 / 1315 मिनटPDF नोट्समुफ़्त

अंतिम वर्ग जोड़ने के बाद क्या होता है?

यह प्रोग्राम वर्गों का योग निकालता है। printf पर sum और i, दोनों का मान बताइए। खास तौर पर सोचिए कि लूप समाप्त होते समय i अभी भी 4 है या नहीं।

C
#include <stdio.h>

int main(void)
{
    int sum = 0;
    int i;
    for (i = 1; i <= 4; i++) {
        sum += i * i;
    }
    printf("%d %d\n", sum, i);
    return 0;
}

लूप अपनी शर्त के अनुमति देने तक body दोहराता है। एक पूरा चक्कर पुनरावृत्ति (iteration) है। आप असाइनमेंट पढ़ना और शून्य/गैर-शून्य शर्त समझना जानते हैं। वही नियम रखें: अगली जाँच और अपडेट में वर्तमान अवस्था ली जाती है, शुरुआती अवस्था नहीं। यहाँ C11, पढ़ने से पहले मान पाए हुए int चर और −32767…32767 के भीतर रहने वाले मध्यवर्ती परिणाम हैं। दिखाए गए हर पूरे प्रोग्राम को अलग संकलित किया जा सकता है।

for के चार चरण पढ़ें

for (i=1; i<=4; i++) में पहला भाग शुरुआती अवस्था बनाता है, बीच का भाग शर्त है और अंतिम भाग अपडेट है। दो semicolon इन भागों को अलग करते हैं; उनका अर्थ यह नहीं है कि तीनों body से पहले चलेंगे।

  1. आरंभ: लूप तक पहुँचने पर i=1 केवल एक बार चलाएँ।
  2. जाँच: वर्तमान i से i<=4 निकालें। गलत हो तो तुरंत लूप छोड़ें।
  3. body: जाँच सही हो तो बड़े कोष्ठकों के कथन क्रम से चलाएँ।
  4. अपडेट: यह body पूरी होने पर i++ चलाएँ, फिर चरण 2 पर लौटें, चरण 1 पर नहीं।

यहाँ i की घोषणा लूप से पहले है, इसलिए बाद के printf में भी वह उपलब्ध है। int i; घोषणा अपने आप उपयोगी शुरुआती मान नहीं देती, लेकिन for का आरंभ किसी भी पढ़ने से पहले उसमें 1 रखता है। पहले का sum=0 एक संचायक (accumulator) बनाता है: ऐसा चर जो पुनरावृत्तियों के बीच अब तक का योग रखता है। sum += i*i का अर्थ है वर्तमान i का वर्ग निकालकर उसे वर्तमान sum में जोड़ना। body के अंदर sum को फिर शून्य न करें; ऐसा करने से पहले जोड़े गए पद खो जाएँगे।

अपडेट i++, body की गणना से अलग है। यहाँ इस व्यंजक का पुराना मान उपयोग नहीं होता; उसका जरूरी प्रभाव i को 1 बढ़ाना है। अंतिम सफल body के बाद भी अंतिम अपडेट होता है। उसके बाद वाली जाँच ही बताती है कि अब और body नहीं चलनी चाहिए।

स्रोत-नियम: for के चरणों का क्रम C11 के N1570 मसौदे, §6.8.5.3p1 में है। लूप के रुकने का नियम §6.8.5p4 में है।

हल किया हुआ उदाहरण 1: योग, body की संख्या और अंतिम अवस्था

  1. पहली जाँच से पहले: sum=0 और आरंभ ने i=1 किया है।
  2. जाँच 1: 1<=4 सही है। body: i*i=1; sum=0+1=1। अपडेट: i=2।
  3. जाँच 2: 2<=4 सही है। body: i*i=4; sum=1+4=5। अपडेट: i=3।
  4. जाँच 3: 3<=4 सही है। body: i*i=9; sum=5+9=14। अपडेट: i=4।
  5. जाँच 4: 4<=4 सही है। body: i*i=16; sum=14+16=30। अपडेट: i=5।
  6. जाँच 5: 5<=4 गलत है। न body चलाएँ, न एक और अपडेट। sum=30, i=5 पर बाहर निकलें।
  7. आउटपुट: 30 5, फिर नई पंक्ति।

आरंभ एक बार, शर्त का मूल्यांकन पाँच बार, body चार बार और अपडेट चार बार होता है। body को i=1,2,3,4 मिलता है; शर्त को रुकने वाला मान 5 भी मिलता है। 30 4 की जोड़ी में अंतिम अपडेट छूटा है। 55 5 में गलती से 5 का वर्ग भी जोड़ा गया है, जबकि उसकी जाँच गलत है।

संचायक सही क्यों है और लूप रुकता क्यों है?

लूप इनवेरिएंट (loop invariant) अवस्था के बारे में ऐसा कथन है जो चुने हुए जाँच-बिंदु पर हर बार सही रहता है। यहाँ बिंदु चुनें “हर जाँच के ठीक पहले”: sum में पहले आ चुके धनात्मक पूर्णांकों, यानी 1 से i−1 तक के वर्गों का योग है। यह चर का अर्थ बताने वाला सामान्य वर्णन है, कोड में जोड़ा जाने वाला नया कथन नहीं।

  • पहली जाँच पर i=1 है और अभी कोई पद नहीं आया, इसलिए योग 0 है। body चलने से पहले ही वर्णन सही है।
  • मान लें वर्णन सही है और i<=4 भी सही है। body ठीक अगला वर्ग i*i जोड़ती है। अपडेट i को अगला पूर्णांक बनाता है; इसलिए अगली जाँच से पहले योग फिर ठीक 1 से i−1 तक है।
  • बाहर निकलते समय i=5 है, इसलिए बना रहा वर्णन बताता है कि sum में 1 से 4 तक के वर्ग हैं। यह 1+4+9+16=30 से मेल खाता है।

सही योग बनना और लूप रुकना अलग प्रश्न हैं। यह लूप इसलिए रुकता है कि i, 1 से शुरू होकर हर पूरे चक्कर पर 1 बढ़ता है और 5 बन जाता है। तब जाँच गलत होती है। बीच में आने वाला हर मान, गुणनफल और योग छोटा तथा int में निरूपित करने योग्य है। केवल शर्त लिखने से प्रगति निश्चित नहीं होती: जरूरी अपडेट न हो तो यह नहीं मान सकते कि शर्त कभी अपने आप गलत हो जाएगी। यहाँ हम केवल समाप्त होने वाले, सुरक्षित छोटी सीमा के लूप का ट्रेस करते हैं।

while पहले जाँचता है; प्रगति body करती है

while में while (condition) के बाद body होती है। हर संभावित body से पहले शर्त जाँचें। शुरुआत में गलत हो तो body शून्य बार चलती है। while कथन में अपने आप चलने वाला अपडेट भाग नहीं है: प्रगति के लिए अवस्था बदलनी हो तो body के कथनों को वह करना होगा।

हल किया हुआ उदाहरण 2: हर बार पूर्णांक को आधा करें

C
#include <stdio.h>

int main(void)
{
    int n = 41;
    int steps = 0;
    while (n > 3) {
        n = n / 2;
        steps++;
    }
    printf("%d %d\n", n, steps);
    return 0;
}
  1. शुरुआत: n=41, steps=0।
  2. जाँच 1: 41>3 सही है। body: n=41/2=20; फिर steps में 1 हो जाता है।
  3. जाँच 2: 20>3 सही है। body: n=20/2=10; फिर steps में 2 हो जाता है।
  4. जाँच 3: 10>3 सही है। body: n=10/2=5; फिर steps में 3 हो जाता है।
  5. जाँच 4: 5>3 सही है। body: n=5/2=2; फिर steps में 4 हो जाता है।
  6. जाँच 5: 2>3 गलत है। एक और भाग या बढ़ोतरी किए बिना बाहर निकलें।
  7. आउटपुट: 2 4, फिर नई पंक्ति।

body चार बार और शर्त का मूल्यांकन पाँच बार होता है। body में n का असाइनमेंट चार बार तथा steps++ चार बार चलता है। अंतिम गलत जाँच के बाद पाँचवाँ अपडेट न जोड़ें। इस while में for जैसा अलग अपडेट चरण नहीं है।

हर जाँच पर steps पूरे हो चुके आधा करने वाले चरण गिनता है और n, शुरुआती 41 पर उतने पूर्णांक भाग करके मिला मान है। शून्य चरण पर यह वर्णन सही है; हर body एक पूर्णांक भाग करती है और steps एक बार बढ़ाती है, इसलिए वर्णन सही बना रहता है। हर चक्कर में पूर्णांक भाग ही करें: 41/2 से 20 और 5/2 से 2 मिलता है। 20.5 या 2.5 रखने पर आप अलग प्रकार के अंकगणित का ट्रेस करेंगे।

रुकने के लिए देखें: n>3 सही रहने तक n कम-से-कम 4 वाला पूर्णांक है। उसे 2 से भाग देने पर गैर-ऋणात्मक पूर्णांक मिलता है जो n से सख्ती से छोटा है। लगातार घटता गैर-ऋणात्मक पूर्णांक अनंत बार नहीं घट सकता, इसलिए अंततः वह 3 या उससे कम होगा और अगली जाँच गलत होगी। इस तर्क में शर्त और अपडेट दोनों जरूरी हैं। शुरुआती n=3 या n=0 हो तो पहली जाँच ही गलत है: शून्य body, एक जाँच, और steps का मान 0 रहता है। स्रोत-नियम: N1570, §6.8.5.1p1 और पूर्णांक भाग के लिए §6.5.5p6।

खाली सीमा और body के बाद जाँचने वाला रूप

for या while में body एक भी बार न चलना संभव है। उदाहरण के लिए for में i=6 से शुरू करें और i<=4 जाँचें: आरंभ फिर भी i में 6 रखता है, लेकिन पहली जाँच गलत होती है और न body चलती है न अपडेट। संचायक का शुरुआती मान बना रहता है। “शर्त जाँचने के लिए लूप को एक बार चलना ही पड़ेगा” इन पहले-जाँच वाले रूपों के लिए गलत है।

do...while में शर्त body के बाद होती है। उसका क्रम body → जाँच → body → जाँच है, जब तक कोई जाँच गलत न हो। अंत वाले while(condition) के बाद semicolon होता है। सामान्य प्रवेश और body पूरी होने पर, पहली जाँच से पहले body एक बार चलती है, भले ही शुरू में की जाने वाली वैसी ही शर्त गलत होती। नीचे अभ्यास प्रश्न 3 इसी सीमा की तुलना करता है। N1570, §6.8.5.2p1 देखें।

इस पाठ के सीमित लूप केवल शर्त गलत होने पर निकलते हैं। उनके लिए ये गिनतियाँ रखें:

  • for: एक आरंभ; k body और k अपडेट; अंतिम गलत जाँच सहित k+1 जाँच। k=0 हो तो एक जाँच और कोई अपडेट नहीं।
  • while: k body और k+1 जाँच। असाइनमेंट और बढ़ोतरी उसकी body से गिनें; कोई छिपा अपडेट नहीं है।
  • do...while: k body और k जाँच; यहाँ के उदाहरणों में k कम-से-कम 1 है। हर पूरी body के बाद एक जाँच है, जिसमें अंतिम गलत जाँच भी शामिल है।

ये मूल प्रोग्राम के तार्किक चरणों की गिनतियाँ हैं। इनसे compiler optimization के बाद मशीन निर्देशों या वास्तव में चलाए गए परीक्षणों की कोई निश्चित संख्या नहीं बताई जा रही। गिनतियाँ यहाँ के सामान्य सरल प्रवाह पर लागू हैं; बीच से निकलना और अन्य नियंत्रण-प्रवाह सुविधाएँ इस पाठ से बाहर हैं।

अभ्यास: उत्तर जाँचने से पहले ट्रेस करें

इन चार प्रश्नों के अंक नहीं हैं। केवल अंतिम छपा मान देखने के बजाय जाँच, body और अपडेट अलग-अलग लिखें।

अभ्यास प्रश्न 1: एक से बड़ा अपडेट

body में आने वाले i के मान, अंतिम sum और अंतिम i लिखिए। शर्त के मूल्यांकन और अपडेट की संख्या गिनिए।

C
#include <stdio.h>

int main(void)
{
    int sum=0,i;
    for(i=2;i<9;i+=3){sum+=i;}
    printf("%d %d\n",sum,i);
    return 0;
}

अभ्यास प्रश्न 2: पूर्णांक भाग की संख्या

body कितनी बार चलती है? अंत में count और n बताइए, और ट्रेस में अंतिम गलत जाँच भी शामिल कीजिए।

C
#include <stdio.h>

int main(void)
{
    int n=30,count=0;
    while(n>0){n=n/3;count++;}
    printf("%d %d\n",count,n);
    return 0;
}

अभ्यास प्रश्न 3: पहले जाँच या पहले body?

दोनों लूप की तुलना कीजिए। अंत में a और b बताइए तथा दोनों में body और जाँच की संख्या दीजिए।

C
#include <stdio.h>

int main(void)
{
    int a=0,b=0;
    while(a<0){a++;}
    do{b++;}while(b<0);
    printf("%d %d\n",a,b);
    return 0;
}

अभ्यास प्रश्न 4: अंतिम पद शामिल करने वाली शर्त

for(i=1; ___; i++) में ऐसी शर्त भरिए कि body sum+=i ठीक 1 से 5 तक जोड़े। अंतिम योग और गिनतियाँ दीजिए। मान लें कि शुरुआत में sum=0 है और body में केवल sum+=i है।

अभ्यास के पूरे हल और गलत उत्तरों का कारण

अभ्यास प्रश्न 1 का हल

  1. sum=0 से शुरू करें; आरंभ में i=2 होता है।
  2. 2<9 सही है। body, 2 जोड़ती है: sum=2। अपडेट, 3 जोड़ता है: i=5।
  3. 5<9 सही है। body, 5 जोड़ती है: sum=7। अपडेट: i=8।
  4. 8<9 सही है। body, 8 जोड़ती है: sum=15। अपडेट: i=11।
  5. 11<9 गलत है। बाहर निकलें। आउटपुट: 15 11, फिर नई पंक्ति।

body में 2,5,8 आते हैं। तीन body, तीन अपडेट और चार जाँच हैं। अंतिम i को 8 कहना अंतिम अपडेट छोड़ता है। अंतिम i को 9 कहना मान लेता है कि लूप ठीक सीमा पर रुकेगा, जबकि अपडेट 8 से 11 पर छलाँग लगाता है। sum=26 में असफल जाँच के बाद 11 को भी जोड़ दिया गया है। इनवेरिएंट यह है कि sum में वर्तमान i से पहले body में आए ठीक उन्हीं पदों का योग है। 3 की धनात्मक बढ़ोतरी अंततः i को सीमा के पार ले जाती है।

अभ्यास प्रश्न 2 का हल

  1. शुरुआत: n=30, count=0। जाँच 30>0 सही है।
  2. पहली body: n=30/3=10, count=1। अगली जाँच 10>0 सही है।
  3. दूसरी body: n=10/3=3, count=2। अगली जाँच 3>0 सही है।
  4. तीसरी body: n=3/3=1, count=3। अगली जाँच 1>0 सही है।
  5. चौथी body: n=1/3=0, count=4। अंतिम जाँच 0>0 गलत है।

चार body और पाँच जाँच हैं; आउटपुट 4 0, फिर नई पंक्ति है। तीन body कहना गलत है क्योंकि n=1 पर भी n>0 सही है। पाँच body कहने में अंतिम गलत जाँच को body गिन लिया गया है। अंतिम n को भिन्न मान देना गलत है, क्योंकि हर भाग के दोनों operands int हैं। n>0 रहने पर 3 से भाग इस धनात्मक पूर्णांक को सख्ती से घटाता है, 1 से 0 वाला चरण भी ऐसा करता है, इसलिए लूप रुकता है। count ठीक पूरे हो चुके भागों की संख्या रखता है।

अभ्यास प्रश्न 3 का हल

  1. शुरुआत: a=0, b=0।
  2. while की जाँच a<0 अर्थात 0<0 गलत है। उसकी body शून्य बार और जाँच एक बार होती है; a में 0 रहता है।
  3. do की body पहली जाँच से पहले चलती है। b++ से b, 0 से 1 होता है।
  4. उसकी जाँच b<0 अब 1<0 है, जो गलत है। do में एक body और एक जाँच हुई।
  5. आउटपुट: 0 1, फिर नई पंक्ति।

0 0 की जोड़ी में do की शर्त को body से पहले जाँच लिया गया है। 1 1 में while को भी गलती से एक शुरुआती body दे दी गई है। do की जाँच अपडेट हुआ b=1 पढ़ती है, शुरुआती b=0 नहीं। किसी भी गलत जाँच के बाद अतिरिक्त अपडेट नहीं होता।

अभ्यास प्रश्न 4 का हल

C
#include <stdio.h>

int main(void)
{
    int sum=0;
    for(int i=1;i<=5;i++){sum+=i;}
    printf("%d\n",sum);
    return 0;
}

for के आरंभ में int i=1 की घोषणा ही शुरुआती मान देती है। यह i उसी लूप का है और उसके बाद उपलब्ध नहीं है; इसलिए printf केवल sum छापता है। अगले पाठ में स्थानीय चर के scope पर फिर आएँगे।

i<=5 लिखें; इस पूर्णांक क्रम के लिए i<6 भी सही है। body में i=1,2,3,4,5 आते हैं और चलता योग 1,3,6,10,15 बनता है। पाँचवीं body के बाद i++ से लूप वाला i, 6 हो जाता है। छठी जाँच गलत होती है, इसलिए आउटपुट 15 और नई पंक्ति है। एक आरंभ, पाँच body, पाँच अपडेट और छह जाँच हैं।

i<5 शर्त में पद 5 छूटता है और योग 10 आता है। i<=6 में अनचाहा 6 जुड़ता है और योग 21 आता है। किसी और पद-क्रम से संयोग से 15 छप जाए, तब भी “ठीक 1 से 5 तक” की माँग पूरी नहीं होती। संचायक 0 से शुरू होता है और फिर शून्य पर रीसेट नहीं होता। हर जाँच से पहले वह वर्तमान i से छोटे धनात्मक पूर्णांकों का योग है; असफल जाँच तक पहुँचने पर इच्छित पाँच पद जुड़ चुके हैं।

लूप को फ़ंक्शन में ले जाना

अब आप अंतिम सफल body और अंतिम गलत जाँच अलग कर सकते हैं, संचायक का अर्थ बता सकते हैं और समझा सकते हैं कि छोटा लूप क्यों रुकता है। अगले पाठ में सीमित लूप को फ़ंक्शन के अंदर रखेंगे और उसके स्थानीय चर, caller के चरों से अलग ट्रेस करेंगे।

उदाहरण से समझें

मान लें छोटी सूची की कीमतें एक चलते योग में जोड़नी हैं। हर जोड़ से पहले एक निशान बताता है कि अगली वस्तु कौन-सी है। for पहले निशान रखता है, जाँचता है कि वह वस्तु सूची में है या नहीं, उसकी कीमत जोड़ता है, फिर निशान आगे बढ़ाता है। अंतिम जोड़ के बाद भी निशान सूची के पार बढ़ता है; अगली जाँच काम रोकती है। इसलिए निशान की अंतिम जगह और जोड़ के लिए उपयोग हुई अंतिम जगह अलग हैं।

चलता योग पहले की वस्तुओं को याद रखता है। हर नई वस्तु से पहले उसे मिटाने पर केवल अंतिम कीमत बचेगी। while में भी यही विचार है, लेकिन निशान आगे बढ़ाने का काम body पर है। do...while ऐसा है जैसे एक काम करने के बाद पूछना कि उसे दोहराना है या नहीं; उसे तभी चुनें जब वह पहला काम सच में चाहिए। यह उपमा स्रोत-कोड का क्रम समझाती है, processor का समय नहीं।

सूत्र और नियम

  • for: एक बार आरंभ → जाँच → body → अपडेट → फिर जाँच।
  • for की गलत जाँच अगली body और उसका अपडेट, दोनों छोड़ती है। अंतिम सफल body के बाद भी उसका अपडेट होता है।
  • while: जाँच → body → जाँच। प्रगति का अपडेट body या शर्त में दिया जाना चाहिए; हमारे उदाहरणों में अपडेट body में हैं।
  • do...while: body → जाँच। अंत के while(condition) के बाद semicolon है।
  • केवल गलत शर्त पर निकलने वाले सामान्य सीमित लूप में: for के k body, k अपडेट और k+1 जाँच; while के k body और k+1 जाँच; do...while के k body और k जाँच, जहाँ k>=1 है।
  • पहले-जाँच वाले लूप में शून्य body और एक जाँच हो सकती है। शुरुआत से खाली सीमा में संचायक नहीं बदलता।
  • संचायक को लूप से पहले मान दें; हर बार रीसेट करने के बजाय उसके वर्तमान मान में जोड़ें।
  • एक जाँच-बिंदु पर इनवेरिएंट बताएँ; शुरू में उसका सही होना और body/अपडेट से बना रहना जाँचें।
  • सही योग से अलग यह समझाएँ कि गलत शर्त की ओर प्रगति कैसे होती है। बीच की गणनाएँ भी int में निरूपित होनी चाहिए।
  • body में आए मान, रुकने वाला मान, body/जाँच/अपडेट की गिनती और अंतिम अवस्था दें। स्रोत के चरणों की गिनती मशीन निर्देशों की गिनती नहीं है।

इस पाठ के नोट्स

प्रगति सहेजने के लिए साइन इन करें। साइन इन