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

द्वि-आयामी ऐरे में पंक्तियाँ और कॉलम

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

एक ऐरे के भीतर छोटे ऐरे हो सकते हैं

एक-आयामी ऐरे में हर तत्व का एक इंडेक्स होता है। अब मान लें कि हर पंक्ति में तीन पूर्णांक हैं और ऐसी दो पंक्तियाँ हैं। हम अब भी एक समय पर एक ऑब्जेक्ट चुनते हैं: पहले पंक्ति, फिर उस पंक्ति के भीतर पूर्णांक। इन दोनों चयनों को अलग रखने से लूप, पॉइंटर के प्रकार और सीमाएँ समझना आसान होता है।

इस पाठ में आप द्वि-आयामी इनिशियलाइज़र पढ़ेंगे, नेस्टेड लूप का ट्रेस करेंगे, संचयक को सही जगह रीसेट करेंगे, चुने हुए कॉलम का स्कैन करेंगे और पंक्तियों के बीच की चाल को पूर्णांकों के बीच की चाल से अलग पहचानेंगे। हर पॉइंटर को एक-दूसरे की जगह इस्तेमाल होने वाला एड्रेस मानने के बजाय पंक्ति का प्रकार समझाएँगे।

पहले से आवश्यक बातें: इनिशियलाइज़ किए ऐरे और वैध इंडेक्स; लूप-जाँच, अपडेट और संचयक; पॉइंटर के लक्ष्य और कॉपी हुए मान; ऐरे से पॉइंटर में रूपांतरण; तत्वों में गिनी पॉइंटर की चाल, one-past सीमा, sizeof, size_t और %zu। पहले का कॉल-मॉडल नहीं बदलता, लेकिन इस पाठ में नए सहायक फ़ंक्शन के इंटरफ़ेस की जरूरत नहीं है।

C11 पढ़ाने के लिए हमारा चुना संस्करण है, GATE पाठ्यक्रम का निर्धारित संस्करण नहीं। यहाँ सभी वास्तविक ऐरे के आयाम धनात्मक स्थिरांक हैं और उनके int तत्व इनिशियलाइज़ किए गए हैं। int की सभी गणनाएँ −32767 से 32767 के भीतर रहती हैं। हर पूरा प्रोग्राम स्वतंत्र है। स्पष्ट रूप से चिह्नित वर्गीकरण-अंश कभी कंपाइल या निष्पादित न करें। int का कोई विशेष बाइट आकार या पॉइंटर चौड़ाई नहीं मानी गई है। परिवर्तनीय लंबाई के ऐरे, मेमोरी आवंटन, सामान्य डबल-पॉइंटर प्रोग्रामिंग और मैट्रिक्स एल्गोरिदम इस पाठ के बाहर हैं।

दोनों आयाम और इनिशियलाइज़र के दोनों स्तर पढ़ें

int grid[2][3] = {{4, 1, 7}, {2, 6, 3}}; की घोषणा ऐसे पढ़ें: दो तत्वों का ऐरे, जिसका हर तत्व खुद तीन int ऑब्जेक्टों का ऐरे है। बाहरी ऐरे का तत्व पूरी पंक्ति है। पंक्ति का तत्व एक पूर्णांक है।

पंक्ति इंडेक्सकॉलम 0कॉलम 1कॉलम 2
0417
1263

भीतर के पहले ब्रेस-समूह से पंक्ति 0 इनिशियलाइज़ होती है और दूसरे से पंक्ति 1। आयाम गिनतियाँ हैं: दो पंक्तियाँ और हर पंक्ति में तीन कॉलम। वे अंतिम वैध इंडेक्स नहीं हैं।

  • grid[0] तीन पूर्णांकों वाली पहली पंक्ति चुनता है
  • grid[1][2] पंक्ति 1 का कॉलम 2 चुनता है, जिसका वर्तमान मान 3 है
  • grid[0][1] दूसरा पूर्णांक चुनता है, जिसका वर्तमान मान 1 है
  • grid[r][c] पढ़ने या लिखने के लिए 0 <= r && r < 2 और 0 <= c && c < 3, दोनों चाहिए

पहले इंडेक्स का अर्थ “जिस आयाम का स्कैन हो रहा है” नहीं है। इस घोषणा में वह हमेशा पंक्ति चुनता है। कॉलम का स्कैन करते समय कॉलम इंडेक्स स्थिर रखकर पंक्ति इंडेक्स बदलते हैं। दोनों इंडेक्स की जगह बदलने से चुना ऑब्जेक्ट बदलता है और दूसरी सीमा भी टूट सकती है।

ब्रेस का समूह छोड़े हुए मान भी स्पष्ट करता है। int ready[2][3] = {{5}, {2, 4}}; की शुरुआती पंक्तियाँ {5, 0, 0} और {2, 4, 0} हैं। शून्य इनिशियलाइज़र के नियम से आते हैं, automatic स्टोरेज के बारे में किसी सामान्य गारंटी से नहीं। बिना इनिशियलाइज़र वाली automatic घोषणा को ये गारंटी वाले शून्य नहीं मिलते। हमारे निष्पादन योग्य प्रोग्रामों में पढ़ने से पहले तत्व इनिशियलाइज़ हैं।

