Kodewick
第 9 课,共 30 课目录

作用域和 static

第 9 课,共 30 课 · 第 2 部分 · 函数和数组 · 约 15 分钟

一个变量在哪些地方能用,叫它的作用域。到现在为止,变量都写在函数里面,它们只在这个函数里能用。两个函数可以各有一个同名的变量,互不相干:

main.c
1#include <stdio.h>2 3void morning(void) {4    int steps = 3200;5    printf("上午:%d 步\n", steps);6}7 8void evening(void) {9    int steps = 5100;10    printf("晚上:%d 步\n", steps);11}12 13int main(void) {14    morning();15    evening();16    return 0;17}
输出
上午:3200 步
晚上:5100 步

局部变量

写在函数里面的变量叫局部变量。每次调用函数,它就新建出来;函数一结束,它就没了。别的函数看不见它,main 也不行:

main.c
1#include <stdio.h>2 3void add_tax(int price) {4    int total = price + price / 10;5    printf("含税:%d\n", total);6}7 8int main(void) {9    add_tax(200);10    printf("%d\n", total);11    return 0;12}
输出
main.c: In function 'main':
main.c:10:20: error: 'total' undeclared (first use in this function)
   10 |     printf("%d\n", total);
      |                    ^~~~~
main.c:10:20: note: each undeclared identifier is reported only once for each function it appears in

total 只活在 add_tax 里。到了 main,编译器根本不认识这个名字:'total' undeclared,意思是「total 没声明过」。参数也是局部变量,price 同样只在 add_tax 里能用。

要把结果交给 main,就像上一课那样用 return。

代码块里的变量

不只是函数,任何一对花括号 { } 都是一个代码块。在代码块里声明的变量,出了这对花括号就不能用了:

main.c
1#include <stdio.h>2 3int main(void) {4    int hours = 10;5    if (hours > 8) {6        int extra = hours - 8;7        printf("加班 %d 小时\n", extra);8    }9    printf("一共 %d 小时,加班 %d 小时\n", hours, extra);10    return 0;11}
输出
main.c: In function 'main':
main.c:9:51: error: 'extra' undeclared (first use in this function)
    9 |     printf("一共 %d 小时,加班 %d 小时\n", hours, extra);
      |                                                   ^~~~~
main.c:9:51: note: each undeclared identifier is reported only once for each function it appears in

extra 是在 if 的代码块里声明的,第 9 行已经在代码块外面了。要在外面用,就把它声明在外面,比如在 if 前面写 int extra = 0;。for (int i = 0; ...) 里的 i 也一样,只在这个循环里有。

全局变量

写在所有函数外面的变量叫全局变量。从它声明的地方往下,所有函数都能用它,它从程序开始一直活到程序结束:

main.c
1#include <stdio.h>2 3int visitors = 0;4 5void welcome(void) {6    visitors++;7    printf("欢迎,你是第 %d 位客人\n", visitors);8}9 10int main(void) {11    welcome();12    welcome();13    welcome();14    printf("今天一共 %d 位\n", visitors);15    return 0;16}
输出
欢迎,你是第 1 位客人
欢迎,你是第 2 位客人
欢迎,你是第 3 位客人
今天一共 3 位

welcome 和 main 用的是同一个 visitors。点单步看看:全局变量不在任何一个函数的栈帧里,它单独放着,哪个函数改了它,大家看到的都是新值。

全局变量要是没写初始值,会自动是 0。局部变量不会,没赋值就用是错的(第 2 课讲过)。

同名遮住

里面的作用域可以声明一个和外面同名的变量。这时在里面,这个名字指的是里面的变量,外面那个被遮住了,暂时看不见:

先猜一猜

这段程序显示什么?

1#include <stdio.h>2 3int count = 100;4 5int main(void) {6    int count = 5;7    count++;8    printf("%d\n", count);9    return 0;10}

代码块里也能遮住外面的变量。出了代码块,外面那个又回来了:

main.c
1#include <stdio.h>2 3int main(void) {4    int score = 70;5    if (score < 80) {6        int score = 80;7        printf("代码块里:%d\n", score);8    }9    printf("代码块外:%d\n", score);10    return 0;11}
输出
代码块里:80
代码块外:70

