if 语句的作用域

archive time: 2021-06-04

今天我们来看看 C 语言中作用域的例子

缘起

最近看到群里有个人问了个比较有意思的问题

main() {
    int a = 3, b = 4, c = 5, t = 99;
    if (b < a && a < c) t = a; a = c; c = t;
    if (a < c && b < c) t = b; b = a; a = t;
    printf("%d%d%d\n", a, b, c);
}

问这个程序的输出是什么

只要顺着程序逻辑看简单推算即可得知输出结果应该是 4599

但是那人却继续问: “那两个 if 中不是做了交换操作吗? 第一个 if 不执行,而第二个执行,所以 b 被赋上了a的值,a赋上了b的值,c不变, 所以答案不应该是 435 吗?”

这个错误可谓是十分经典了,很多初学者面对这些格式不规范的 谭氏 C 语言1 总是不知所措

在这里,他将if的作用域当成了该语句所在的那一行了,而忽略了 ; 的作用

C 语言的作用域

作用域2,简单说,就是说这个语句或者变量,它的影响范围有多广, 最直观的一类作用域的就是 block scope,也就是被一对花括号 (curly bracket) 包裹起来的区域

for (int i = 0; i < n; ++i) {
    // do some
    ...
}

在这样一个 for 循环里,i 就具有 块作用域, 也就是说 i 仅在它在被声明开始到花括号结束为止,这个范围内有效, 其他范围都不会被 i 影响

作用域分类

在 C 语言里,最规范的分类,应该有以下 四个 作用域的分类:

  • block scope
  • function scope
  • function prototype scope
  • file scope

其中最为常见的就是 block scope, 因为在 C 语言里,用来组织语句的方式就是代码块, 或者说,用一对大括号将一堆语句括起来

其次就是 function prototype scope, 这个作用域的定义就是在函数原型, 或者说声明语句内,其中声明的变量具有函数原型作用域

void exampleFunction(int paramA, double paramB[paramA]);

就比如上面这个例子,paramAparamB 都具有函数原型作用域, 也是就是说他们的影响范围仅在这样一条语句内,对之后的所有语句或代码块都不会有所影响, 出了这条语句,也无法再次访问这个参数变量了

该作用域同时说明了,函数原型中重要的不是变量名,而是其类型, 在有函数重载的语言里,比如 Java, 如何判断一个函数和另一个同名函数是重复还是重载,看的是其参数列表的参数的 类型, 参数列表的类型都一一对应的话,如果无继承关系,那么就是 函数重复,而非 函数重载, 若参数列表的参数的类型有所不同,那么就是 函数重载

function scope 是针对于跳转语句 (jump statement) 而言的, 说的是在一个函数内声明的 label 的作用域范围是整个函数,但超出这个函数的限定范围就不在生效

这也是为什么在同一个函数内不能有两个相同名称的标签的原因,因为会产生歧义,无法正常解析究竟要跳转到哪个标签

file scope 就是所谓的 全局作用域, 这个全局指的就是一个编译单元3,通常就是一个.c文件, 因为一个.c文件和一个.o(bj)文件一般而言是对应的,而在 C 语言里,一个.o(bj)就是所谓的编译单元

来看看这道题

这道题的代码,我们格式化 (format) 一下,就看得很清楚了

int main(void) {
    int a = 3, b = 4, c = 5, t = 99;

    if (b < a && a < c)
        t = a;
    a = c;
    c = t;

    if (a < c && b < c)
        t = b;
    b = a;
    a = t;

    printf("%d%d%d\n", a, b, c);

    return 0;
}

实际上,这里的 if 就是单个语句, 比如 if (b < a && a < c) t = a; 这条语句,这就是它的作用域, 并且通过缩进我们也可以清楚地知道,if 所管理的语句就只有 t = a;,是无法处理之后的语句的

所以我们带入相关逻辑就可知道,a = cc = tt = bb = a,最后 a = t, 再代入相应的值 a = 3, b = 4, c = 5, t = 99,就可以知道

a* = b = 4
b* = c = 5
c* = t = 99

所以答案自然是 4599


这是一道很简单的逻辑推理题,但是主要考察的地方在于对于作用域的判断上, 还是一个比较容易犯错的点,这同时也说明了有一个好的编程风格和习惯是一件多么重要的事

1

谭浩强所著的 C 语言教材多以这类不规范的代码书写,经常有初学者出现一个程序 999error的事迹, 因此得名

2

参考网站,cppreference

3

又称翻译单元