本文将主要从static在C和C++共有的作用及C++特有的作用两个方面进行解析。
在C和C++*有的作用
隐藏(对变量、函数均可)
当同时编译多个文件时,所有未加static前缀的全局变量或全局函数都具有全局性。举例来说明,同时编译两个源文件,一个是a.c,另一个是main.c,如下:
1 //a.c
2 char a = 'A'; // global variable
3 void msg()
4 {
5 printf("Hello\n");
6 }
1 //main.c
2 int main()
3 {
4 extern char a; // extern variable must be declared before use
5 printf("%c ", a);
6 (void)msg();
7 return 0;
8 }
程序的运行结果是:A Hello
为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏。
保持变量内容的持久(全局生存期)
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见
PS:如果作为static局部变量在函数内定义,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
程序举例如下:
1 #include <stdio.h>
2
3 int fun(){
4 static int count1 = 10; //在第一次进入这个函数的时候,变量a被初始化为10!并接着自减1,以后每次进入该函数
5 //就不会被再次初始化了,仅进行自减1的操作;在static发明前,要达到同样的功能,则只能使用全局变量:
6 return count1--;
7
8 }
9
10 int count2 = 1;
11
12 int main(void)
13 {
14 printf("global\t\tlocal static\n");
15 for (; count2 <= 10; ++count2)
16 printf("%d\t\t%d\n", count2, fun());
17
18 return 0;
19 }
运行结果:
1 global local static
2 1 10
3 2 9
4 3 8
5 4 7
6 5 6
7 6 5
8 7 4
9 8 3
10 9 2
11 10 1
基于以上两点可以得出一个结论:把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。
默认初始化变量为0
其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加‘\0’;太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是‘\0’;不妨做个小实验验证一下。
1 #include <stdio.h>
2
3 int main()
4 {
5 static int a;
6 static char str[10];
7 printf("integer: %d; string: (begin)%s(end)", a, str);
8 return 0;
9 }
PS: 需要注意的是,一个类中的static变量至少需要在类外声明(要不在被引用时会报错),声明的同时可以初始化。
在C++中特有的作用
一般情况下,如果有n个同类的对象,那么每一个对象都分别有自己的数据成员,不同对象的数据成员各自有值,互不相干。但是有时人们希望有某一个或几个数据成员为所有对象所共有,这样可以实现数据共享。
可以使用全局变量来达到共享数据的目的。例如在一个程序文件中有多个函数,每一个函数都可以改变全局变量的值,全局变量的值为各函数共享。但是用全局变量的安全性得不到保证,由于在各处都可以*地修改全局变量的值,很有可能偶然失误,全局变量的值就被修改,导致程序的失败。因此在实际工作中很少使用全局变量。
如果想在同类的多个对象之间实现数据共享,也不要用全局对象,可以用静态的数据成员。
静态数据成员
静态数据成员是一种特殊的数据成员。它以关键字static开头。例如:
1 class Box
2 {
3 public:
4 int volume();
5 private:
6 static int height; //把height定义为静态的数据成员
7 int width;
8 int length;
9 };
如果希望各对象中的height的值是一样的,就可以把它定义为静态数据成员,这样它就为各对象所共有,而不只属于某个对象的成员,所有对象都可以引用它。
静态的数据成员在内存中只占一份空间。每个对象都可以引用这个静态数据成员。静态数据成员的值对所有对象都是一样的。如果改变它的值,则在各对象中这个数据成员的值都同时改变了。这样可以节约空间。
关于静态数据成员的几点说明:
1) 如果只声明了类而未定义对象,则类的一般数据成员是不占内存空间的,只有在定义对象时,才为对象的数据成员分配空间。但是静态数据成员不属于某一个对象,在为对象所分配的空间中不包括静态数据成员所占的空间。静态数据成员是在所有对象之外单独开辟空间。只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被引用。
2) 对于静态变量,如果在一个函数中定义了静态变量,在函数结束时该静态变量并不释放,仍然存在并保留其值。静态数据成员也类似,它不随对象的建立而分配空间,也不随对象的撤销而释放(一般数据成员是在对象建立时分配空间,在对象撤销时释放)。静态数据成员是在程序编译时被分配空间的,到程序结束时才释放空间。
3) 静态数据成员至少需要在类外声明(要不在被引用时会报错),声明同时可以初始化,但只能在类体外进行初始化。如:
int Box::height=10; //表示对Box类中的数据成员初始化
其一般形式为:
数据类型 类名::静态数据成员名=初值;
不必在初始化语句中加static。
注意,不能用参数对静态数据成员初始化。如在定义Box类中这样定义构造函数是错误的:
Box(int h,int w,int len):height(h){ } //错误,height是静态数据成员
另外,如果一个成员变量同时是static和const的,那是可以在声明的时候就赋初值,如:
const static height = 10;
or
static const height = 10;
4) 静态数据成员既可以通过对象名引用,也可以通过类名来引用(该数据成员的访问权限是public才可行)。请观察下面的程序。
1 #include <iostream>
2 using namespace std;
3 class Box
4 {
5 public:
6 Box(int, int);
7 int volume();
8 static int height; //把height定义为公用的静态的数据成员
9 int width;
10 int length;
11 };
12 Box::Box(int w, int len) //通过构造函数对width和length赋初值
13 {
14 width = w;
15 length = len;
16 }
17 int Box::volume()
18 {
19 return(height*width*length);
20 }
21 int Box::height = 10; //对静态数据成员height初始化
22 int main()
23 {
24 Box a(15, 20), b(20, 30);
25 cout << a.height << endl; //通过对象名a引用静态数据成员
26 cout << b.height << endl; //通过对象名b引用静态数据成员
27 cout << Box::height << endl; //通过类名引用静态数据成员(在这里height需要是public的,上边程序要稍微改一下)
28 cout << a.volume() << endl; //调用volume函数,计算体积,输出结果
29 }
上面3个输出语句的输出结果相同(都是10)。这就验证了所有对象的静态数据成员实际上是同一个数据成员。
请注意,在上面的程序中将height定义为公用的静态数据成员,所以在类外可以直接引用。可以看到在类外可以通过对象名引用公用的静态数据成员,也可以通过类名引用静态数据成员。即使没有定义类对象,也可以通过类名引用静态数据成员。这说明静态数据成员并不是属于对象的,而是属于类的,但类的对象可以引用它。如果静态数据成员被定义为私有的,则不能在类外直接引用,而必须通过公用的成员函数引用。
5) 有了静态数据成员,各对象之间的数据有了沟通的渠道,实现数据共享,因此可以不使用全局变量。全局变量破坏了封装的原则,不符合面向对象程序的要求。但是也要注意公用静态数据成员与全局变量的不同,静态数据成员的作用域只限于定义该类的作用域内(如果是在一个函数中定义类,那么其中静态数据成员的作用域就是此函数内)。在此作用域内,可以通过类名和域运算符“::”引用静态数据成员,而不论类对象是否存在。
静态成员函数
与数据成员类似,成员函数也可以定义为静态的,在类中声明函数的前面加static就成了静态成员函数。如:
static int volume( );
和静态数据成员一样,静态成员函数是类的一部分,而不是对象的一部分。
如果要在类外调用公用的静态成员函数,要用类名和域运算符“::”。如
Box::volume( );
实际上也允许通过对象名调用静态成员函数,如
a.volume( );
但这并不意味着此函数是属于对象a的,而只是用a的类型而已。
与静态数据成员不同,静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员。
我们知道,当调用一个对象的成员函数(非静态成员函数)时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有this指针。既然它没有指向某一对象,就无法对一个对象中的非静态成员进行默认访问(即在引用数据成员时不指定对象名)。
可以说,静态成员函数与非静态成员函数的根本区别是:非静态成员函数有this指针,而静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。
静态成员函数可以直接引用本类中的静态数据成员,因为静态成员同样是属于类的,可以直接引用。在C++程序中,静态成员函数主要用来访问静态数据成员,而不访问非静态成员。
例如,在一个静态成员函数中有以下语句:
cout<<height<<endl; //若height已声明为static,则引用本类中的静态成员,合法
cout<<width<<endl; //若width是非静态数据成员,不合法
但是,并不是绝对不能引用本类中的非静态成员,只是不能进行默认访问,因为无法知道应该去找哪个对象。
如果一定要引用本类的非静态成员,应该加对象名和成员运算符“.”。如
cout<<a.width<<endl; //引用本类对象a中的非静态成员
假设a已定义为Box类对象,且在当前作用域内有效,则此语句合法。
下例展示了静态成员函数的应用:
1 #include <iostream>
2 using namespace std;
3 class Student //定义Student类
4 {
5 public:
6 Student(int n, int a, float s) :num(n), age(a), score(s){ } //定义构造函数
7 void total();
8 static float average(); //声明静态成员函数
9 private:
10 int num;
11 int age;
12 float score;
13 static float sum; //静态数据成员
14 static int count; //静态数据成员
15 };
16 void Student::total() //定义非静态成员函数
17 {
18 sum += score; //累加总分
19 count++; //累计已统计的人数
20 }
21 float Student::average() //定义静态成员函数
22 {
23 return(sum / count);
24 }
25
26 float Student::sum = 0; //对静态数据成员初始化
27 int Student::count = 0; //对静态数据成员初始化
28
29 int main()
30 {
31 Student stud[3] = { //定义对象数组并初始化
32 Student(1001, 18, 70),
33 Student(1002, 19, 78),
34 Student(1005, 20, 98)
35 };
36 int n;
37 cout << "please input the number of students:";
38 cin >> n; //输入需要求前面多少名学生的平均成绩
39 for (int i = 0; i < n; i++) //调用3次total函数
40 stud[i].total();
41 cout << "the average score of " << n << " students is " << Student::average() << endl; //调用静态成员函数
42
43 return 0;
44 }
运行结果为:
please input the number of students:3↙
the average score of 3 students is 82.3333
另外,静态成员函数不能被声明为const(将成员函数声明为const就是承诺不会修改该函数所属的对象)。静态成员函数也不能被声明为虚函数。
参考资料
《C++ Primer》第四版