能写不等于该写。两个同名的变量很容易看混,最好起不同的名字。

static 局部变量

普通局部变量每次调用都重新建,上次的值留不下来。下面的 count 每次都从 0 开始,所以总是显示 1:

main.c
1#include <stdio.h>2 3void press(void) {4    int count = 0;5    count++;6    printf("按了 %d 次\n", count);7}8 9int main(void) {10    press();11    press();12    press();13    return 0;14}
输出
按了 1 次
按了 1 次
按了 1 次

在声明前面加上 static,它就成了 static 局部变量:只有这个函数能用(作用域还是局部的),但它和全局变量一样,从程序开始活到结束。= 0 只在最开始做一次,以后每次调用,它都记得上次的值:

main.c
1#include <stdio.h>2 3void press(void) {4    static int count = 0;5    count++;6    printf("按了 %d 次\n", count);7}8 9int main(void) {10    press();11    press();12    press();13    return 0;14}
输出
按了 1 次
按了 2 次
按了 3 次

点单步比一比这两段:前一段每次调用,press 的栈帧里都有一个新的 count,从 0 开始;这一段的 count 不在栈帧里,函数结束了它也还在,下次调用接着往上加。

少用全局变量

全局变量用着方便,可是谁都能改它。程序一长,一个值不对了,就得把所有函数都查一遍,才知道是谁改的。下面这段想打 3 行星号,main 和 print_stars 却共用了一个全局的 i:

main.c
1#include <stdio.h>2 3int i;4 5void print_stars(int n) {6    for (i = 0; i < n; i++) {7        printf("*");8    }9    printf("\n");10}11 12int main(void) {13    for (i = 0; i < 3; i++) {14        print_stars(5);15    }16    return 0;17}
输出
*****

只打了 1 行。print_stars 跑完,i 已经是 5 了;回到 main,i++ 变成 6,i < 3 不成立,循环就结束了。点单步,盯着 i 看。把 int i; 删掉,两个循环各写 for (int i = 0; ...),各用各的局部变量,就对了。

小技巧: 一个函数需要的值,尽量用参数传进去;算出来的结果,用 return 交回来。这样看函数的第一行,就知道它用什么、交出什么。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

取号

next_ticket 每调用一次,应该返回下一个号:1、2、3……现在它每次都返回 1。改好它,让程序显示 1 号、2 号、3 号,各占一行。

要显示
1 号
2 号
3 号
exercise1.c
1#include <stdio.h>2 3int next_ticket(void) {4    int number = 0;5    number++;6    return number;7}8 9int main(void) {10    printf("%d 号\n", next_ticket());11    printf("%d 号\n", next_ticket());12    printf("%d 号\n", next_ticket());13    return 0;14}
输出
点「运行」或按 Ctrl+Enter
练习 2

运费

这段程序编译不过。改好它:订单满 100 元免运费,否则运费 12 元。程序要显示 运费:12。

要显示
运费:12
exercise2.c
1#include <stdio.h>2 3int main(void) {4    int amount = 85;5    if (amount < 100) {6        int shipping = 12;7    }8    printf("运费:%d\n", shipping);9    return 0;10}
输出
点「运行」或按 Ctrl+Enter
练习 3

购物车

add 把价格加到全局的 total 上,可程序最后显示的是 合计:0。找出原因,改好它,让程序显示 合计:60。

要显示
合计:60
exercise3.c
1#include <stdio.h>2 3int total = 0;4 5void add(int price) {6    total = total + price;7}8 9int main(void) {10    int total = 0;11    add(25);12    add(35);13    printf("合计:%d\n", total);14    return 0;15}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 作用域就是变量能用的范围。
  • 函数里和代码块里声明的是局部变量,只在自己的花括号里能用;参数也是局部变量。
  • 函数外面声明的是全局变量,所有函数都能用,没写初始值就是 0。
  • 里面声明的同名变量会遮住外面的;最好别这样起名。
  • static 局部变量只在自己的函数里能用,但会一直活着,记住上次的值。
  • 少用全局变量:需要的值用参数传进去,结果用 return 交回来。