static 和 extern 都是C语言中的关键字;
static 是静态的意思,extern 是用来声明外部符号的;
在学习 static 和 extern 之前,我们先来了解一下作用域和生命周期。
作用域是程序设计概念,通常来说,一段程序代码中所有用到的名字并不总是有效(可用)的,而限定这个名字可用性的代码范围就是这个名字的作用域。
(1)局部变量的作用域是变量所在的局部范围;
(2)全局变量的作用是整个工程项目。
我们先来通过示例理解一下(1):
可以看到上面代码中第二个 printf()函数中的参数a是不可用的, 这是因为这个a不在我们创建的变量a的作用域内。
再来看:
当我们把变量a的创建放在小花括号外,这时我们创建的变量a的作用域就是整个主函数。
同样的,我们在主函数中定义的局部变量在其他自定义函数中也是不能用的。
我们再来通过示例理解(2):
相信通过上面的几个实验,我们已经能基本掌握上面的两句话。
再来说一下生命周期:
生命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的一个时间段。
(1)局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域变量销毁,生命周期结束。
(2)全局变量的生命周期是:整个程序的生命周期。
我们来看下面两个代码有什么不同:
可以看到在我们给创建 j 变量前加了一个 static 之后,打印的结果发生了变化,通过这个例子我们来猜测 static 的作用。
在没加 static 之前,我们在主函数中四次调用 print ()函数,每次进入 print ()函数时都会重新初始化一次变量 j ,因此每次 j++ 前 j 的值都为0,所以打印出了四个1;加上 static 之后,第一次进入 print()函数时初始化 j 为0,j++为1后打印,print()函数完成,第二次执行 print()函数后打印了2。
我们来倒推一下,此时 j 为2,所以 j++之前 j 的值应该还是1,我们可以猜一下在第二次进入 print()函数的时候是不是就不执行初始化 j 这一条代码了,那么 j++之前 j 还是上次出 print()函数时 j 的值也就是1,j++后 j 为2再打印,以此类推。
按照上面我们的猜测好像说的通,事实上我们已经大致猜出了 static 的作用。在第二次进入 print()函数时,确实不执行 static 修饰的这一条代码。
总结:(1)static 修饰的局部变量在进入作用域前就存在,出作用域也不销毁,而且用的是上次留下来的值。
(2)编译器在编译代码的时候,就为静态变量(即 static 修饰的变量)分配了地址,而不是进入函数创建这种变量。
(3)static 修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是一样的,生命周期就和程序的生命周期一样了,只有程序结束,变量才销毁,内存才回收。但是作用域是不变的。
关于局部变量和全局变量的存储我在这篇文章中有简单介绍过 —> 数据和变量
(4)未来一个变量出了函数后,我们还想保留值,等下次进入函数继续使用,就可以使用 static 修饰。
先来看上面的代码, 我们在 Add.c 文件中创建了一个全局变量a,如果我们想在 main.c 文件中使用这个a,因为全局变量默认是带有外部链接属性的,所以只需要用 extern 声明外部变量a即可使用。
再来看:
在我们使用 static 修饰全局变量a后, 在 main.c 文件中就不能正常使用了,为什么呢?
总结:(1)static 修饰全局变量的时候,改变了全局变量的外部链接属性,使得外部链接属性变成了内部链接属性。
(2)这种变量只能在自己所在的 .c 文件中使用,其他 .c 文件中无法使用。
(3)如果一个全局变量只想在所在的源文件内部使用,不想被其他文件发现,就可以使用 static 修饰。
上面的代码说明了一个问题,函数也是有外部链接属性的。 只要在其他的 .c 文件中正确声明,也是可以直接使用的。
同样的,如果我们写的自定义函数不想被其他人使用,只要用 static 修饰函数即可。
static 修饰函数,让函数的外部链接属性变成了内部链接属性,使得函数只能在自己所在的 .c 文件中使用,其他 .c 文件中无法使用。
其实 static 修饰函数和 static 修饰全局变量是一模一样的。