参考文章:
一、面向过程语言的static(例如c)
在面向过程语言中,static主要有三个作用:
(1)存储的内容持久化;函数在栈上分配的空间在此函数执行结束时会释放掉,而静态的变量或函数分配到全局数据区的内存中,直到程序结束才被释放。
(2)控制文件间指定数据的可见性;静态的变量或函数仅能在声明的文件中访问,对其他文件不可见。
(3)默认初始化为0。全局变量也具备这一属性,减少了置0操作。
1、静态全局变量
对于一个完整的程序,在内存中的分布情况如下图:
——————————
| 代码区 //low address |
| 全局数据区 |
| 堆区 |
| 栈区 //high address |
——————————
一般程序把新产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(包括函数内部的静态局部变量)存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
静态全局变量的特点:
(1)静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的,类似于c#的internal。
(2)其它文件中可以定义相同名字的变量,不会发生冲突。
静态全局变量和全局变量的区别:
(1)全局变量默认是动态的,作用域是整个工程,在声明之外的文件中,通过extern 全局变量名的声明,就可以使用全局变量;而静态全局变量即使通过extern也无法访问。
2、静态局部变量
执行到在函数体中的普通变量时,会给它分配栈内存。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量特点:
(1)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化。
(2)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。
3、静态函数
与静态全局变量相同。
二、面向对象语言的static(例如c#)
1、静态数据成员
对于非静态数据成员,该类每个对象都有自己的副本,而静态数据成员被当作是类的成员,而不专属某个对象。也就是说,对该类的所有对象来说,静态数据成员只分配一次内存,这块内存对所有对象共享的,所有对象获取它的值都是同一个。通常用于每个对象都相同的某个属性,且是需要保持一致的属性,比如存款年利率。
静态数据成员特点:
(1)某一个对象对其更新,该类的所有对象都将得到相同的新值。
(2)静态数据成员定义时要分配空间,所以不能在类声明中定义(应该是针对c++的声明定义方式)。
(3)静态数据成员和普通数据成员一样遵从public,protected,private访问规则。例如private static int i;只能在类中访问,虽然是静态数据。
静态数据成员相对于全局变量的优势:
(1)静态数据成员没有进入程序的全局命名空间,因此不存在与程序中其它全局名字冲突的可能性。
(2)可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能。(应该又是针对c++的定义全局变量)
2、静态成员函数
静态成员函数与静态数据成员一样,都是类的内部 实现,属于类定义的一部分,为该类而不是某一个对象服务。
静态成员函数特点:
(1)出现在类体外的函数定义不能指定关键字static(又是c++)。
(2)不能使用this指针指向该类的对象本身。因为它不属于任何一个对象。
(3)静态成员函数不能访问非静态成员函数和非静态数据成员,只能访问类的静态成员函数和静态数据成员。
三、什么情况下使用static
1、需要将函数中此变量的值保存至下一次调用。
2、需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。
四、内部机制
静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
因此,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;(按文章的知识来说,声明是不分配内存的,这里是不是出错?)二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
五、注意事项
1、类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。
2、不能将静态成员函数定义为虚函数。
3、由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。
4、由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X Window系统结合,同时也成功的应用于线程函数身上。
5、static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。
6、静态数据成员是静态存储的,所以必须对它进行初始化。
7、静态成员初始化与一般数据成员初始化不同:(又是c++)
初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;
初始化时使用作用域运算符来标明它所属类;
所以我们得出静态数据成员初始化的格式: <;数据类型><;类名>::<;静态数据成员名>=<;值>
8、为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。(肯特指定c++,其他语言有待考察)
9、static修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、析构函数或类以外的类型。(特指c#)