Tracks
/
C
C
/
Temario
/
Bloques
Bl

Bloques en C

{one: "1 ejercicio", many: "%{count} ejercicios", other: "%{count} ejercicios"}

Acerca de Bloques

Un bloque es un conjunto de instrucciones y declaraciones agrupadas en una única unidad sintáctica. Los bloques se definen a menudo, aunque no siempre, entre llaves. Los bloques pueden anidarse dentro de funciones y dentro de otros bloques.

En el siguiente ejemplo, number se define dentro de la función main. flag se define dentro del bloque if, que está anidado dentro de la función main. Las variables definidas dentro de un bloque solo son válidas dentro de ese bloque. Por lo tanto, intentar compilar el código da como resultado un error de compilación.

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

El mensaje de error puede resultar confuso, ya que flag sí se usa antes en la función. Sin embargo, flag se define dentro de un bloque dentro de la función. El bloque interno puede «ver» la variable number de su función externa, pero la función externa no puede «ver» la variable flag definida dentro de su bloque interno. Cuando la ejecución sale del bloque, la variable flag se destruye automáticamente y deja de estar disponible para el programa. La propia función main ve el identificador flag por primera vez en la última instrucción printf. Si se elimina la última instrucción printf, el programa compilará e imprimirá 1 al ejecutarse. Esto se debe a que la variable flag es válida en el bloque que contiene la primera instrucción printf.

A partir de C99, las instrucciones de una sola línea if, for, while y do también se consideran bloques. En el siguiente ejemplo, aunque i no se define entre llaves, solo existe en el bloque definido por la instrucción for y no es visible para la función main externa.

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

Si se elimina la última instrucción printf, el programa compilará e imprimirá 0 2 4 6 8 10 al ejecutarse. Algunos lenguajes, como Go y Rust, dejan claro que las instrucciones condicionales y de bucle de una sola línea constituyen un bloque. Exigen que esas instrucciones de una sola línea sigan encerradas entre llaves.

Una función se comporta en cierto modo como un bloque. El identificador de una variable definida en una función no puede verse fuera de la función, del mismo modo que el identificador de una variable definida en un bloque no puede verse fuera del bloque. Dos variables en dos funciones pueden tener el mismo nombre, ya que las dos funciones no se ven entre sí, del mismo modo que dos variables en dos bloques pueden tener el mismo nombre, ya que los dos bloques no se ven entre sí. Pero las funciones y los bloques no son idénticos. Una función no puede definirse dentro de otra función, pero un bloque puede anidarse dentro de otro bloque. Y algunos identificadores de un bloque pueden verse fuera del bloque. En el siguiente ejemplo, el código no compilará

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

}

El código anterior no compila y da el siguiente error

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

Aunque i puede definirse en cada bloque, los nombres de label deben ser únicos en una función. Esto se debe a que las labels tienen scope de función, lo que significa que la función ve cada etiqueta sin importar dónde se defina en la función. Por lo tanto, a diferencia de las variables, sin importar dónde se defina una label en una función, debe tener un nombre único dentro de esa misma función. Para que el código funcione, hay que cambiar el nombre de una de las labels para que ambas sean únicas, como en el siguiente ejemplo

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

}

Las instrucciones de una sola línea pueden «apilarse» y seguir dentro de un mismo bloque sin usar llaves. En el ejemplo siguiente, la instrucción for contiene una instrucción if, que a su vez contiene una instrucción printf. Aunque hay dos instrucciones en total en el bloque for, solo su instrucción if de una sola línea se evalúa como el cuerpo del bucle for. La instrucción printf de una sola línea de la instrucción if se evalúa a su vez como su cuerpo. Si los bloques anidados son cada uno de una sola línea, ninguno de los bloques necesita llaves. Ten en cuenta que el comentario no necesita llaves para encerrarlo a él y a la instrucción printf dentro del bloque if, ya que el comentario no es una instrucción. La indentación deja claro lo que ocurre, pero un equipo de desarrollo puede tener sus propias guías de estilo de código sobre dónde colocar las llaves en estas situaciones para eliminar cualquier tipo de ambigüedad.

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

Hemos visto que no se puede acceder desde fuera del bloque a los valores definidos dentro de un bloque. Y hemos visto que normalmente se puede acceder desde dentro del bloque a los valores definidos fuera de él. Es posible que una variable externa a un bloque quede oculta dentro del bloque si el identificador de esa variable se usa al definir una variable del bloque. En el siguiente ejemplo, el identificador number se usa tanto para una variable a nivel de función como para una variable a nivel de bloque. Decimos que la variable a nivel de bloque oculta o sombrea a la variable a nivel de función.

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

Los compiladores pueden generar un error si se intenta sombrear un parámetro de función. El siguiente ejemplo

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

da como resultado los siguientes errores en dos plataformas

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

y

redefinition of formal parameter 'number'
Editar en GitHub El enlace se abre en una nueva ventana o pestaña