नेस्टेड लूप में बाहरी इंडेक्स बढ़ने से पहले भीतरी काम पूरा होता है

नेस्टेड लूप किसी दूसरे लूप की बॉडी के भीतर का लूप है। पंक्ति स्कैन करते समय r स्थिर रखें और c को 0, 1 और 2 पर जाने दें। पूरा भीतरी लूप समाप्त होने के बाद ही बाहरी लूप r बढ़ाता है।

पहला हल किया हुआ प्रोग्राम इस निष्पादन-क्रम से पढ़ें:

  1. बाहरी r को 0 से इनिशियलाइज़ करें और r < 2 जाँचें
  2. उस पंक्ति की बाहरी बॉडी में जाएँ, row_total को 0 से इनिशियलाइज़ करें और भीतरी लूप तक पहुँचें
  3. भीतरी c को 0 से इनिशियलाइज़ करें, c < 3 जाँचें, तत्व संसाधित करें और फिर c अपडेट करें
  4. भीतरी जाँच/बॉडी/अपडेट तब तक दोहराएँ जब तक c = 3 पर जाँच असफल न हो
  5. बाहरी बॉडी के बाकी स्टेटमेंट चलाएँ, जिनमें इस पंक्ति का योग सहेजना भी है
  6. बाहरी बॉडी पूरी करें, r अपडेट करें और बाहरी शर्त फिर जाँचें

अगली सफल बाहरी जाँच पर बॉडी फिर int row_total = 0; तक पहुँचती है। इससे नई पंक्ति का योग शून्य से शुरू होता है। फिर से for (int c = 0; ...) तक पहुँचने पर उस भीतरी स्कैन की शुरुआत भी कॉलम 0 से होती है। पिछले भीतरी स्कैन का अंतिम मान 3 अगली पंक्ति में आगे न ले जाएँ। हर घोषित c भीतरी लूप के अपने निष्पादन तक स्थानीय है; उस लूप के बाद उसका उपयोग नहीं कर सकते। ये स्रोत-स्तर के निष्पादन चरण हैं, मशीन निर्देशों के बारे में दावे नहीं।

रीसेट की जगह संचयक का उद्देश्य बताती है। अलग-अलग पंक्ति-योग के लिए हर पंक्ति पर एक बार रीसेट करें। हर भीतरी बॉडी में रीसेट करेंगे तो उसी पंक्ति के पहले मान छूट जाएँगे। केवल बाहरी लूप से पहले रीसेट करेंगे तो मान अगली पंक्तियों में भी चलता रहेगा। तीनों स्थान ऐरे की सीमा के भीतर रह सकते हैं; केवल एक अलग-अलग पंक्ति-योग का उद्देश्य पूरा करता है।

हल किया हुआ उदाहरण 1 अलग पंक्ति-योग और पूरे ग्रिड का योग

दोनों पंक्ति-योग और अंत का संयुक्त योग बताएँ। इनिशियलाइज़ किया एक-आयामी totals ऐरे पूरे हुए परिणाम रखता है; उसका स्टोरेज grid से अलग है।

C
#include <stdio.h>

int main(void)
{
    int grid[2][3] = {{4, 1, 7}, {2, 6, 3}};
    int totals[2] = {0, 0};
    int grand_total = 0;
    for (int r = 0; r < 2; r++) {
        int row_total = 0;
        for (int c = 0; c < 3; c++) {
            row_total += grid[r][c];
        }
        totals[r] = row_total;
        grand_total += row_total;
    }
    printf("%d %d | %d\n", totals[0], totals[1], grand_total);
    return 0;
}

भीतरी बॉडी में देखने का क्रम और पंक्ति-योग हैं:

देखने की संख्याrcपढ़ा मानजोड़ के बाद row_total
10044
20115
302712
41022
51168
612311

पंक्ति 0 का भीतरी लूप पूरा होने पर totals[0] में 12 रखें और grand_total में 12 जोड़ें। पंक्ति 1 का भीतरी लूप पूरा होने पर totals[1] में 11 रखें और वर्तमान 12 में 11 जोड़ें। आउटपुट: 12 11 | 23, फिर नई पंक्ति। grid अपरिवर्तित है।

