Kurzusok
/
C
C
/
Tanterv
/
Blokkok
Bl

Blokkok ebben a kurzusban: C

{one: "1 feladat", other: "%{count} feladat"}

A(z) Blokkok fogalomról

A blokk utasítások és deklarációk egyetlen szintaktikai egységbe csoportosított halmaza. A blokkokat gyakran, de nem mindig kapcsos zárójelek közé zárjuk. A blokkok függvényeken belül és más blokkokon belül is egymásba ágyazhatók.

A következő példában a number a main függvényen belül van definiálva. A flag abban az if blokkban van definiálva, amely a main függvényen belül van egymásba ágyazva. A blokkon belül definiált változók csak azon a blokkon belül érvényesek. Ezért ha megpróbáljuk lefordítani a kódot, fordítási hibát kapunk.

#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);
    
}

A hibaüzenet zavaró lehet, hiszen a flag tényleg használatban van korábban a függvényben. A flag viszont a függvényen belüli egyik blokkban van definiálva. A belső blokk „látja” a külső függvényében lévő number változót, a külső függvény viszont nem „látja” a belső blokkjában definiált flag változót. Amint a végrehajtás kilép a blokkból, a flag változó automatikusan megsemmisül, és a program számára többé nem elérhető. A main függvény maga az utolsó printf utasításban találkozik először a flag azonosítóval. Ha eltávolítjuk az utolsó printf utasítást, a program lefordul, és futtatáskor 1-et ír ki. Ez azért van, mert a flag változó abban a blokkban érvényes, amely az első printf utasítást tartalmazza.

A C99 óta az egysoros if, for, while és do utasítások is blokknak számítanak. A következő példában bár az i nincs kapcsos zárójelek között definiálva, csak abban a blokkban létezik, amelyet a for utasítás definiál, és a külső main függvény nem látja.

#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);
    
}

Ha eltávolítjuk az utolsó printf utasítást, a program lefordul, és futtatáskor a 0 2 4 6 8 10 szöveget írja ki. Egyes nyelvek, például a Go és a Rust, egyértelműen kimondják, hogy az egysoros feltételes és ciklusutasítások is blokkot alkotnak. Náluk az ilyen egysoros utasításokat is kapcsos zárójelek közé kell tenni.

A függvény némileg úgy viselkedik, mint egy blokk. A függvényben definiált változó azonosítója nem látható a függvényen kívül, akárcsak a blokkban definiált változó azonosítója a blokkon kívül. Két függvényben lehet ugyanaz a név, mivel a két függvény nem lát bele a másikba, akárcsak két blokk esetében: ott is lehet ugyanaz a név, mert a két blokk nem lát bele a másikba. A függvények és a blokkok azonban nem azonosak. Függvényt nem lehet egy másik függvényen belül definiálni, blokkot viszont igen: az egymásba ágyazható. Ráadásul a blokkban lévő egyes azonosítók a blokkon kívülről is láthatók. A következő példában a kód nem fordul le:

#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;
      }
  }

}

A fenti kód a következő hibával nem fordul le:

/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:
      |     ^~~~~

Bár az i mindkét blokkban definiálható, a label neveknek egy függvényen belül egyedinek kell lenniük. Ez azért van, mert a labels függvényszintű scope-pal rendelkeznek, vagyis a függvény mindegyik címkét látja, függetlenül attól, hogy a függvény mely pontján van definiálva. Így a változókkal ellentétben a label nevének egyedinek kell lennie ugyanazon függvényen belül, bárhol is definiáljuk. Ahhoz, hogy a kód működjön, az egyik label nevét meg kell változtatni, hogy mindkettő egyedi legyen, mint a következő példában:

#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;
        }
    }

}

Az egysoros utasítások „egymásra pakolhatók”, és kapcsos zárójelek nélkül is egy blokkon belül maradnak. Az alábbi példában a for utasítás tartalmaz egy if utasítást, az pedig egy printf utasítást. Bár a for blokkjában összesen két utasítás van, a for ciklus törzseként csak az egysoros if utasítás értékelődik ki. Az if utasítás egysoros printf utasítása pedig a saját törzseként értékelődik ki. Ha az egymásba ágyazott blokkok mindegyike csak egyetlen sor, egyik blokknál sem kellenek kapcsos zárójelek. Vegyük észre, hogy a megjegyzés és az if blokkban lévő printf utasítás körül nincs szükség kapcsos zárójelekre, mert a megjegyzés nem utasítás. A behúzás világossá teszi, mi történik, de egy fejlesztőcsapatnak lehet saját kódolási irányelve arról, hogyan helyezze el a kapcsos zárójeleket az ilyen helyzetekben, hogy minden kétértelműséget kiküszöböljön.

#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);
}

Láttuk, hogy a blokkon belül definiált értékek a blokkon kívülről nem érhetők el. És azt is láttuk, hogy a blokkon kívül definiált értékek általában elérhetők a blokkon belülről. Előfordulhat, hogy egy blokkon kívüli változó elrejtőzik a blokkon belül, ha a változó azonosítóját egy blokkban definiált változó definiálásához használjuk. A következő példában a number azonosítót egyszerre használjuk egy függvényszintű és egy blokkszintű változóhoz. Azt mondjuk, hogy a blokkszintű változó elrejti, más szóval árnyékolja a függvényszintű változót.

#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);
}

A fordítók hibát jelezhetnek, ha egy függvényparamétert próbálunk árnyékolni. A következő példa

#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);
}

két platformon a következő hibákat eredményezi

/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) {
      |                   ~~~~^~~~~~

és

redefinition of formal parameter 'number'
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg