作用域和 static
一个变量在哪些地方能用,叫它的作用域。到现在为止,变量都写在函数里面,它们只在这个函数里能用。两个函数可以各有一个同名的变量,互不相干:
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 也不行:
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 intotal 只活在 add_tax 里。到了 main,编译器根本不认识这个名字:'total' undeclared,意思是「total 没声明过」。参数也是局部变量,price 同样只在 add_tax 里能用。
要把结果交给 main,就像上一课那样用 return。
代码块里的变量
不只是函数,任何一对花括号 { } 都是一个代码块。在代码块里声明的变量,出了这对花括号就不能用了:
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 inextra 是在 if 的代码块里声明的,第 9 行已经在代码块外面了。要在外面用,就把它声明在外面,比如在 if 前面写 int extra = 0;。for (int i = 0; ...) 里的 i 也一样,只在这个循环里有。
全局变量
写在所有函数外面的变量叫全局变量。从它声明的地方往下,所有函数都能用它,它从程序开始一直活到程序结束:
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 里声明了自己的 count,在 main 里 count 就是这个局部变量,全局的 count 被遮住了。count++ 把局部的 5 变成 6,全局的还是 100,没被碰过。
代码块里也能遮住外面的变量。出了代码块,外面那个又回来了:
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:
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 只在最开始做一次,以后每次调用,它都记得上次的值:
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:
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 交回来。这样看函数的第一行,就知道它用什么、交出什么。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
取号
✓next_ticket 每调用一次,应该返回下一个号:1、2、3……现在它每次都返回 1。改好它,让程序显示 1 号、2 号、3 号,各占一行。
1 号 2 号 3 号
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}
运费
✓这段程序编译不过。改好它:订单满 100 元免运费,否则运费 12 元。程序要显示 运费:12。
运费:12
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}
购物车
✓add 把价格加到全局的 total 上,可程序最后显示的是 合计:0。找出原因,改好它,让程序显示 合计:60。
合计:60
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}
这一课学会了
- 作用域就是变量能用的范围。
- 函数里和代码块里声明的是局部变量,只在自己的花括号里能用;参数也是局部变量。
- 函数外面声明的是全局变量,所有函数都能用,没写初始值就是 0。
- 里面声明的同名变量会遮住外面的;最好别这样起名。
static局部变量只在自己的函数里能用,但会一直活着,记住上次的值。- 少用全局变量:需要的值用参数传进去,结果用
return交回来。