भीतरी जाँच से ठीक पहले के बिंदु पर r वर्तमान पंक्ति पहचानता है और row_total उस पंक्ति के पहले ही देखे गए कॉलम 0 से c - 1 तक का योग है। c = 0 पर यह समूह खाली है और योग शून्य है। c = 3 वाली असफल जाँच पर इसमें पूरी पंक्ति शामिल है। हर बाहरी-जाँच बिंदु पर grand_total पहले पूरी हो चुकी सभी पंक्तियों का योग है; totals उनके अलग-अलग योग रखता है। totals की अभी संसाधित नहीं हुई प्रविष्टियाँ अपने शुरुआती शून्य रखती हैं। इन अर्थों से दोनों शुरुआती व्यवस्थाएँ स्पष्ट होती हैं: row_total हर पंक्ति पर फिर शुरू होता है, जबकि grand_total पूरी हो चुकी पंक्तियों को सँभालकर रखता है।

बाहरी लूप में दो बॉडी और शर्त की तीन जाँचें हैं। उसकी हर बॉडी में भीतरी लूप की तीन बॉडी और चार जाँचें होती हैं। कुल मिलाकर छह भीतरी बॉडी और आठ भीतरी शर्त-जाँचें हैं; हर पंक्ति के लिए अलग अंतिम असफल जाँच भी गिनी गई है। दोनों इंडेक्स अपनी सीमित सीमा की ओर एक-एक बढ़ते हैं। पंक्ति 2 या कॉलम 3 पर कोई बॉडी नहीं चलती।

दूसरी पंक्ति का योग 23 बताना पहली पंक्ति का योग दूसरी में आगे ले जाना होगा। पहली पंक्ति का योग 7 बताना उस पंक्ति की पिछली प्रविष्टियाँ छोड़ देता है। (0,0), (1,1) से शुरू होने वाला क्रम दोनों इंडेक्स को गलत तरह से एक साथ बढ़ाता है: वास्तविक भीतरी लूप बाहरी अपडेट से पहले तीनों कॉलम पूरे करता है।

हल किया हुआ उदाहरण 2 कॉलम स्थिर रखें, फिर चुने बदलाव से पहले पढ़ें

इस बार केवल कॉलम 1 चुना गया है। हर देखे पूर्णांक का पुराना मान before_total में जोड़ें, और केवल 5 से कम होने पर उसमें 4 बढ़ाएँ। चुना कॉलम 0 से 2 के भीतर है और लूप दोनों मौजूद पंक्तियों पर जाता है। दिखाए गए छोटे जोड़ int में समाते हैं।

C
#include <stdio.h>

int main(void)
{
    int grid[2][3] = {{3, 8, 1}, {6, 2, 5}};
    int column = 1;
    int before_total = 0;
    int changed = 0;
    for (int r = 0; r < 2; r++) {
        before_total += grid[r][column];
        if (grid[r][column] < 5) {
            grid[r][column] += 4;
            changed++;
        }
    }
    printf("%d %d | %d %d\n",
           before_total, changed, grid[0][column], grid[1][column]);
    return 0;
}
देखी पंक्तिस्थिर कॉलमपुराना मानजोड़ के बाद before_totalसंभव लेखन के बाद मानchanged
018880
1121061

पंक्ति 0 पर 8 जोड़ें। शर्त 8 < 5 असत्य है, इसलिए तत्व 8 रहता है। पंक्ति 1 पर पुराना 2 जोड़ने से before_total = 10 होता है; शर्त सत्य है, इसलिए 6 लिखें और changed को 1 करें। अंतिम ऐरे की पंक्तियाँ {3, 8, 1} और {6, 6, 5} हैं। आउटपुट: 10 1 | 8 6, फिर नई पंक्ति।

हर लूप-जाँच से पहले before_total में चुने कॉलम की पहले ही देखी गई पंक्तियों के मूल मानों का योग है। changed गिनता है कि उनमें कितनी स्थितियों पर लेखन हुआ। हर चुनी स्थिति केवल एक बार देखी जाती है; इस लूप ने दूसरे कॉलम न पढ़े हैं, न बदले हैं। r = 2 पर असफल जाँच के बाद ये बातें पूरे चुने कॉलम के लिए सही हैं। दो बॉडी और तीन लूप-जाँचें हैं।

कॉलम का नया योग 14 है, लेकिन before_total 10 रहता है। सहेजा हुआ पूर्णांक योग बाद के तत्व-बदलावों के साथ अपने आप नहीं बदलता। इस चर का मान 14 बताना बताए हुए पढ़ने/लिखने के क्रम को उलट देता है। तीन बार जाना मानना कॉलम की गिनती को पंक्ति की सीमा बना देता है। एक स्थिर कॉलम के लिए नेस्टेड लूप आवश्यक नहीं है: केवल एक इंडेक्स बदलता है।

लेआउट स्टोरेज बताता है; लूप का क्रम देखने का क्रम बताता है

2-बाय-3 ऐरे के छह पूर्णांक इस क्रम में रखे होते हैं: grid[0][0], grid[0][1], grid[0][2], grid[1][0], grid[1][1], grid[1][2]। इसे row-major लेआउट कहते हैं। हर पंक्ति लगातार स्टोरेज में है और दोनों पंक्तियाँ बाहरी ऐरे के लगातार तत्व हैं। अगली पंक्ति शुरू होने से पहले दायाँ इंडेक्स एक पंक्ति में आगे बढ़ता है।

यदि एक int का आकार S C बाइट है, तो एक पंक्ति का आकार 3 * S और पूरे ग्रिड का आकार 2 * 3 * S है। यह आकार का प्रतीकात्मक वर्णन है, निष्पादन योग्य पॉइंटर अंकगणित या S को 4 मानने का दावा नहीं। इससे scalar पॉइंटर के रास्ते भीतरी ऐरे को छह-तत्वों वाला एक ही int ऐरे मानने की अनुमति भी नहीं मिलती।

प्रोग्राम कॉलम-पहले देखने का क्रम चुन सकता है, जबकि स्टोरेज row-major ही रहता है। कॉलम-पहले स्कैन का क्रम होगा (0,0), (1,0), (0,1), (1,1), (0,2), (1,2)। लूपों की नेस्टिंग बदलने से अभिगम का क्रम बदलता है, घोषित आकार या लेआउट नहीं। हर अभिगम को उसकी पंक्ति और कॉलम, दोनों की सीमाओं के भीतर रहना चाहिए। इन छोटे ट्रेस से हम हार्डवेयर के समय का कोई दावा नहीं करते।

पंक्ति का पॉइंटर पूर्णांक के पॉइंटर से अलग है

ज्ञात आकार int grid[2][3] लें। घोषणा int (*row)[3] = grid; तीन int ऑब्जेक्टों के ऐरे का पॉइंटर बनाती है। *row के चारों ओर गोल कोष्ठक महत्वपूर्ण हैं: घोषणा कहती है कि row के लक्ष्य तक जाने पर तीन पूर्णांकों का ऐरे मिलता है। हमें केवल यह एक नई पॉइंटर घोषणा चाहिए, सामान्य जटिल घोषणाएँ नहीं।

इस इनिशियलाइज़र में grid अपने पहले तत्व के पॉइंटर में बदलता है। उसका पहला तत्व पंक्ति grid[0] है, इसलिए परिणाम का प्रकार int (*)[3] है। वह सीधे int * तक नहीं पहुँच जाता और न int ** बनता है।

इसके विपरीत, int *cell = grid[0]; पहले पंक्ति 0 चुनता है और फिर उस पंक्ति के व्यंजक को उसके पहले पूर्णांक grid[0][0] के पॉइंटर में बदलता है। रूपांतरण ऐरे के दूसरे स्तर पर हुआ है।

बताए हुए संदर्भ का व्यंजकप्रकार या चुना ऑब्जेक्ट
वास्तविक ऑब्जेक्ट gridदो ऐरे का ऐरे; प्रत्येक में तीन int ऑब्जेक्ट
row के इनिशियलाइज़र के रूप में gridint (*)[3], पंक्ति 0 की ओर
cell के इनिशियलाइज़र के रूप में grid[0]int *, पंक्ति 0, कॉलम 0 की ओर
&grid[0]int (*)[3], पूरी पहली पंक्ति का पॉइंटर
&grid[0][0]int *, पहले पूर्णांक का पॉइंटर
grid[1][2]पंक्ति 1, कॉलम 2 का int ऑब्जेक्ट

शुरुआत में row = grid होने पर row + 1 पंक्ति 1 की ओर जाता है। एक कदम उस प्रकार का एक पूरा ऑब्जेक्ट गिनता है जिसकी ओर पॉइंटर है; यहाँ वह तीन पूर्णांकों की पूरी पंक्ति है। cell = grid[0] होने पर cell + 1 पंक्ति 0, कॉलम 1 की ओर जाता है। उसका कदम उसी भीतरी ऐरे में एक int गिनता है। इन दोनों व्यंजकों में से कोई भी अकेले अपने पॉइंटर चर या रखे हुए पूर्णांक नहीं बदलता। row = row + 1; जैसा असाइनमेंट पॉइंटर की नई स्थिति सहेजता है।

row के लक्ष्य तक जाने पर चुनी हुई पंक्ति मिलती है; फिर (*row)[c] उस पंक्ति का पूर्णांक c चुनता है। शुरुआत में row पंक्ति 0 पर हो, तो row[1][2] पंक्ति 1, कॉलम 2 चुनता है। row को पंक्ति 1 पर ले जाने के बाद उसके कॉलम 2 के लिए (*row)[2] या row[0][2] लें। पॉइंटर के सापेक्ष सबस्क्रिप्ट वर्तमान पॉइंटर स्थिति से गिना जाता है, ठीक पिछले पाठ की तरह।

