一、extern
在所有的代码块(函数、if 块、switch 块等)之外定义的变量称为全局变量,它的作用范围默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。
如果你一直在编写单个 .c 文件的程序,那么请注意,全局变量的作用范围不是从变量定义处到该文件结束,在其他文件中也有效。
虽然全局变量的作用范围是整个程序,但是如果希望在 a.c 中使用 b.c 中的变量,也必须先进行声明。声明使用 extern 关键字,请看下面的代码。
a.c 源码:
- #include <stdio.h>
- #include <stdlib.h>
- extern int num; // 必须对 num 进行声明
- int main ()
- {
- printf("num = %d", num);
- system("pause");
- return 0;
- }
- int num = 100; // 对 num 进行定义
num = 100
我们在 b.c 中定义了一个全局变量 num,在 a.c 中调用了它。extern int num; 的作用是告诉编译器 num 不在 a.c 中,请到其他文件中查找。如果没有 extern,编译器就会在当前文件中查找,发现没有就会报错。
提示:编译是针对单个源文件的,在编译 a.c 时编译器找不到 num,链接时才会在 b.c 的目标代码(.obj 文件)中找到 num。
与其他变量不同,extern 变量有声明和定义之分。
extern 变量的定义格式为:
- extern type name = value;
声明格式为:
- extern type name;
注意:
- 在定义 extern 变量时不能省略 value,否则就变成了变量声明。
- 声明 extern 变量时要指明数据类型(必须和定义时的数据类型一致)。
- 声明可以有多次,定义只能有一次。
在 a.c 中,我们在所有代码块外部对 num 进行了声明,这个时候 num 的作用范围是 a.c 整个文件(确切的说是从声明开始处到文件结束)。如果在代码块内部声明会怎样呢?
对 a.c 进行更改:
- #include <stdio.h>
- #include <stdlib.h>
- int main ()
- {
- {
- extern int num;
- printf("num = %d", num);
- }
- printf("num = %d", num);
- system("pause");
- return 0;
- }
extern 函数
从本质上讲,函数和变量是类似的,它们都指向内存中的一块区域:函数指向存放了函数体二进制代码的程序代码区,变量指向静态数据区、栈区或堆区。extern 除了用于变量,也可以用于函数,请看下面的代码:
sum.c 源码:
- int sum(int n1, int n2){
- return n1 + n2;
- }
- #include <stdio.h>
- #include <stdlib.h>
- extern int sum(int, int);
- int main ()
- {
- int num1 = 20, num2 = 110;
- printf("%d + %d = %d", num1, num2, sum(num1, num2));
- system("pause");
- return 0;
- }
20 + 110 = 130
我们在 sum.c 中定义了一个函数 sum() 用来计算两个数的和,在 main.c 中对函数进行了调用。extern 的作用是告诉编译器 sum() 函数不在 main.c 中,请到其他文件中去查找。
但是,函数和变量的声明有所不同,对于函数,你可以省略 extern。例如将 main.c 中的:
- extern int sum(int, int);
- int sum(int, int);
这是因为,函数的定义和声明区别很明显,有函数体就是定义,没有函数体就是声明,所以有没有 extern 都是函数声明。但是变量不一样,没有 extern 就是变量定义,重复定义是错误的。
二、static
上一节我们讲到,全局变量和函数的作用范围默认是整个程序,也就是所有的源文件。这给我们带来了很大的方便,让我们能够在
A 文件中调用 B 文件中定义的变量和函数,不必把所有的代码都集中到一个文件,有利于模块化的程序设计。
但是有时候这也会带来冲突,例如在 a.c 中定义了一个全局变量 n,在 b.c 中又定义了一次,编译时就会发生重复定义的错误,因为变量只能定义一次。
如果两个文件都是我们自己编写的或者其中一个是,遇到这样的情况还比较好处理,改变变量的名字就可以;但如果两个文件都是其他程序员编写的,或者是第三方的库,修改起来就颇费精力了。所以,实际开发中我们一般将不需要被其他文件调用的全局变量或函数的作用范围限制在当前文件中。
可以通过 static 关键字来限制,请看下面的代码。
a.c 源码:
- #include <stdio.h>
- static int n = 10;
- void print_n_a(){
- printf("n(a.c) = %d\n", n);
- }
- #include <stdio.h>
- static int n = 20;
- void print_n_b(){
- printf("n(b.c) = %d\n", n);
- }
- #include <stdio.h>
- #include <stdlib.h>
- int n = 100;
- int main ()
- {
- print_n_a();
- print_n_b();
- printf("n(main.c) = %d\n", n);
- system("pause");
- return 0;
- }
n(a.c) = 10
n(b.c) = 20
n(main.c) = 100
我们在 a.c、b.c 和 main.c 中都定义了变量 n,a.c 和 b.c 中的变量 n 都只在各自的文件内有效,main.c 中的变量 n 在整个程序内有效。
由此可见,加了 static 的变量或函数的作用范围仅限于当前文件,对其他源文件隐藏,利用这一特性可以在不同的文件中定义同名的变量或函数,而不必担心命名冲突。
static 局部变量
static 声明的变量称为静态变量,不管是全局变量还是局部变量,都存储在静态数据区(全局变量本来就存储在静态数据区,即使不加 static)。静态数据区的数据在程序启动时就会初始化,直到程序运行结束;对于代码块中的静态局部变量,即使代码块执行结束,也不会销毁。
注意:静态数据区的变量只能初始化(定义)一次,以后只能改变它的值,不能再被初始化,即使有这样的语句,也无效。
请看下面的代码:
- #include <stdio.h>
- #include <stdlib.h>
- int main ()
- {
- int result, i;
- for(i = 1; i<=100; i++){
- result = sum(i);
- }
- printf("1+2+3+...+99+100 = %d\n", result);
- system("pause");
- return 0;
- }
- int sum(int n){
- // 也可以不赋初值 0,静态数据区的变量默认初始化为 0
- static int result = 0;
- result += n;
- return result;
- }
1+2+3+...+99+100 = 5050
我们在 sum() 中定义了一个静态局部变量 result,它存储在静态数据区,sum() 函数执行结束也不会销毁,下次调用继续有效。静态数据区的变量只能初始化一次,第一次调用 sum() 时已经对 result 进行了初始化,所以再次调用时就不会初始化了,也就是说 static int result = 0; 语句无效。
静态局部变量虽然存储在静态数据区,但是它的作用域仅限于定义它的代码块,sum() 中的 result 在函数外无效,与 main() 中的 result 不冲突,除了变量名一样,没有任何关系。
总结起来,static 变量的主要作用有两个。
1) 隐藏
程序有多个源文件时,将全局变量或函数的作用范围限制在当前文件,对其他文件隐藏。2) 保持变量内容的持久化
将局部变量存储到静态数据区。静态数据区的内存在程序启动时就已分配好(内存中所有的字节默认值都是0x00),直到程序运行结束。