语句

语句块 属于 C

{other: "%{count} 个练习"}

关于 语句块

块是一组语句和声明组合成的语法单元。 块通常(但并非总是)用大括号定义。 块可以嵌套在函数中,也可以嵌套在其他块中。

在下面的例子中,number定义在main函数里。 flag定义在嵌套于main函数中的if块里。 在块内定义的变量只在该块内有效。 因此,尝试编译这段代码会导致编译错误。

#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函数本身是在最后一条printf语句里才第一次见到flag这个标识符的。 如果去掉最后那条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无论定义在函数中的哪里,它在同一个函数内都必须有一个唯一的名字。 要让代码能正常工作,需要修改其中一个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语句,而这条if语句里又有一条printf语句。 虽然for块里一共有两条语句,但只有那条单行的if语句会作为for循环的循环体执行。 而if语句里那条单行的printf语句,又会作为if语句的主体执行。 如果嵌套的每个块都只有一行,那么所有块都不需要大括号。 注意,注释不需要用大括号把它和if块里的printf语句括在一起,因为注释不是语句。 缩进让这段代码的意图一目了然,但开发团队可能对这种情况下的括号放置有自己的编码规范,以消除任何歧义。

#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 编辑 该链接会在新窗口或标签页中打开