दोनों स्तरों की one-past स्थितियाँ भी अलग हैं। grid + 2 दो-पंक्तियों वाले बाहरी ऐरे के बाद का वैध सिरा है; वहाँ अभिगम के लिए कोई पंक्ति नहीं है। grid[0] + 3 पंक्ति 0 के तीन पूर्णांकों के बाद का वैध सिरा है; उस पॉइंटर के रास्ते पढ़ने या लिखने के लिए वहाँ कोई पूर्णांक नहीं है। अगली पंक्ति के पहले पूर्णांक तक पहुँचने के लिए grid[1][0] या वैध पंक्ति-पॉइंटर से चुनें। भीतरी ऐरे की सीमा पार करने के लिए scalar सिरे को डीरेफ़रेंस न करें। विशेष रूप से, grid[0][3] पढ़ना अवैध है, भले ही अगली पंक्ति उसके बाद रखी हो। लगातार स्टोरेज होने से उप-ऐरे की सीमा खत्म नहीं होती।

अंत में, int ** का अर्थ है किसी int-पॉइंटर ऑब्जेक्ट का पॉइंटर। यह ग्रिड पूर्णांकों की पंक्तियाँ रखता है; यह int * ऑब्जेक्टों का ऐरे नहीं रखता जिनके रास्ते डबल पॉइंटर चले। रूपांतरण से मिला पंक्ति-पॉइंटर प्रकार int (*)[3] और int ** असंगत हैं। एक को दूसरे की जगह न रखें और प्रकार का अंतर छिपाने के लिए कास्ट न जोड़ें। इस अंतर की पहचान ही इस पाठ में डबल-पॉइंटर का पूरा दायरा है।

जहाँ वास्तविक ऐरे उपलब्ध हो, वहाँ पंक्तियाँ और कॉलम गिनें

वास्तविक grid की घोषणा वाले स्थान पर sizeof grid / sizeof grid[0] का मान 2 है: पूरे ग्रिड का आकार भाग एक पंक्ति का आकार। sizeof grid[0] / sizeof grid[0][0] का मान 3 है: एक पंक्ति का आकार भाग एक पूर्णांक का आकार। इन sizeof व्यंजकों के ऐरे ऑपरेंड पॉइंटर में बदलने के बजाय अपने ऐरे प्रकार रखते हैं।

नीचे दोनों गिनतियाँ पिछले पाठ में बताए size_t चरों में रखेंगे; उन चरों को छापने के लिए %zu लें। row नाम का पॉइंटर चर अब भी पॉइंटर है, इसलिए sizeof row ग्रिड का कुल आकार नहीं देता और न उसकी पंक्ति-संख्या खोजता है। तीन कॉलम वाला स्थिर पंक्ति-प्रकार कदम का प्रकार निर्धारित करता है; वह उपलब्ध पंक्तियों की रनटाइम गिनती नहीं रखता। इस पाठ में द्वि-आयामी फ़ंक्शन पैरामीटर नहीं लेते।

हल देखने से पहले अभ्यास करें

ये चार मौलिक ट्रांसफ़र अभ्यास बिना अंक के हैं। संख्यात्मक उत्तर के साथ तर्क का बिंदु, चुने इंडेक्स या पॉइंटर के लक्ष्य भी दें। अभ्यास 3 और 4 के पाठ-ब्लॉक केवल वर्गीकरण के लिए हैं; उन्हें कभी कंपाइल या निष्पादित न करें।

अभ्यास 1 एक पंक्ति का योग और एक कॉलम का योग

आउटपुट और देखी गई स्थितियों के दोनों क्रम बताएँ। कौन-सी स्थिति दोनों गणनाओं में आती है? क्या इससे किसी एक गणना के भीतर वह दो बार गिनी जाती है?

C
#include <stdio.h>

int main(void)
{
    int grid[2][3] = {{5, 2, 9}, {4, 7, 1}};
    int row_total = 0;
    for (int c = 0; c < 3; c++) {
        row_total += grid[1][c];
    }
    int column_total = 0;
    for (int r = 0; r < 2; r++) {
        column_total += grid[r][2];
    }
    printf("%d %d\n", row_total, column_total);
    return 0;
}

अभ्यास 2 कॉलम-पहले स्कैन में गलत जगह रखे रीसेट की पहचान

उद्देश्य तीन अलग-अलग कॉलम-योग निकालना है। यह प्रोग्राम सीमाओं के भीतर रहता है, लेकिन क्या वह उद्देश्य पूरा करता है? वास्तविक आउटपुट, भीतरी बॉडी का ठीक देखने का क्रम और बाहरी तथा भीतरी लूप-शर्त की जाँचों की गिनती दें। फिर total का इनिशियलाइज़ेशन सही जगह ले जाएँ और सुधरा आउटपुट दें।

C
#include <stdio.h>

int main(void)
{
    int grid[2][3] = {{2, 4, 1}, {6, 3, 5}};
    int totals[3] = {0, 0, 0};
    int total = 0;
    for (int c = 0; c < 3; c++) {
        for (int r = 0; r < 2; r++) {
            total += grid[r][c];
        }
        totals[c] = total;
    }
    printf("%d %d %d\n", totals[0], totals[1], totals[2]);
    return 0;
}

अभ्यास 3 बदलते आयाम की सीमा सुधारें

