17 cout<<”create one student”
19 strcpy(name,pname);
20
21 number++;
22
23 cout<
25 }
26
27 ~student() {
28
29 cout<<”destruct one student”<
31 number--;
32
33 cout<
35 }
36
37 static number(){
38
39 return number; }
40
41 protected:
42
43 char nme[40]
44
45 static int number;
46
47 };
48
49 void fn(){
50
51 student s1;
52
53 student s2;
54
55 cout<
57 }
58
59 main(){
60
61 fn();
62
63 cout<
65 }
程序输出结果如下:
create one student
1
create one student
2
2
destruct one student
1
destruct one student
0
0
上面的程序代码中我们使用了静态成员变量和静态成员函数,下面我们先阐述静态数据成员。
四,静态成员变量
在代码中我们可以看出,number既不是对象s1也不是对象s2的一部分,它是属于student这个类的。每一个student对象都有name成员,但是number成员却只有一个,所有的student对象共享使用这个成员。s1.number与s2.number是等值的。在student的对象空间中没有为number成员保留空间,它的空间分配不在student的构造函数里完成,空间回收也不再析构函数里面完成。因此与name成员不一样,它不会随着对象的产生或者消失而产生或消失。
由于静态数据成员的空间分配在全局数据区,因此在程序一开始运行的时候就必须存在,所以静态成员的空间的分配和初始化不可能在函数包括main主函数中完成,因为函数在程序中被调用的时候才为内部的对象分配空间。这样,静态成员的空间分配和初始化只能由下面的三种途径。一是类的外部接口的头文件,那里声明了类定义。二是类定义的内部实现,那里有类成员函数的定义和具体实现。三是应用程序的main()函数前的全局数据声明和定义处。由于静态数据成员必须实际的分配空间,因此不可能在类定义头文件中分配内存。另一方面也不能在头文件中类声明的外部定义,因为那会造成多个使用该类的源程序重复出现定义。静态数据成员也不能在main()函数的全局数据声明处定义。如果那样的话每一个使用该类的程序都必须在程序的main()函数的全局数据声明处定义一下该类的静态成员。这是不现实的。唯一的办法就是将静态数据成员的定义放在类的实现中。定义时候用类名引导,引用时包含头文件即可。
例如:
1 class a{
2 static int i;
3 public:
4 //
5 }
在类定义文件中 int a::i=1;
有两点需要注意的是
1.静态数据成员必须且只能定义一次,如果重复定义,连接器会报告错误。同时它们的初始化必须在定义的时候完成。对于预定义类型的静态数据成员,如果没有赋值则赋零值。对于用户自定义类型必须通过构造函数赋值,如果没有构造函数包括缺省构造函数,编译无法通过。
1 void fn(){
2
3 class foo{
4
5 static int i;//定义非法。
6
7 public:
8 //
9 }
10
11 }
五,静态成员函数
与静态成员变量一样,我们也可以创建一个静态函数成员,它为类的全部服务而不是为某一个类的具体对象服务。静态函数成员与静态成员一样,都是类的内部实现,属于类定义的一部分。程序student.cpp中的number()就是静态成员函数,它的定义位置与一般成员函数相同。
普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下this是缺省的。如函数add()实际上是写成this.add().但是与普通函数相比,静态成员函数由于不是与任何的对象相联系因此它不具有this指针,从这个意义上讲,它无法访问属于具体类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。如下面的代码:
1 class x {
2
3 int i;
4
5 static int j;
6
7 public:
8
9 x(int i=0):i(i){
10
11 j=i
12
13 }//非静态成员函数可以访问静态函数成员和静态数据成员。
14
15 int val() const {return i;}
16
17 static int incr(){
18
19 i++;//错误的代码,因为i是非静态成员,因此incr()作为静态成员函数无
20
21 //法访问它。
22
23 return ++j;
24
25 }
26
27 static int fn(){
28
29 return incr();//合法的访问,因为fn()和incr()作为静态成员函数可以互相访
30
31 //问。
32
33 }
34
35 };
36
37 int x::j=0;
38
39 main(){……}
根据上面的程序可以总结几点:
1. 静态成员之间可以相互的访问包括静态成员函数访问静态数据成员和访问静态成员函数。
2. 非静态成员函数可以任意的访问静态成员函数和静态数据成员。
3. 静态成员函数不能访问非静态成员函数和非静态数据成员。
由于没有this指针的额外开销,因此静态成员函数与全局函数相比速度上会有少许的增长。
六,理解控制连接
理解控制连接之前我们先了解一下外部存储类型的概念。一般的在程序的规模很小的情况下我们用一个源程序既可以表达完整。但事实上稍微有价值的程序都不可能只用一个程序来表达的,而是分割成很多的小的模块,每一个模块完成特定的功能,构成一个源文件。所有的源文件共享一个main函数。在不同的源文件之间为了能够相互进行数据或者函数的沟通,这时候通常声明数据或者函数为extern,这样用extern声明的数据或者函数就是全局变量或者函数。默认情况下的函数的声明总是extern的。在文件范围内定义的全局数据和函数对程序中的所有的编译单元来说都是可见的。这就是通常我们所说的外部连接。
但有时候我们可能想限制一个名字对象的可见性,想让一个对象在本地文件范围内是可见的,这样这个文件中的所有的函数都可以利用这个对象,但是不想让本地文件之外的其余文件看到或者访问该对象,或者外部已经定义了一个全局对象,防止内部的名字对象与之冲突。
通过在全局变量的前面加上static,我们可以做到这一点。在文件的范围内,一个被声明为static的对象或者函数的名字对于编译单元来说是局部变量,我们称之为静态全部变量。这些名字不使用默认情况下的外部连接,而是使用内部连接。从下面的例子可以看出static是如何控制连接的。
工程文件为first.prj由两个源文件组成。
1 // exam1.cpp
2
3 // exam2.cpp
4
5 //***************************************
6
7 // exam1.cpp