static 是由最初的限制和持久过渡到后的共享。
C语言时代的static
在C语言中,只存在函数和变量。
A、函数和全局变量,一旦加上static关键字,就限制了其作用域为本文件有效。
B、局部变量,一旦加上static关键字,其生命其为整个应用程序。
C++语言时代的static
在C++语言中,除了向C语言兼容外,提供了类的支持,同样static在C语言的特性在C++同样有效。
C、类的static关键字,在C++的类中,static只能用于修饰数据成员和函数成员(不能修饰构造函数和析构函数),用于该类所具有的信息而不属具体某个对象.
D、<注意>如果在类的成员函数中定义了static局部变量,其值会在所有对象中共享!这一点不好!
C#语言时代的static
在C#语言中,基于类static访问特性和C++相似。同时,也有了一些改变。
E、在C#中,禁止了类成员函数中的局部变量用static修饰。
F、类的静态成员字段的初始化更加简单,可以直接在类定义中赋值。初始值设定是按照文本顺序执行,如果类中有静态字段而无静态构造函数,系统会自动生成,并先于静态构造函数执行。
G、类提供了static修饰构造函数。类的静态构造函数在给定应用程序域中至多执行一次:只有创建类的实例或者引用类的任何静态成员才激发静态构造函数
可见static从C-->C++是由简入繁。
C#作为一种全新的语言,他消除了C++语言的一些复杂性,全面支持面向对象。所以没有C语言的全局函数和全局变量。类成员函数内也不支持静态成员变量了。
下面用下事例代码,全面阐述以上内容
// C语言事例A
// 用来表示不能被其它文件访问的全局变量和函数。, 但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.
// 使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
// c2.c
int g_i = 0 ;
static int g_is = 0 ;
int f1()
{
return 1;
}
static int f2()
{
return 2;
}
// c1.c
extern int g_i; // 引用文件c3.h的全局变量
extern int g_is; // 声明有一个外部全局变量g_is
extern int f1();
extern int f2();
#include < stdio.h >
void main()
{
printf("%d\n",g_i);
//printf("%d\n",g_is); //报错
printf("%d\n",f1());
//printf("%d\n",f2()); //报错,不能访问的函数.
}
// C语言事例B
// 局部变通变量与局部静态变量的区别
// 1. 存储空间分配不同
// auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.
// 2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次
// 3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符, 而auto类型的初值是不确定的.
// c3.c
#include < stdio.h >
void Print()
{
static int a=0;
int b=0;
a++;
b++;
printf("a = %d,b = %d \n",a,b);
}
void main()
{
for (int i=0; i<3; i++)
Print();
}
// 会发现a的会记住上一次的值。具有不可重入性。
/
// C++语言事例C
// 很多时候需要将类看作一个整体,当这个整体的一些相关信息就可以用static来维护和管理
// c4.c
#include < iostream.h >
class Test
{
private:
int a;
static char creator[16];
public:
static char* const GetCreator()
{
//a = 1; //静态成员函数不能访问类的非静态字段
return creator;
}
} ;
char Test::creator[ 16 ] = " 这是Fung建的类! " ;
void main()
{
cout<<Test::GetCreator()<<endl;
}
// C++语言事例D
// 尽量不要在类的成员函数中定义static变量,这可能导致程序混乱.
// c5.c
#include < iostream.h >
class Test
{
public:
void fun()
{
static int a=0;
a += 3;
cout<<a<<endl;
}
} ;
void main()
{
Test* t = new Test();
t->fun();
t->fun();
Test* k = new Test();
k->fun(); //这里第一输出就变成了9
}
// ★静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。
// c6.c
#include < iostream.h >
class Test
{
static int s;
int a;
public:
int fun(int i = s) //静态数据成员可以成为成员函数的可选参数
{
return i;
}
//int fun1(int i = a) //这个是当然不行了
//{
// return i;
//}
} ;
int Test::s = 5 ;
void main()
{
Test* t = new Test();
cout<<t->fun()<<endl;
}
/
// C#语言事例
// (1)用于对静态字段、只读字段等的初始化。
// (2)添加static关键字,不能添加访问修饰符,因为静态构造函数都是私有的。
// (3)类的静态构造函数在给定应用程序域中至多执行一次:只有创建类的实例或者引用类的任何静态成员才激发静态构造函数
// (4)静态构造函数是不可继承的,而且不能被直接调用。
// (5)如果类中包含用来开始执行的 Main 方法,则该类的静态构造函数将在调用 Main 方法之前执行。
// 任何带有初始值设定项的静态字段,则在执行该类的静态构造函数时,先要按照文本顺序执行那些初始值设定项。
// (6)如果没有编写静态构造函数,而这时类中包含带有初始值设定的静态字段,那么编译器会自动生成默认的静态构造函数。
using System;
class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}
public static int F(string s) {
Console.WriteLine(s);
return 1;
}
}
class A
{
public static int X = Test.F("Init A");
}
class B
{
public static int Y = Test.F("Init B");
}
// 或者产生如下输出:
// Init A
// Init B
// 1 1
// 或者产生如下输出:
// Init B
// Init A
// 1 1
// 这是因为 X 的初始值设定项和 Y 的初始值设定项的执行顺序无法预先确定,上述两种顺序都有可能发生;
// 唯一能够确定的是:它们一定会在对那些字段的引用之前发生。但是,下面的示例:
using System;
class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}
public static int F(string s) {
Console.WriteLine(s);
return 1;
}
}
class A
{
static A() {}
public static int X = Test.F("Init A");
}
class B
{
static B() {}
public static int Y = Test.F("Init B");
}
// 所产生的输出必然是:
// Init B
// Init A
// 1 1
// 这是因为关于何时执行静态构造函数的规则规定:
// B 的静态构造函数(以及 B 的静态字段初始值设定项)必须在 A 的静态构造函数和字段初始值设定项之前运行
有的东西你天天在用,但未必就代表你真正了解它,正如我之前所了解的 static 。
一、静态类
静态类与非静态类的重要区别在于静态类不能实例化,也就是说,不能使用 new 关键字创建静态类类型的变量。在声明一个类时使用static关键字,具有两个方面的意义:首先,它防止程序员写代码来实例化该静态类;其次,它防止在类的内部声明任何实例字段或方法。
1、静态类的主要特性:
[1] 仅包含静态成员。
[2] 无法实例化。
[3] 静态类的本质,是一个抽象的密封类,所以不能被继承,也不能被实例化。
[4] 不能包含实例构造函数。
[5] 如果一个类下面的所有成员,都需要被共享,那么可以把这个类定义为静态类。
2、静态类与私有构造函数区别:
[1] 私有构造器方式仍然可以从类的内部对类进行实例化,而静态类禁止从任何地方实例化类,其中包括从类自身内部。
[2] 使用私有构造器的类中,是允许有实例成员的,编译器不允许静态类有任何实例成员。
[3] 使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员,编译器将保证不会创建此 类的实例。
[4] C#编译器会自动把它标记为sealed。这个关键字将类指定为不可扩展;换言之,不能从它派生出其他类。
二、静态变量
1、通过static关键字修饰,是属于类,实例成员属于对象,在这个类第一次加载的时候,这个类下面的所有静态成员会被加载。
2、静态成员只被创建一次,所以静态成员只有一份,实例成员有多少个对象,就有多少份。
3、类加载的时候,所有的静态成员就会被创建在“静态存储区”里面,一旦创建直到程序退出,才会被回收。
4、变量需要被共享的时候,方法需要被反复调用的时候,就可以把这些成员定义为静态成员。
5、在静态方法中,不能直接调用实例成员,因为静态方法被调用的时候,对象还有可能不存在。
6、this/base 关键字在静态方法中不能使用,因为有可能对象还不存在。
7、可以创建这个类的对象,制定对象的成员在静态方法中操作。
8、在实例方法中,可以调用静态成员,因为这个时候静态成员肯定存在。
9、非静态类可以包含静态的方法、字段、属性或事件;
10、无论对一个类创建多少个实例,它的静态成员都只有一个副本;
11、静态方法和属性不能访问其包含类型中的非静态字段和事件,并且不能访问任何对象的实例变量;
12、静态方法只能被重载,而不能被重写,因为静态方法不属于类的实例成员;
13、虽然字段不能声明为 static const,但 const 字段的行为在本质上是静态的。这样的字段属于类,不属于类的实例。
三、静态方法
1、静态方法是不属于特定对象的方法;
2、静态方法可以访问静态成员变量;
3、静态方法不可以直接访问实例变量,可以在实例函数调用的情况下,实例变量做为参数传给静态方法;
4、静态方法也不能直接调用实例方法,可以间接调用,首先要创建一个类的实例,然后通过这一特定对象来调用静态方法。
四、静态构造函数
1、静态类可以有静态构造函数,静态构造函数不可继承;
2、静态构造函数可以用于静态类,也可用于非静态类;
3、静态构造函数无访问修饰符、无参数,只有一个 static 标志;
4、静态构造函数不可被直接调用,当创建类实例或引用任何静态成员之前,静态构造函数被自动执行,并且只执行一次。
例如:
class Program { public static int i =0; public Program() { i = 1; Console.Write("实例构造方法被调用"); } static Program() { i = 2; Console.Write("静态构造函数被执行"); } static void Main(string[] args) { Console.Write(Program.i);//结果为2,首先,类被加载,所有的静态成员被创建在静态存储区,i=0,接着调用了类的成员,这时候静态构造函数就会被调用,i=2 Program p = new Program(); Console.Write(Program.i);//结果为1,实力化后,调用了实例构造函数,i=1,因为静态构造函数只执行一次,所以不会再执行。 } }
五、静态变量的存储
使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员static修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、析构函数或类以外的类型。
静态全局变量
定义:在全局变量前,加上关键字 static 该变量就被定义成为了一个静态全局变量。
特点:A、该变量在全局数据区分配内存。 B、初始化:如果不显式初始化,那么将被隐式初始化为0。
静态局部变量
定义:在局部变量前加上static关键字时,就定义了静态局部变量。
特点: A、该变量在全局数据区分配内存。 B、初始化:如果不显式初始化,那么将被隐式初始化为0。 C、它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或 语句块结束时,其作用域随之结束。
静态数据成员
特点: A、内存分配:在程序的全局数据区分配。 B、初始化和定义: a、静态数据成员定义时要分配空间,所以不能在类声明中定义。 b、为了避免在多个使用该类的源文件中,对其重复定义,所在,不能在类的头文件中 定义。 c、静态数据成员因为程序一开始运行就必需存在,所以其初始化的最佳位置在类的内部实现。 C、特点 a、对相于 public,protected,private 关键字的影响它和普通数据成员一样, b、因为其空间在全局数据区分配,属于所有本类的对象共享,所以,它不属于特定的类对象,在没产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它。
D、访问形式 a、 类对象名.静态数据成员名
E、静态数据成员,主要用在类的所有实例都拥有的属性上。比如,对于一个存款类,帐号相对 于每个实例都是不同的,但每个实例的利息是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局区的内存,所以节省存贮空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了,因为它们实际上是共用一个东西。
静态成员函数
特点: A、静态成员函数与类相联系,不与类的对象相联系。 B、静态成员函数不能访问非静态数据成员。原因很简单,非静态数据成员属于特定的类实例。
作用: 主要用于对静态数据成员的操作。
调用形式: A、类对象名.静态成员函数名()
static静态变量的实例与分析
实例:
using System; namespace teststatic { class class1 { static int i = getNum(); int j = getNum(); static int num = 1; static int getNum() { return num; } static void Main(string[] args) { Console.WriteLine("i={0}",i); Console.WriteLine("j={0}", new class1().j); Console.Read(); } } }
现在分析上面的代码:
Console.WriteLine(string.Format("i={0}",i)); 这里i是static变量,而且类class1是第一次被引 用,要先为class1里面所有的static变量分配内存。尽管现在有超线程技术,但是指令在逻辑还是一条一条的按顺序执行的,所以 先为static int i分配内存,并且在该内存中保持int的缺省值0,接着再为static int num 变量分配内存,值当然也为0。
然后执行第二步,为变量赋值:先为static int i变量赋值,i=getNum(),看getNum里面的代码,就是return num,这个时候num的值是0,于是i就为0了。然后对变量num赋值,num=1;这行代码执行后,num就为1了。
所以最后的结果为:
i=0 j=1
首先分为两部分 寄存器和内存(包括缓存)
内存分为两部分 代码和数据
数据分为两部分 静态存储区和运行时存储
运行时存储分为 堆栈 和 堆
静态存储分为 全局静态存储 和 常量