इस असुरक्षित अंश को केवल पढ़ें। उद्देश्य कॉलम 2 का योग है। पहली सीमा-बाहरी जोड़ी पहचानें जिस तक यह पहुँचने की कोशिश करेगा। केवल लूप की सीमा सुधारें, फिर सुधरा योग दें और समझाएँ कि कॉलम को 1 करने से पंक्ति की सीमा क्यों नहीं सुधरेगी।

int grid[2][3] = {{1, 3, 2}, {7, 4, 6}};
int column = 2;
int total = 0;
for (int r = 0; r < 3; r++) {
    total += grid[r][column];
}

अभ्यास 4 पंक्ति-पॉइंटर और scalar पॉइंटर अलग रखें

पूरा आउटपुट और row तथा cell के अंतिम लक्ष्य बताएँ। दोनों आकार-अनुपात और हर पॉइंटर कदम समझाएँ।

C
#include <stddef.h>
#include <stdio.h>

int main(void)
{
    int grid[2][3] = {{5, 1, 4}, {2, 8, 6}};
    int (*row)[3] = grid;
    int *cell = grid[0] + 1;
    row = row + 1;
    (*row)[0] = *cell + 3;
    cell = cell + 1;
    *cell = (*row)[1] - 2;
    size_t rows = sizeof grid / sizeof grid[0];
    size_t columns = sizeof grid[0] / sizeof grid[0][0];
    printf("%zu %zu | %d %d %d | %d %d %d\n",
           rows, columns, grid[0][0], grid[0][1], grid[0][2],
           grid[1][0], grid[1][1], grid[1][2]);
    return 0;
}

अलग से, उसी जीवित 2-बाय-3 ग्रिड के संदर्भ में नीचे की हर चीज़ का वर्गीकरण करें। ये स्वतंत्र विकल्प हैं, बाद में क्रम से चलने वाले स्टेटमेंट नहीं। वैध अंतिम सिरा, अवैध तत्व-पठन और असंगत घोषणा अलग पहचानें।

grid + 2
grid[0] + 3
grid[0][3]
grid[1][3]
int **wrong = grid;

पूरे हल और गलत रास्तों की प्रतिक्रिया

अभ्यास 1 का हल

पहला लूप पंक्ति 1 स्थिर रखता है। वह (1,0), (1,1), (1,2) पर जाकर 4, 7 और 1 पढ़ता है। उसके क्रमिक योग 4, 11 और 12 हैं। हर जाँच पर row_total पंक्ति 1 के पहले ही देखे शुरुआती हिस्से का वर्णन करता है। दूसरा लूप कॉलम 2 स्थिर रखकर (0,2), (1,2) पर जाता है और 9 फिर 1 पढ़ता है। उसके स्वतंत्र क्रमिक योग 9 और 10 हैं।

आउटपुट: 12 10, फिर नई पंक्ति। ग्रिड अपरिवर्तित है। स्थिति (1,2) हर गणना में एक बार आती है; कोई भी लूप उसे दो बार नहीं देखता। संचयक दो अलग प्रश्नों के उत्तर देते हैं और उनकी चलती हुई अवस्था साझा नहीं है।

पहला योग 10 बताना माँगी पंक्ति के बजाय कॉलम की गणना करता है। दूसरा योग 22 बताना नए शून्य के बजाय पहले लूप के पूरे हुए 12 से शुरू करता है। हर लूप के बदलते इंडेक्स को अपने आयाम की सीमा चाहिए: पहले के लिए तीन कॉलम, दूसरे के लिए दो पंक्तियाँ।

अभ्यास 2 का हल

देखने का क्रम है (0,0), (1,0), (0,1), (1,1), (0,2), (1,2)। पढ़े मान 2, 6, 4, 3, 1 और 5 हैं। हर कॉलम पर रीसेट न होने से क्रमिक योग 2, 8, 12, 15, 16 और 21 हैं। इसलिए कॉलम पूरे होने पर 8, 15 और 21 सहेजे जाते हैं। वास्तविक आउटपुट: 8 15 21, फिर नई पंक्ति।

बाहरी लूप की तीन बॉडी और चार जाँचें हैं। हर भीतरी लूप की दो बॉडी और तीन जाँचें हैं, इसलिए कुल छह भीतरी बॉडी और नौ भीतरी जाँचें हैं। भीतरी घोषणा तक फिर पहुँचने पर हर नए कॉलम के लिए r दोबारा 0 से शुरू होता है। r = 2 पर भीतरी जाँच असफल होने से बाहरी लूप समाप्त नहीं होता।

int total = 0; को बाहरी बॉडी के भीतर, भीतरी लूप से पहले ले जाएँ:

C
#include <stdio.h>

int main(void)
{
    int grid[2][3] = {{2, 4, 1}, {6, 3, 5}};
    int totals[3] = {0, 0, 0};
    for (int c = 0; c < 3; c++) {
        int total = 0;
        for (int r = 0; r < 2; r++) {
            total += grid[r][c];
        }
        totals[c] = total;
    }
    printf("%d %d %d\n", totals[0], totals[1], totals[2]);
    return 0;
}

