基本上每种语言都要讨论这个话题,C语言也不例外,由于只有你彻底了解每一个变量或函数存储方式、做用范围和销毁时间才可能正确的使用这门语言。今天将着重介绍C语言中变量做用范围、存储方式、生命周期、做用域和可访问性。数据结构
在C语言中变量从做用范围包括全局变量和局部变量。全局变量在定义以后全部的函数中都可以使用,只要前面的代码修改了,那么后面的代码中再使用就是修改后的值;局部变量的做用范围通常在一个函数内部(一般在一对大括号{}内),外面的程序没法访问它,它却能够访问外面的变量。函数
// // main.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> int a=1; void changeValue(){ a=2; printf("a=%d\n",a); } int main(int argc, const char * argv[]) { int b=1; changeValue(); //结果:a=2 printf("a=%d,b=%d\n",a,b); //结果:a=2,b=1 ,由于changeValue修改了这个全局变量 return 0; }
C语言的强大之处在于它能直接操做内存(指针),可是要彻底熟悉它的操做方式咱们必需要弄清它的存储方式。存储变量的位置分为:普通内存(静态存储区)、运行时堆栈(动态存储区)、硬件寄存器(动态存储区),固然这几种存储的效率是从低到高的。而根据存储位置的不一样在C语言中又能够将变量依次分为:静态变量、自动变量、寄存器变量。ui
首先说一下存储在普通内存中的静态变量,全局变量和使用static声明的局部变量都是静态变量,在系统运行过程当中只初始化一次(在下面的例子中虽然变量b是局部变量,在外部没法访问,可是他的生命周期一直延续到程序结束,而变量c则在第一次执行完就释放,第二次执行时从新建立)。编码
// // 2.1.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> int a=1; //全局变量存储在静态内存中,只初始化一次 void showMessage(){ static int b=1; //静态变量存储在静态内存中,第二次调用不会再进行初始化 int c=1; ++b; a+=2; printf("a=%d,b=%d,c=%d\n",a,b,c); } int main(int argc, const char * argv[]) { showMessage(); //结果:a=3,b=2,c=1 showMessage(); //结果:a=5,b=3,c=1 return 0; }
被关键字auto修饰的局部变量是自动变量,可是auto关键字能够省略,所以能够得出结论:全部没有被static修饰的局部变量都是自动变量。spa
// // 1.3.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> #include <stdlib.h> int main(int argc, const char * argv[]) { int a=1; auto int b=2; printf("a=%d,b=%d\n",a,b); //结果:a=1,b=2 ,a和b都是自动变量,auto能够省略 //须要注意的是,上面的自动变量是存储在栈中,其实还能够存储到堆中 char c[]="hello,world!"; long len=strlen(c)*sizeof(char)+1;//之因此加1是由于字符串后面默认有一个\0空操做符不计算在长度内 char *p=NULL;//能够直接写成:char *p; p=(char *)malloc(len);//分配指定的字节存放c中字符串,注意因为malloc默认返回“void *”须要转化 memset(p,0,len);//清空指向内存中的存储内容,由于分配的内存是随机的,若是不清空可能会由于垃圾数据产生没必要要的麻烦 strcpy(p,c); printf("p=%s\n",p);//结果:p=hello,world! free(p);//释放分配的空间 p=NULL;//注意让p指向空,不然p将会是一个存储一个无用地址的野指针 return 0; }
固然存储自动变量的栈和堆实际上是两个彻底不一样的空间(虽然都在运行时有效的空间内):栈通常是程序自动分配,其存储结果相似于数据结构中的栈,先进后出,程序结束时由编译器自动释放;而堆则是开发人员手动编码分配,若是不进行手动释放就只有等到程序运行完操做系统回收,其存储结构相似于链表。在上面的代码中p变量一样是一个自动变量,一样可使用auto修饰,只是它所指向的内容放在堆上(p自己存放在栈上)。操作系统
这里说明几点:malloc分配的空间在逻辑上连续,物理上未必连续;p必须手动释放,不然直到程序运行结束它占用的内存将一直被占用;释放p的过程只是把p指向的空间释放掉,p中存放的地址并未释放,须要手动设置为NULL,不然这将是一个无用的野指针;.net
默认状况下不管是自动变量仍是静态变量它们都在内存中,不一样之处就是自动变量放在一块运行时分配的特殊内存中。可是寄存器变量倒是在硬件寄存器中,从物理上来讲它和内存处在两个彻底不一样的硬件中。你们都是知道寄存器存储空间很小,可是它的效率很高,那么合理使用寄存器变量就至关重要了。什么是寄存器变量呢?使用register修饰的int或char类型的非静态局部变量是寄存器变量。没错,须要三个条件支撑:register修饰、必须是int或char类型、必须是非静态局部变量。指针
除了存储位置不一样外,寄存器变量彻底符合自动变量的条件,所以它的生命周期实际上是和自动变量彻底同样的,当函数运行结束后它就会被自动释放。因为寄存器空间珍贵,所以咱们须要合理使用寄存器变量,只有访问度很高的变量咱们才考虑使用寄存器变量,若是过多的定义寄存器变量,当寄存器空间不够用时会自动转化为自动变量。code
// // 1.3.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> int main(int argc, const char * argv[]) { register int a=1; printf("a=%d\n",a); return 0; }
上面咱们说到变量的存储类型,其实在C语言中还有两种存储类型:常量存储区和代码存储区,分别用于存储字符串常量、使用const修饰的全局变量以及二进制函数代码。blog
在C语言中没有其余高级语言public、private等修饰符,来限定变量和函数的有效范围,可是却有两个相似的关键字能达到相似的效果:extern和static。
咱们知道在C语言中变量的定义顺序是有严格要求的,要使用变量则必须在使用以前定义,extern用于声明一个已经存在的变量,这样一来即便在后面定义一个变量只要前面声明了,也一样可使用。具体的细节经过下面的代码相信你们均可以看明白:
// // 2.1.c // ScopeAndLifeCycle // // Created by Kenshin Cui on 14-7-12. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> //若是在main函数下面定义了一个变量a,若是在main上面不进行声明是没法在main中使用a的; //一样若是只进行了extern声明不进行定义同样会报错,由于extern并不负责定义变量a而仅仅是声明一个已经定义过的变量; //固然若是说在main上面定义int a;去掉main下面的定义一样是能够的,至关于在上面定义,但若是两个地方都定义a的话(main上面的extern去掉),则程序认为上面的定义是声明,只是省略了extern关键字; //第一种状况,在下面定义,不进行声明,报错 int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } int a; //第二种状况,在上面定义,正确 int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } //第三种状况,在下面定义在上面声明,正确 extern int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } int a; //第四种状况,只在上面声明(编译时没有问题,由于上面的声明骗过了编译器,但运行时报错,由于extern只能声明一个已经定义的变量),错误 extern int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } //第五种状况,上下同时定义(这种方式是正确的,由于上面的定义会被认为是省略了extern的声明),正确 int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } int a; //其实下面的状况也是不会出错的 int a; int a; int main(int argc, const char * argv[]) { printf("a=%d\n",a); return 0; } int a; int a; //第六种状况,将全局变量声明为局部变量,可是它的实质仍是全局变量,正确 int a; int main(int argc, const char * argv[]) { extern int a; printf("a=%d\n",a); return 0; } int a; //第七种状况,在函数内部从新定义一个变量a,虽然不会报错,可是两个a不是同一个 int a; int main(int argc, const char * argv[]) { int a; printf("a=%d\n",a);//注意这里输出的a实际上是内部定义的a,和函数外定义的a没有关系 return 0; } int a;
若是两个文件同时定义一个全局变量,那实质上他们指的是同一个变量。从下面的例子能够看出,在main.c中修改了变量a以后message.c中的变量a值也修改了。
须要注意,在上面的代码中不管在message.h中将a定义前加上extern,仍是在main.h中的a定之前加上extern结果都是同样的,extern一样适用。和在单文件中同样,不能两个定义都添加extern,不然就没有定义了。若是把message.c中a的定义(或声明)去掉呢,那么它可否访问main.c中的全局变量a呢,答案是否认的(这和在一个文件中定义了一个函数在另外一个文件不声明就直接用是相似的)。
extern做用于函数就再也不是简单的声明函数了,而是将这个函数做为外部函数(固然还有内部函数,下面会说到),在其余文件中也能够访问。可是你们应该已经注意到,在上面的代码中message.c中的showMessage前面并无添加extern关键字,在main.c中不是照样访问吗?那是由于这个关键字是能够省略的,默认状况下全部的函数都是外部函数。
和做用于变量不一样,上面main.c和message.c中的extern均可以省略,在这里extern的做用就是定义或声明一个外部函数。从上面能够看到在不一样的文件中能够定义同一个变量,它们被视为同一个变量,可是须要指出的是外部函数在一个程序中是不能重名的,不然会报错。
其实在前面的例子中咱们已经看到static关键字在变量中的使用了,在例子中使用static定了一个局部变量,并且咱们强调static局部变量在函数中只被初始化一次。那么若是static做用于全局变量是什么效果呢?若是static做用于全局变量它的做用就是定义一个只能在当前文件访问的全局变量,相等于私有全局变量。
从上面的输出结果能够看出message.c中的变量a和main.c中的变量a并非同一个变量,事实上message.c中的变量a只能在message.c中使用,虽然main.c中的变量a是全局变量可是就近原则,message.c会使用本身内部的变量a。固然,上面例子中main.c中的变量a定义成静态全局变量结果也是同样的,只是这样若是还有其余源文件就不能使用a变量了。可是main.c中的a不能声明成extern,由于main.c不能访问message.c中的变量a,这样在main.c中就没变量a的定义了。
static做用于函数和做用于变量实际上是相似的,若是static做用于函数则这个函数就是内部函数,其余文件中的代码不能够访问。下面的代码在运行时会报错,由于mesage.c中的showMessage()函数是私有的,在main.c中尽管进行了声明,能够在编译阶段经过,可是在连接阶段会报错。
最后作一下简单总结一下: