我们知道,不同类型的变量有不同的存储类型、不同的生命周期、不同的作用域。这讲介绍2个比较重要的关键字:
static
和extern
。static和extern不仅可以用在变量上,还可以用在函数上。
一、变量类型
C语言根据变量的存储类型的不同,可以把变量分为:
自动变量、
静态变量、
寄存器变量`。自动变量
是存储在堆栈
中的。
静态变量
是存储在静态内存
中的,也就是不属于堆栈。
哪些是静态变量:
所有的
全局变量
都是静态变量
被关键字
static
修饰的局部变量也是静态变量
生命周期:静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束。
二、extern与函数
我们知道:如果一个程序中有多个源文件(.c),编译成功会生成对应的多个目标文件(.obj),这些目标文件还不能单独运行,因为这些目标文件之间可能会有关联,比如a.obj可能会调用c.obj中定义的一个函数。将这些相关联的目标文件链接在一起后才能生成可执行文件。
先来理解2个概念:
外部
函数:如果在当前文件中定义的函数允许
其他文件访问、调用,就称为外部
函数。C语言规定,不允许有同名的外部函数。
内部
函数:如果在当前文件中定义的函数不允许
其他文件访问、调用,只能在内部使用,就称为内部
函数。C语言规定不同的源文件可以有同名的内部函数,并且互不干扰。
接下来就演示在一个源文件中调用另外一个源文件定义的函数,比如在main.c中调用one.c中定义的one函数。
1.首先在one.c中定义了一个one函数
如果你想让这个one函数可以被main.c访问,那么one函数就必须是外部
函数。完整的定义是要加上extern
关键字。
不过这个extern跟auto关键字一样废,完全可以省略
,因为默认情况下,所有的函数就是外部函数。我们可以简化一下:
2.接下来,我想在main.c的main函数中,调用one.c中的one函数
怎样才能调用one.c中的one函数呢?你可能会产生2个想法:
想法1:直接在main函数中写上one();
这个做法肯定不行
,因为main函数根本不知道one函数的存在,怎么调用呢?这个在标准C编译器里面会报错的,但是在Xcode中只是个警告。
想法2:在main.c中包含one.c文件
大家都知道#include的作用纯粹就是内容拷贝,所以又相当于
哎,这么一看好像是对的哦,在main函数前面定义了个one函数,然后在main函数中调用了这个one函数。从语法上看是对的,所以编译是没问题的。但是这个程序不可能运行成功
,因为在链接的时候会报错。我们已经在one.c中定义了one函数,现在又在main.c中定义one函数,C语言规定不允许有同名的外部函数,链接的时候链接器会发现在one.obj和main.obj中定义了同一个函数,会直接报错,Xcode中的错误信息是这样的:
duplicate symbol _one是说one这个标识符重复了,linker是指链接器。
上面的2种想法都是不可行的,其实思路是一致的:让main函数知道one函数的存在。正确的做法应该是在main函数前面对one函数进行提前声明(看清楚,是声明,不是定义,定义和声明是两码事)
。
3.在main函数前面对one函数进行提前声明
你想要把其他源文件中定义的外部
函数拿过来声明,完整的做法,应该使用extern
关键字,表示引用别人的"外部函数"
运行程序,从控制台输出可以发现 "one.c中定义的one函数" 已经被 "main.c的main函数" 成功调用了。
也有人可能会马上冒出一个想法:假如除开one.c,还有其他源文件也有定义这个one函数怎么办?那main函数调用的究竟是谁的one函数啊?放心,绝对不会有这种情况,刚才不是说了么,不允许重复定义同一个外部函数,不然链接器会报错的,所以只会有一个外部one函数。
上述就是extern
关键字对函数的作用:用来定义和声明一个外部函数。其实extern又跟auto一样废,完全可以省略。于是,我们可以简化成这样:
为了模块化地开发,在正规的项目里面,我们会把one函数的声明写到另一个头文件中,当然,这个头文件的命名最好有意义、规范一点,比如叫one.h。以后,谁想调用这个one函数,包含one.h这个头文件就行了。于是最后的代码结构是这样的:
三、static与函数
1.定义内部函数
从上面的例子可以看出,one.c中定义的one函数是可以被其他源文件访问的。其实有时候,我们可能想定义一个"内部函数",也就是不想让其他文件访问本文件中定义的函数。这个非常简单,你只需要在定义函数的时候加个static
关键字即可。
(我们就在上面例子的代码基础上进行修改)
我在void one()的前面加了个static,代表one函数是个内部函数。
然后你会发现程序运行不起来了,在链接的时候就报错了。报错的原因很简单:我们在main.c中调用了one.c中定义的one函数,但是现在one.c的one函数是个"内部函数",不允许其他文件访问。我们来看看错误信息:
第1个红框中的Undefined symbols...意思是one这个标识符没有被定义,也就是找不到one;第2个红框的linker表明是链接器报错了。
但这个程序是可以编译成功的,因为我们在main函数前面声明了one函数(函数的声明和定义是两码事),这个函数声明可以理解为:在语法上,骗一下main函数,告诉它one函数是存在的,所以从语法的角度上main函数是可以调用one函数的。究竟这个one函数存不存在呢,有没有被定义呢?编译器是不管的。在编译阶段,编译器只会检测单个源文件的语法合不合理,并不检测函数有没有定义,只有在链接的时候才会检测这个函数存不存在,也就是有没有被定义。
我们再来讨论一个问题,为什么好多情况下都是可以成功编译,但是链接的时候报错呢?只要你理解编译和链接的作用就好办了。
所谓
编译
,就是单独检查每个源文件的语法是否合理,并不会检查每个源文件之间的关联关系,一个源文件编译成功就生成一个目标文件。
所谓
链接
,就是检查目标文件的关联关系,将相关联的目标文件组合在一起,生成可执行文件。
看完这2个概念,再回去思考下前面报的错,应该可以完全明白了。
2.声明内部函数
我们还可以用static声明一个内部函数
1 #include <stdio.h>
2
3 static void test();
4
5 int main(int argc, const char * argv[])
6 {
7 test();
8 return 0;
9 }
10
11 static void test() {
12 printf("调用了test函数");
13 }
在第11行定义了一个test函数,这是一个内部函数,接着在第3行对test函数进行提前声明,然后就可以在第7行可以调用test()函数了
四、static、extern与函数的总结
1.static
在定义函数时,在函数的最左边加上static
可以把该函数声明为内部函数
(又叫静态函数),这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部
函数,则互不干扰。
static也可以用来声明一个内部函数
2.extern
在定义
函数时,如果在函数的最左边加上关键字extern
,则表示此函数是外部函数,可供其他文件调用。C语言规定,如果在定义函数时省略extern
,则隐含为外部函数。
在一个文件中要调用其他文件中的外部函数,则需要在当前文件中用
extern声明
该外部
函数,然后就可以使用,这里的extern也可以省略。
五、对变量的作用
extern 关键字对变量的作用
1、不同源文件中的同名变量
前面讲到,你在一个源文件中无论写多少遍全局变量int a;,它们代表的都是同一个变量。还有一个事实,假如在另一个源文件中也有全局变量int a;,那么这两个源文件的所有全局变量int a;都代表着同一个变量。extern关键字还是适用的,比如:
和
或者是:
和
上面的两种情况下,test.c和main.c中使用的全局变量a都还是代表着同一个变量
注意了,不可以两个文件的所有全部变量a都用extern,下面的做法是错误的:
和
因为extern
是用来声明
一个已经定义过的变量
,这两个文件都是在声明变量,没有人定义变量,在链接的时候肯定报错:
大致错误意思是:标示符a未定义
static 关键字对变量的作用
但很多时候,我们并不想让源文件中的全局变量跟其他源文件共享,相当于私有的全局变量,那么你就得用static关键字来定义变量。
这样写完,test.c和main.c的变量a分别代表着不同的变量,它们是没有联系的、互不干扰的。也就是说,main.c无法访问test.c中的变量a,因此在main.c中将a修改为10后,test.c中的a依然为0。输出结果:
。
其实static还可以用来修饰局部变量。
static和extern的总结
1.extern可以用来声明
一个全局变量,但是不能用来定义变量
2.默认情况下,一个全局变量是可以供多个源文件共享的,也就说,多个源文件中同名的全局变量都代表着同一个变量
3.如果在定义全局变量的时候加上static关键字,此时static的作用在于限制该全局变量的作用域,只能在定义该全局变量的文件中才能使用,跟其他源文件中的同名变量互不干扰
注:本文转自李明杰老师的博文。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。