ال

الكتل في C

{zero: "لا توجد تمارين", one: "1 تمرين", two: "تمرينان", few: "%{count} تمارين", many: "%{count} تمرينًا", other: "%{count} تمرين"}

نبذة عن الكتل

الكتلة مجموعة من العبارات والإعلانات مجمَّعة في وحدة نحوية واحدة. وغالبًا ما تُعرَّف الكتل داخل الأقواس المعقوفة، لكن ليس دائمًا. ويمكن أن تتداخل الكتل داخل الدوال وداخل كتل أخرى.

في المثال التالي، يُعرَّف number داخل الدالة main. ويُعرَّف flag داخل الكتلة if المتداخلة داخل الدالة main. والمتغيرات المعرَّفة داخل كتلة صالحة داخل تلك الكتلة فقط. لذا فإن محاولة تصريف الكود تؤدي إلى خطأ في التصريف.

#include <stdio.h>

int main() {

  int number = 1;

  if (number == 1) {
      int flag = 1;
      printf("%d", flag);
  }

  // this line generates a compilation error
  // error: 'flag' undeclared (first use in this function)
  printf("%d", flag);
    
}

قد تكون رسالة الخطأ مربكة، لأن flag مُستخدَم فعلًا في موضع سابق داخل الدالة. غير أن flag مُعرَّف داخل كتلة ضمن الدالة. تستطيع الكتلة الداخلية أن «ترى» المتغير number في الدالة الخارجية، لكن الدالة الخارجية لا تستطيع أن «ترى» المتغير flag المعرَّف داخل كتلتها الداخلية. وبمجرد أن يخرج التنفيذ من الكتلة، يُتلَف المتغير flag تلقائيًا ولم يعد متاحًا للبرنامج. أما الدالة main نفسها فترى المعرّف flag للمرة الأولى في عبارة printf الأخيرة. وإذا حُذفت عبارة printf الأخيرة، سيُصرَّف البرنامج ويطبع 1 عند تشغيله. والسبب أن المتغير flag صالح في الكتلة التي تحتوي على عبارة printf الأولى.

بدءًا من C99، تُعدّ عبارات if وfor وwhile وdo ذات السطر الواحد كتلًا أيضًا. وفي المثال التالي، ورغم أن i غير معرَّف داخل أقواس معقوفة، فإنه لا يوجد إلا في الكتلة المعرَّفة بعبارة for، ولا تراه الدالة main الخارجية.

#include <stdio.h>

int main() {

    for (int i = 0, j = 11; i < j; i+=2)
        printf("%d", i);

    // this line generates a compilation error
    // error: 'i' undeclared (first use in this function)
    printf("%d", i);
    
}

وإذا حُذفت عبارة printf الأخيرة، سيُصرَّف البرنامج ويطبع 0 2 4 6 8 10 عند تشغيله. وبعض اللغات، مثل Go وRust، تنص صراحةً على أن عبارات الشرط والحلقات ذات السطر الواحد تُشكّل كتلة. وهي تشترط أن تظل هذه العبارات ذات السطر الواحد محاطة بأقواس معقوفة.

وتعمل الدالة إلى حدٍّ ما عمل الكتلة. فمعرّف المتغير المعرَّف في دالة لا يمكن رؤيته خارج الدالة، تمامًا كما لا يمكن رؤية معرّف المتغير المعرَّف في كتلة خارج تلك الكتلة. ويمكن أن يحمل متغيران في دالتين الاسم نفسه، لأن الدالتين لا ترى إحداهما داخل الأخرى، تمامًا كما يمكن أن يحمل متغيران في كتلتين الاسم نفسه، لأن الكتلتين لا ترى إحداهما داخل الأخرى. لكن الدوال والكتل ليست متماثلة. فلا يمكن تعريف دالة داخل دالة أخرى، بينما يمكن تداخل كتلة داخل كتلة أخرى. كما أن بعض المعرّفات في الكتلة يمكن رؤيتها خارج الكتلة. وفي المثال التالي، لن يُصرَّف الكود

#include <stdio.h>

int main() {

  {
      int i = 0;
      begin:
      printf("i == %d\n", i);
      if (i == 0) {
          i = 1;
          goto begin;
      }
  }

  {
      int i = 1;
      begin:
      printf("i == %d\n", i);
      if (i == 1) {
          i = 0;
          goto begin;
      }
  }

}

يفشل الكود أعلاه في التصريف مع الخطأ التالي

/tmp/gH2TVYnTSJ.c: In function 'main':
/tmp/gH2TVYnTSJ.c:17:5: error: duplicate label 'begin'
   17 |     begin:
      |     ^~~~~
/tmp/gH2TVYnTSJ.c:7:5: note: previous definition of 'begin' was here
    7 |     begin:
      |     ^~~~~

رغم أنه يمكن تعريف i في كل كتلة، فإن أسماء label يجب أن تكون فريدة داخل الدالة. والسبب أن labels لها نطاق الدالة، أي أن الدالة ترى كل label أيًّا كان موضع تعريفه فيها. لذا، وخلافًا للمتغيرات، يجب أن يحمل كل label اسمًا فريدًا داخل الدالة نفسها أيًّا كان موضع تعريفه فيها. ولكي يعمل الكود يجب تغيير اسم أحد labels ليكون كلاهما فريدًا، كما في المثال التالي

#include <stdio.h>

int main() {

    {
        int i = 0;
        begin:
        // prints
        // i == 0
        // i == 1
        printf("i == %d\n", i);
        if (i == 0) {
            i = 1;
            goto begin;
        }
    }

    {
        int i = 1;
        start:
        // prints
        // i == 1
        // i == 0    
        printf("i == %d\n", i);
        if (i == 1) {
            i = 0;
            goto start;
        }
    }

}

يمكن «تكديس» العبارات ذات السطر الواحد بحيث تظل داخل كتلة واحدة دون استخدام أقواس معقوفة. وفي المثال أدناه، تحتوي عبارة for على عبارة if التي تحتوي بدورها على عبارة printf. ورغم وجود عبارتين إجمالًا في كتلة for، فإن ما يُقيَّم كجسم لحلقة for هو عبارة if ذات السطر الواحد فقط. وتُقيَّم بدورها عبارة printf ذات السطر الواحد التابعة لعبارة if كجسم لها. وإذا كانت الكتل المتداخلة كلها بسطر واحد فقط، فلا تحتاج أي منها إلى أقواس معقوفة. لاحظ أن التعليق لا يحتاج إلى أقواس معقوفة تحيط به هو وعبارة printf داخل كتلة if، لأن التعليق ليس عبارة. وتوضّح المسافات البادئة ما يحدث، لكن قد يكون لفريق التطوير إرشادات برمجية خاصة به حول وضع الأقواس المعقوفة في مثل هذه الحالات لإزالة أي التباس.

#include <stdio.h>

int main() {

    for (int i = 0, j = 11; i < j; i++)
        if (i & 1 == 1)
            // prints 1 3 5 7 9 
            printf("%d ", i);
}

رأينا أن القيم المعرَّفة داخل كتلة لا يمكن الوصول إليها من خارج الكتلة. ورأينا أن القيم المعرَّفة خارج كتلة يمكن الوصول إليها عادةً من داخل الكتلة. ومن الممكن أن يُحجب متغير خارج الكتلة داخل الكتلة إذا استُخدم معرّف ذلك المتغير في تعريف متغير داخل الكتلة. وفي المثال التالي، يُستخدم المعرّف number لمتغير على مستوى الدالة ولمتغير على مستوى الكتلة معًا. ونقول إن المتغير على مستوى الكتلة يحجب المتغير على مستوى الدالة أو يظلّله.

#include <stdio.h>

int main() {
    int number = 1;
    // prints number in function is 1
    printf("number in function is %d\n", number);
    
    if (number == 1) {
        // the definition of a block-level variable using the same name
        // shadows the function-level variable
        int number = 2;
        // prints number in function is 2
        printf("number in if block is %d\n", number);
    }
    // prints number in function is 1
    printf("number in function is %d\n", number);
}

قد تُصدر المصرّفات خطأً إذا جرت محاولة تظليل معامل دالة. والمثال التالي

#include <stdio.h>

void print_number(int number) {
    // will fail to compile
    int number = 2;
    printf("number in function is %d\n", number);
}

int main() {
    int number = 1;
    printf("number in main is %d\n", number);
    print_number(number);
    printf("number in main is %d\n", number);
}

يؤدي إلى الأخطاء التالية على منصّتين

/tmp/jIvhD5UOop.c: In function 'print_number':
/tmp/jIvhD5UOop.c:5:9: error: 'number' redeclared as different kind of symbol
    5 |     int number = 2;
      |         ^~~~~~
/tmp/jIvhD5UOop.c:3:23: note: previous definition of 'number' was here
    3 | void print_number(int number) {
      |                   ~~~~^~~~~~

و

redefinition of formal parameter 'number'
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة