一、局部变量和全局变量:
(1)局部变量:局部变量也叫自动变量,它声明在函数开始,生存于栈,它的生命随着函数的返回而结束。
- #include <stdio.h>
- int main(void)
- {
- auto int i = 9; <span style="white-space:pre"> </span>//声明局部变量的关键字是 auto; 因可以省略,所以几乎没人使用
- printf("%d ", i);
- getchar();
- return 0;
- }
#include <stdio.h> int main(void) { auto int i = 9; //声明局部变量的关键字是 auto; 因可以省略,所以几乎没人使用 printf("%d ", i); getchar(); return 0; }
(2)全局变量:全局变量声明在函数体外,一般应在函数前。每个函数都可以使用它,不过全局变量应尽量少用。
- #include <stdio.h>
- void add(void);
- void mul(void);
- int gi = 3; <span style="white-space:pre"> </span>//全局变量(声明在函数外)
- int main(void)
- {
- printf("%d ", gi); //输出的是 3
- add();
- printf("%d ", gi); //输出的是 5
- mul();
- printf("%d ", gi); //输出的是 10
- getchar();
- return 0;
- }
- void add(void) {
- gi += 2;
- }
- void mul(void) {
- gi *= 2;
- }
#include <stdio.h> void add(void); void mul(void); int gi = 3; //全局变量(声明在函数外) int main(void) { printf("%d ", gi); //输出的是 3 add(); printf("%d ", gi); //输出的是 5 mul(); printf("%d ", gi); //输出的是 10 getchar(); return 0; } void add(void) { gi += 2; } void mul(void) { gi *= 2; }
全局变量会被初始化为空, 而局部变量在没有赋值前是一个垃圾值:
- #include <stdio.h>
- int gi;<span style="white-space:pre"> </span>//全局变量
- int main(void)
- {
- int i;<span style="white-space:pre"> </span>//局部变量
- printf("%d, %d ", gi, i);
- getchar();
- return 0;
- }
#include <stdio.h> int gi; //全局变量 int main(void) { int i; //局部变量 printf("%d, %d ", gi, i); getchar(); return 0; }
当全局变量与局部变量重名时,使用的是局部变量:
- #include <stdio.h>
- int a = 111, b = 222;
- int main(void)
- {
- int a = 123;
- printf("%d,%d ", a, b);<span style="white-space:pre"> </span>//输出的是 123,222
- getchar();
- return 0;
- }
#include <stdio.h> int a = 111, b = 222; int main(void) { int a = 123; printf("%d,%d ", a, b); //输出的是 123,222 getchar(); return 0; }
二、对象的生存周期(lifetime)
(1)静态生存周期(即全局变量的生存周期)
具有静态生存周期的所有对象,都是在程序开始执行之前就被事先创建和初始化。它们的寿命覆盖整个程序的执行过程。如在函数内定义了一个static变量,那第一次调用该函数后,该变量的值将会被保留,当第二次被调用时,该变量的值还是第一次调用结束时的值。
(2)自动生存周期(即局部变量的生存周期)
自动生存周期的对象的寿命由“对象定义所处在的大括号{}”决定。每次程序执行流进入一个语句块,此语句块自动生存周期的对象就会被创建一个新实例,同时被初始化。
三、标识符的链接(linkage)
(1)外部链接
表示在整个程序中(多个程序文件)是相同的函数或对象。常见的有,在函数体外声明的extern变量。
(2)内部链接
表示只在当前程序文件中是相同的函数或对象。其它程序文件不能对其进行访问。常见的有,在函数体外声明的static变量。
(3)无链接
一般声明在函数内部的auto、register变量、还有函数的参数,都是无链接。它的作用域是函数内部。
四、存储类型修饰符总结:
存储类型修饰符可以修改标识符的链接和对应对象的生存周期;标识符有链接,而非生命周期;对象有生存周期,而非链接;函数标识符只可用static、extern修饰,函数参数只可用register修饰。
(1)auto(对应自动生存周期)
auto修饰符只能用在函数内的对象声明中,即仅在语句块内使用。
声明中有auto修饰符的对象具有自动生存周期。
它们仅存在于被定义的当前执行代码块中,即局部变量在进入模块时生成,在退出模块时消亡。
定义局部变量的最常见的代码块是函数。 语言中包括了关键字auto,它可用于定义局部变量。但自从所有的非全局变量的缺省值假定为auto以来,auto就几乎很少使用了。
(2)static(对应静态生存周期)
如果是定义在函数外,那么该对象具有内部链接,其它程序文件不能对其访问。如果是定义在函数内,那么该对象具有无链接,函数外不能对其访问。
(注意:static变量初始化时,只能用常量)
用 static 关键字修饰的局部变量称为静态局部变量。
静态局部变量存值如同全局变量,区别在于它只属于拥有它的函数,它也和全局变量一样会被初始化为空。
- #include <stdio.h>
- void fun1(void);
- void fun2(void);
- int main(void)
- {
- int i;
- for (i = 0; i < 10; i++) fun1();
- printf("--- ");
- for (i = 0; i < 10; i++) fun2();
- getchar();
- return 0;
- }
- void fun1(void) {
- int n = 0;<span style="white-space:pre"> </span>//一般的局部变量
- printf("%d ", n++);
- }
- void fun2(void) {
- static int n;<span style="white-space:pre"> </span>//静态局部变量,会被初始化为空
- printf("%d ", n++);
- }
#include <stdio.h> void fun1(void); void fun2(void); int main(void) { int i; for (i = 0; i < 10; i++) fun1(); printf("--- "); for (i = 0; i < 10; i++) fun2(); getchar(); return 0; } void fun1(void) { int n = 0; //一般的局部变量 printf("%d ", n++); } void fun2(void) { static int n; //静态局部变量,会被初始化为空 printf("%d ", n++); }
用 static 关键字修饰的全局变量是静态全局变量,静态全局变量只能用于定义它的单元。
- //譬如在 File1.c 里面定义了:
- static int num = 99; /* 去掉前面的 static 其他单元才可以使用 */
- //在 File2.c 里使用:
- #include <stdio.h>
- extern int num;
- int main(void)
- {
- printf("%d ", num);
- getchar();
- return 0;
- }
//譬如在 File1.c 里面定义了: static int num = 99; /* 去掉前面的 static 其他单元才可以使用 */ //在 File2.c 里使用: #include <stdio.h> extern int num; int main(void) { printf("%d ", num); getchar(); return 0; }
用静态变量记录函数被调用的次数:
- #include <stdio.h>
- int fun(void);
- int main(void)
- {
- int i;
- for (i = 0; i < 10; i++) {
- printf("函数被调用了 %2d 次; ", fun());
- }
- getchar();
- return 0;
- }
- int fun(void) {
- static int n;
- return ++n;
- }
#include <stdio.h> int fun(void); int main(void) { int i; for (i = 0; i < 10; i++) { printf("函数被调用了 %2d 次; ", fun()); } getchar(); return 0; } int fun(void) { static int n; return ++n; }
(3)const
(4)extern(对应静态生存周期)
extern 意为“外来的”。它的作用在于告诉编译器:这个变量或者函数的定义在别的地方,当遇到此变量或函数时应到其他模块中寻找其定义。
(PS:这个变量,它可能不存在于当前的文件中,但它肯定要存在于工程中的某一个源文件中或者一个Dll的输出中。)
- #include <stdio.h>
- extern int g1;
- int main(void)
- {
- extern int g2;<span style="white-space:pre"> </span>//告诉编译器g2定义在其他地方
- printf("%d,%d ", g1,g2);
- getchar();
- return 0;
- }
- int g1 = 77;
- int g2 = 88;
#include <stdio.h> extern int g1; int main(void) { extern int g2; //告诉编译器g2定义在其他地方 printf("%d,%d ", g1,g2); getchar(); return 0; } int g1 = 77; int g2 = 88;
使用extern时,注意不能重复定义,否则编译报错,如:
程序文件一:
extern int a = 10; //编译警告,extern的变量最好不要初始化
程序文件二:
extern int a = 20; //重复定义,应改为extern int a;
(一般最好这样,如果需要初始化,可把extern修饰符去掉(但也不要重复定义),另外如果其它程序文件也需要用到该变量,可用extern来声明该变量。这样会比较清晰。)
另外,extern也可用来进行链接指定。
(5)volatile
(6)register(即寄存器变量,对应自动生存周期)
当声明对象有自动生存周期时,可以使用register修饰符。因此,register也只能用在函数内的声明中。
register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中(而不是栈或堆),以加快其存储速度。然而,编译器不见得会这么做,因此效果一般般。了解一下就行,不建议使用。
- #include <stdio.h>
- #include <time.h>
- #define TIME 1000000000
- int m, n = TIME;<span style="white-space:pre"> </span>//全局变量
- int main(void)
- {
- time_t start, stop;
- register int a, b = TIME;<span style="white-space:pre"> </span>//寄存器变量
- int x, y = TIME;<span style="white-space:pre"> </span>//一般变量
- time(&start);
- for (a = 0; a < b; a++);
- time(&stop);
- printf("寄存器变量用时: %d 秒 ", stop - start);
- time(&start);
- for (x = 0; x < y; x++);
- time(&stop);
- printf("一般变量用时: %d 秒 ", stop - start);
- time(&start);
- for (m = 0; m < n; m++);
- time(&stop);
- printf("全局变量用时: %d 秒 ", stop - start);
- getchar();
- return 0;
- }
#include <stdio.h> #include <time.h> #define TIME 1000000000 int m, n = TIME; //全局变量 int main(void) { time_t start, stop; register int a, b = TIME; //寄存器变量 int x, y = TIME; //一般变量 time(&start); for (a = 0; a < b; a++); time(&stop); printf("寄存器变量用时: %d 秒 ", stop - start); time(&start); for (x = 0; x < y; x++); time(&stop); printf("一般变量用时: %d 秒 ", stop - start); time(&start); for (m = 0; m < n; m++); time(&stop); printf("全局变量用时: %d 秒 ", stop - start); getchar(); return 0; }
使用register修饰符有几点限制:
首先,register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
其次,因为register变量有可能被存放到寄存器中而不是内存中,所以不能用“&”来获取register变量的地址。
总的来说,由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。
在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。
早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。
(7)缺省修饰符
函数内,与auto相同,函数外,与extern相同。
五、概括性例子:
- int func1(void); <span style="white-space:pre"> </span>//func1具有外部链接
- int a = 10; <span style="white-space:pre"> </span>//a具有外部链接,静态生存周期
- extern int b = 1; <span style="white-space:pre"> </span>//b具有外部链接,静态生存周期。但编译会有警告extern变量不应初始化,同时也要注意是否会重复定义
- static int c; <span style="white-space:pre"> </span>//c具有内部链接,静态生存周期
- static int e; <span style="white-space:pre"> </span>//e具有内部链接,静态生存周期
- static void func2(int d)
- {
- <span style="white-space:pre"> </span>//func2具有内部链接;参数d具有无链接,自动生存周期
- <span style="white-space:pre"> </span>extern int a; <span style="white-space:pre"> </span>//a与上面的a一样(同一变量),具有外部链接,静态生存周期。注意这里的不会被默认初始为0,它只是个声明
- <span style="white-space:pre"> </span>int b = 2; <span style="white-space:pre"> </span>//b具有无链接,自动生存同期。并且将上面声明的b隐藏起来
- <span style="white-space:pre"> </span>extern int c; <span style="white-space:pre"> </span>//c与上面的c一样,维持内部链接,静态生存周期。注意这里的不会被默认初始为0,它只是个声明
- <span style="white-space:pre"> </span>//如果去掉了extern修饰符,就跟b类似了,无链接,自动生存周期,把上面声明的c隐藏起来
- <span style="white-space:pre"> </span>static int e; <span style="white-space:pre"> </span>//e具有无链接,静态生存周期。并且将上面声明的e隐藏起来;初始化值为0
- <span style="white-space:pre"> </span>static int f; <span style="white-space:pre"> </span>//f具有无链接,静态生存周期
- }
int func1(void); //func1具有外部链接 int a = 10; //a具有外部链接,静态生存周期 extern int b = 1; //b具有外部链接,静态生存周期。但编译会有警告extern变量不应初始化,同时也要注意是否会重复定义 static int c; //c具有内部链接,静态生存周期 static int e; //e具有内部链接,静态生存周期 static void func2(int d) { //func2具有内部链接;参数d具有无链接,自动生存周期 extern int a; //a与上面的a一样(同一变量),具有外部链接,静态生存周期。注意这里的不会被默认初始为0,它只是个声明 int b = 2; //b具有无链接,自动生存同期。并且将上面声明的b隐藏起来 extern int c; //c与上面的c一样,维持内部链接,静态生存周期。注意这里的不会被默认初始为0,它只是个声明 //如果去掉了extern修饰符,就跟b类似了,无链接,自动生存周期,把上面声明的c隐藏起来 static int e; //e具有无链接,静态生存周期。并且将上面声明的e隐藏起来;初始化值为0 static int f; //f具有无链接,静态生存周期 }
相关链接参考:
http://developer.51cto.com/art/201105/261465.htm
http://apps.hi.baidu.com/share/detail/30353645
http://www.cnblogs.com/del/archive/2008/12/04/1347305.html