在面试的时候,考察语言基础的时候,有时候会遇到问题“static关键字的作用是什么”,以及相关的问题。这篇文章里面尽可能总结的全面一点。
对于c语言。
static可以修饰c语言的变量,表示静态变量,有个很cool的(或者和普通变量很不一样)的属性,就是他的生命周期与当前的生命周期一样。典型的场景就是:在函数中定义了static变量,在函数外面还能够引用这个变量。经常用来作计数器。这是函数内外一种通信的方式,就像全局变量一样。
引申的问题1:为什么具有这种属性?即为什么生命周期和当前程序一样。
答:普通变量分配在栈上,随着函数推出,栈清空,变量销毁;而静态变量的存储区域在程序的静态存储区,就像全局存储区、堆、栈、代码区一样,是一种程序的存储区域。这种存储区在程序load的时候被初始化,变量的生命开始,直到程序结束,才随着程序的销毁而被销毁。从存储的角度来看变量生命周期,就很显然了;其实那种“计数”特性只是一种副产品,从这个角度看是自然而然的事情。
小问题:
静态存储区什么时候被初始化?
答:很多人对“编译时”和“运行时”会搞混。很容易回答在程序编译的时候,被初始化成xxx。其实是在运行时被初始化,就是在程序刚刚load的时候,确切地说是在进入main函数之前。如果没有默认值,则默认值是0x00。那么编译的时候编译器为静态变量做什么了?其实只是在符号表(记录内部变量的表格)中做了记录,如果只是声明而没有定义,则只有符号表的入口,编译的时候并不为它分配空间;反之,则分配空间并用该初始值作为它的值。
引申的问题2:静态变量和全局变量有什么区别?
答:相同的是生命周期,那有什么不同呢?这里和c对static的要求有关了。static的主要作用是控制被修饰变量(函数)的可见范围,即,只对当前文件可见,对外文件不可见。而全局变量是全局可见,全局可引用的。
例如:
在1.c文件中定义static变量,如下:
static int iStaticVariableFrmPlayCpp = 100;
在2.c文件中尝试引用它,如下:
extern int iStaticVariableFrmPlayCpp;
iStaticVariableFrmPlayCpp = 99;
cout << "the value of iStaticVariableFrmPlayCpp is: " << iStaticVariableFrmPlayCpp << endl;
编译器会报编译错误。
但是如果把1.c中的static关键字去掉,使之成为一个全局变量,则不会报错。
static还可以修饰函数,表示静态函数,也是控制函数的可见范围,即只在本文件内部可见,在文件外部不可见。
总的来说,static在c语言中的主要作用还是控制变量和函数的可见范围,能更好滴组织代码。
对于c++语言。
对于c++语言,上述的约定亦然存在。不过c++之于c的最大不同,就是引入了面向对象设计,也就是有了类的概念。在类中,static又有了新的作用。
static修饰类的成员变量,则该变量是“类变量”,不属于类的某个实例。
static修饰类的成员函数,则该函数是“类函数”,不属于类的某个实例,而且只能够访问static成员变量。
引申的问题3:这是怎么做到的?其时和“引申的问题1”一样,就是c++编译器怎么做到这一点的?如果自己设计编译器,怎么支持这种特性?
答:static的这些特性记忆起来挺麻烦的哈。其实也不麻烦,关键不去记那些表面的东西,还是从底层的角度来看。答案同“引申的问题1”一样,想变量的存储区域。不论是修饰c中的变量还是修饰c++的类成员变量,这些变量最终存储在哪里?在静态存储区。静态存储区的东西,肯定不能和某个运行时生成的类实例相关联啊,静态存储区,在main函数之前就已经初始化好了,那时候什么实例都没初始化呢,如果真要关联,和谁关联?怎么关联?正因为如此,静态成员函数也只能够访问静态变量,因为也无法获知静态变量与哪个实例关联。而且,静态变量和静态函数,在编译期间就能够完全确定的,编译期间也无法获得运行期间的实例情况。
对于java语言。
相比c++,java是更加面向对象的一种语言。java中所有东西都是在类中声明定义的,所以,“在c中的static的作用”,在java中并不适用。不过,在“c++中的static的作用”,在java中都适用。除此之外,java的类中还支持“static程序块儿”,就是,也没有函数签名,在class中来这么一段:
class xxx
{
static
{
......
}
}
在类做初始化的时候,会首先执行static中的语句。static程序块儿,也只能访问static变量,更像一个没有函数签名的构造函数,可能把名字省了吧,反正大家都知道那是干啥的。
暂时总结到这里吧。