अब हर भीतरी-जाँच बिंदु पर यह अर्थ सही है: total केवल वर्तमान कॉलम की पहले ही देखी गई पंक्तियों का योग है। कॉलम 0 के योग 2 फिर 8 हैं; कॉलम 1 नए सिरे से शुरू होकर 4 फिर 7 देता है; कॉलम 2 नए सिरे से शुरू होकर 1 फिर 6 देता है। सुधरा आउटपुट: 8 7 6, फिर नई पंक्ति। देखने का क्रम, लूप-जाँचों की गिनती और ग्रिड के मान नहीं बदलते।

भीतरी बॉडी के भीतर रीसेट करने पर हर कॉलम का केवल अंतिम मान बचेगा, जिससे 6, 3 और 5 मिलेंगे। इंडेक्स क्या चुनते हैं यह बदले बिना लूप की सीमाएँ बदलने से अवैध अभिगम हो सकते हैं। मूल समस्या संचयक के जीवनकाल और रीसेट की जगह की है, लेआउट या पॉइंटर रूपांतरण की नहीं।

अभ्यास 3 का हल

वैध पंक्ति इंडेक्स 0 और 1 हैं, जबकि चुना कॉलम 2 हर पंक्ति में वैध है। प्रस्तावित लूप का पहला अवैध अभिगम (2,2) होगा: तीसरी पंक्ति मौजूद नहीं है। उसका व्यवहार अपरिभाषित है, इसलिए C11 मूल अंश के लिए कोई संख्यात्मक आउटपुट या गारंटी वाला आंशिक योग निर्धारित नहीं करता। कॉलम 1 पर जाने से भी पंक्ति 2 का अस्तित्व नहीं बनता।

r < 2 लें। पूरा सुधरा प्रोग्राम है:

C
#include <stdio.h>

int main(void)
{
    int grid[2][3] = {{1, 3, 2}, {7, 4, 6}};
    int column = 2;
    int total = 0;
    for (int r = 0; r < 2; r++) {
        total += grid[r][column];
    }
    printf("%d\n", total);
    return 0;
}

यह 2 और 6 पढ़ता है; क्रमिक योग 2 और 8 हैं। आउटपुट: 8, फिर नई पंक्ति। दो बॉडी और तीन जाँचें हैं; पंक्ति 2 पर असफल जाँच वहाँ तत्व तक पहुँचने से रोकती है। सुधार पूरे चुने कॉलम का उद्देश्य पूरा करता है। सीमा 1 करने से अवैध अभिगम तो बचेगा, लेकिन दूसरी पंक्ति छूट जाएगी। सीमा 3 गलत आयाम से आई थी, किसी मौजूद पंक्ति में तीसरा पूर्णांक न होने से नहीं।

अभ्यास 4 का हल

शुरुआत में row पंक्ति 0 की ओर है, जबकि cell पंक्ति 0, कॉलम 1 की ओर है। row में 1 का कदम अगली पूरी पंक्ति, यानी पंक्ति 1, चुनता है और cell नहीं बदलता। लेखन (*row)[0] = *cell + 3 पंक्ति 0, कॉलम 1 से 1 पढ़कर पंक्ति 1, कॉलम 0 में 4 लिखता है।

cell को 1 आगे बढ़ाने से वह पंक्ति 0 के भीतर ही कॉलम 2 चुनता है। फिर (*row)[1] पंक्ति 1, कॉलम 1 पढ़ता है, जिसका मान 8 है। घटाने से 6 मिलता है, जो cell के रास्ते पंक्ति 0, कॉलम 2 में लिखा जाता है। अंतिम पंक्तियाँ: {5, 1, 6} और {4, 8, 6}। अंतिम पॉइंटर लक्ष्य हैं: row के लिए पूरी पंक्ति 1 और cell के लिए पंक्ति 0, कॉलम 2।

पहला आकार-अनुपात दो पंक्तियों के स्टोरेज को एक पंक्ति के स्टोरेज से भाग देकर 2 देता है। दूसरा तीन-पूर्णांक वाली पंक्ति को एक पूर्णांक से भाग देकर 3 देता है। दोनों में संख्यात्मक बाइट आकार की जरूरत नहीं। दोनों गिनतियाँ size_t चरों में रखी हैं, इसलिए उनके प्रिंट फ़ॉर्मैट %zu हैं; छह तत्वों के मान int हैं, जिन्हें %d से छापते हैं। आउटपुट: 2 3 | 5 1 6 | 4 8 6, फिर नई पंक्ति।

स्वतंत्र वर्गीकरण:

  • grid + 2: पंक्तियों के बाहरी ऐरे का वैध one-past पॉइंटर; वहाँ अभिगम के लिए कोई पंक्ति नहीं है
  • grid[0] + 3: पंक्ति 0 के पूर्णांक ऐरे का वैध one-past पॉइंटर; इस सिरे के रास्ते कोई पूर्णांक पढ़ना या लिखना वैध नहीं है
  • grid[0][3]: पंक्ति 0 के बाहर अवैध पूर्णांक-पठन; अगली पंक्ति का लगातार स्टोरेज कॉलम 3 को वैध नहीं बनाता
  • grid[1][3]: पंक्ति 1 के बाहर अवैध पूर्णांक-पठन; उस पंक्ति में भी चौथा पूर्णांक नहीं है
  • int **wrong = grid;: असंगत पॉइंटर इनिशियलाइज़ेशन, जो C की constraint तोड़ता है और जिसके लिए diagnostic आवश्यक है। grid, int (*)[3] देता है, int ** नहीं। आउटपुट जानने के लिए चलाने वाली यह वैध C11 घोषणा नहीं है

पहले लेखन से पंक्ति 0 बदलना बताने पर पंक्ति के आकार वाला कदम छूट जाता है। cell को चलाने पर row भी चलना मानना स्वतंत्र पॉइंटर ऑब्जेक्टों को मिलाता है। दोनों अवैध कॉलम-पठनों को छह-पूर्णांकों के सपाट स्कैन का हिस्सा मानना भीतरी ऐरे की सीमाएँ हटा देता है। प्रकार, वर्तमान लक्ष्य और लागू ऐरे को साथ रखें।

आगे बढ़ने से पहले

दोनों इंडेक्स को एक साथ बढ़ाए बिना नेस्टेड लूप का ट्रेस करें। चुनी रीसेट-जगह, अभिगम की दोनों सीमाएँ और हर पॉइंटर कदम तय करने वाला प्रकार समझाएँ। जब इन व्याख्याओं को पिछले पाठों के कॉपी हुए मानों, कॉलर में दिखने वाले लेखन और one-past तर्क से जोड़ सकें, तब आप एकीकृत पुनराभ्यास के लिए तैयार हैं। पुनराभ्यास कोई नया पढ़ाई-विषय नहीं जोड़ता। यह मॉड्यूल ऐरे और पॉइंटर के शुरुआती तर्क तक सीमित है, पूरी C या पूरा GATE CS पाठ्यक्रम नहीं।

अर्थ-संबंधी तथ्य-जाँच: WG14 N1570, खंड 5.1.1.3p1, 6.2.5p20, 6.3.2.1p3, 6.5.2.1p2–3, 6.5.3.2p3–4, 6.5.3.4, 6.5.6p8, 6.5.16.1p1, 6.7.6.2, 6.7.9p11, p17–21, 6.8p3, 6.8.5.3p1, 7.19 और 7.21.6.1। पढ़ाने की सभी व्याख्याएँ, उदाहरण, ट्रेस और अभ्यास मौलिक हैं। यह उल्लेख पुराने स्रोत-हिसाब या सभी अधिकारों की समग्र मंज़ूरी का प्रमाण नहीं है।

सूत्र और नियम

  • int grid[2][3] दो पंक्तियों का ऐरे है; हर पंक्ति तीन int ऑब्जेक्टों का ऐरे है
  • grid[r][c] पहले पंक्ति, फिर पूर्णांक चुनता है; दोनों आयामों की सीमाएँ अलग जाँचें
  • पंक्ति-बाहरी नेस्टेड लूप में अगली बाहरी पंक्ति-अपडेट से पहले भीतरी कॉलम लूप पूरा करें
  • भीतरी लूप तक फिर पहुँचने पर उसका इनिशियलाइज़र फिर शुरू होता है; उद्देश्य के अनुसार हर पंक्ति या कॉलम पर अलग योग एक बार रीसेट करें
  • किसी निश्चित बिंदु पर संचयक का अर्थ बताएँ, जिसमें पुराने या अपडेट किए मानों का अंतर भी हो
  • स्थिर कॉलम के स्कैन में पंक्ति इंडेक्स बदलता है और सीमा पंक्तियों की गिनती होती है
  • row-major स्टोरेज पंक्ति-पहले देखने को अनिवार्य नहीं करता और scalar पॉइंटर से भीतरी ऐरे की सीमा पार करके पढ़ने की अनुमति नहीं देता
  • grid, int (*)[3] में बदलता है; grid[0] जैसी चुनी पंक्ति int * में बदल सकती है; इनमें से कोई इस ग्रिड को int ** नहीं बनाता
  • पंक्ति-पॉइंटर का एक कदम अगली पंक्ति चुनता है; scalar पॉइंटर का एक कदम उसकी अपनी पंक्ति का अगला पूर्णांक चुनता है
  • वास्तविक ऐरे पर पूरा आकार / पंक्ति का आकार पंक्तियाँ गिनता है, और पंक्ति का आकार / पूर्णांक का आकार कॉलम गिनता है; size_t और %zu लें

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

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