在结构化程序设计中程序模块的基本单位是函数,因此模块间对内存中数据的共享是通过函数与函数之间的数据共享来实现的,其中包括两个途径——参数传递和全局变量。
面向对象的程序设计方法兼顾数据的共享与保护,将数据与操作数据的函数封装在一起,构成集成度更高的模块。类中的数据成员可以被同一类中的任何一个函数访问这样一方面在类内部的函数之间实现了数据的共享,另一方面这种共享是受限制的,可以设置适当的访问控制属性。把共享只限制在类的范围之内,对类外来说,类的数据成员仍是隐藏的,达到了共享与隐藏两全。
然而这些还不是数据共享的全部。对象与对象之间也需要共享数据。静态成员是解决同一个类的不同对象之间数据和函数共享问题的。例如,可以抽象出某公司全体雇员的共性,设计如下雇员类:
class Employee {
private:
int empNo;
int id;
string name;
...
//其他数据成员与函数成员略
}
如果需要统计雇员总数,这个数据存放在什么地方呢?若以类外的变量来存储总数,不能实现数据的隐藏。若在类中增加一个数据成员用于存放总数,必然在每一个对象中都存储一个副本,不仅冗余,而且每个对象分别维护一个“总数”,容易造成数据的不一致性。由于这个数据应该是为 Employee 类的所有对象所共享的,比较理想的方案是类的所有对象共同拥有一个用于存放总数的数据成员,这就是下面要介绍的静态数据成员。
静态数据成员
我们说“一个类的所有对象具有相同的属性”,是指属性的个数、名称、数据类型相同各个对象的属性值则可以各不相同,这样的属性在面向对象方法中称为“实例属性”,在C++程序中以类的非静态数据成员表示。例如上述 Employee 类中的 empNo,id,name都是以非静态数据成员表示的实例属性,它们在类的每一个对象中都拥有一个复本,这样的实例属性正是每个对象区别于其他对象的特征。
面向对象方法中还有“类属性”的概念。如果某个属性为整个类所共有,不属于任何个具体对象,则采用 static 关键字来声明为静态成员。静态成员在每个类只有一个副本,由该类的所有对象共同维护和使用,从而实现了同一类的不同对象之间的数据共享类属性是描述类的所有对象共同特征的一个数据项,对于任何对象实例,它的属性值是相同的。简单地说,如果将“类”比作一个工厂,对象是工厂生产出的产品,那么静态成员是存放在工厂中、属于工厂的,而不是属于每个产品的。
静态数据成员具有静态生存期。由于静态数据成员不属于任何一个对象,因此可以通过类名对它进行访问,一般的用法是“类名::标识符”。在类的定义中仅仅对静态数据成员进行引用性声明,必须在命名空间作用域的某个地方使用类名限定定义性声明,这时也可以进行初始化。
提示 之所以类的静态数据成员需要在类定义之外再加以定义,是因为需要以这种方式专门为它们分配空间。非静态数据成员无须以此方式定义,因为它们的空间是与它们所属对象的空间同时分配的。
例5-4 具有静态数据成员的 Point类。
源程序:
#include <iostream>
using namespace std;
class Point ( //Point类定义
public: //外部接口
Point(int x = 0,int y = 0) :x(x) y(y){ //构造函数
//在构造函数中对 count 累加,所有对象共同维护同一个 count
count++;
Point(Point &p){ //复制构造函数
x = p.x;
y = p.y;
count++;
}
~Point(){
count--;
}
int getX(){
return x;
}
int getY(){
return y;
}
void showCount(){ //输出静态数据成员
cout <<" Object count = " << count << endl;
}
private: //私有数据成员
int x, y;
static int count; //静态数据成员声明,用于记录点的个数
};
int Point::count=0; //静态数据成员定义和初始化,使用类名限定
int main(){ //主函数
Point a(4,5); //定义对象 a,其构造函数会使 count增 1
cout << "Point A:" << a.getX() << "," << a.getY();
a.showCount(); //输出对象个数
Point b(a); //定义对象b,其构造函数会使 count增1
cout << "Point B:" << b.getX() << "," << b.getY();
b.showCount(); //输出对象个数
return0;
}
程序的运行结果如下:
Point A:4,5 Object count=1
Point B:4,5 Object count=2
百期丽用微态司
上面的例子中,类 Point的数据成员 count 被声明为静态,用来给 Point 类的对象计数,每定义一个新对象,count 的值就相应加 1。静态数据成员 count 的定义和初始化在类外进行,初始化时引用的方式也很值得注意,首先应该注意的是要利用类名来引用,其次,虽然这个静态数据成员是私有类型,在这里却可以直接初始化。除了这种特殊场合,在其他地方,例如主函数中就不允许直接访问了。count 的值是在类的构造函数中计算的,a 对象生成时,调用有默认参数的构造函数, b对象生成时,调用复制构造函数,两次调用构造函数访问的均是同一个静态成员 count。通过对象 a和对象b分别调用showCount 函数,输出的也是同一个 count 在不同时刻的数值。这样,就实现了 a,b两个对象之间的数据共享。
提示 在对类的静态私有数据成员初始化的同时,还可以引用类的其他私有成员例如,如果一个类T存在类型为T的静态私有对象,那么可以引用该类的私有构造函数将其初始化。
静态函数成员
在上面的源程序例子中,函数 showCount 是专门用来输出静态成员 count 的。要输出 count 只能通过 Point类的某个对象来调用函数 showCount。在所有对象声明之前 count 的值是初始值 0。如何输出这个初始值呢?显然由于尚未声明任何对象,无法通过对象来调用showCount。由于 count 是为整个类所共有的,不属于任何对象,因此我们自然会希望对count 的访问也不要通过对象。现在尝试将例 5-4 中的主函数改写如下:
int main()(
Point::showCount(); //直接通过类名调用函数,输出对象个数的初始值Point:: showCount())
Point a(4,5);
cout << "Point A," << a.getX() << "," << a.getY();
a.showCount();
Point b(a);
cout << "Point B," << b.getX() << "," << b.getY();
b.showCount();
}
但是不幸得很,编译时出错了,对普通函数成员的调用必须通过对象名。
尽管如此 C++中还是可以有办法实现我们上述期望的,这就是使用静态成员函数。所谓静态成员函数就是使用 static 关键字声明的函数成员同静态数据成员一样成员函数也属于整个类,由同一个类的所有对象共同拥有,为这些对象所共享。静态成员函数可以通过类名或对象名来调用,而非静态成员函数只能通过对象名调用。
习惯 虽然静态成员函数可以通过类名和对象名两种方式调用,但一般习惯干通类名调用。因为即使通过对象名调用,起作用的也只是对象的类型信息,与所使用的具发对象毫无关系。
class A{
public:
static void f(A a);
private:
int x;
};
void A::f(A a)
{
cout << x; //对x的引用是错误的
cout << a.x; //正确
}
可以看到,通过静态函数成员访问非静态成员是相当麻烦的,一般情况下,它主要用来访问同一个类中的静态数据成员,维护对象之间共享的数据。
提示 之所以在静态成员函数中访问类的非静态成员需要指明对象,是因为对静态成员函数的调用是没有目的对象的,因此不能像非静态成员函数那样,隐含地通过目的对象访问类的非静态成员。
5-5 具有静态数据和函数成员的 Point 类。
#include <iostream>
using namespace std;
class Point { //Point类定义
public: //外部接口
Point(int x=0,int y=0) :x(x),y(y){ //构造函数
//在构造函数中对 count 累加,所有对象共同维护同一个 count141 。
count++;
Point(Point &p){ //复制构造函数
x = p.x;
y = p.y;
count++;
}
~Point(){
count--;
}
int getX(){
return x;
}
int getY(){
return y;
}
static void showCount(){ //静态函数成员
cout << " Object count=" << count << endl;
}
private: //私有数据成员
int x,y;
static int count; //静态数据成员声明,用于记录点的个数
};
int Point::count=0; //静态数据成员定义和初始化,使用类名限定
int main(){ //主函数
Point a(4,5); //定义对象 a,其构造函数会使 count 增1
cout<< "Point A:" << a.getX() << "," << a.getY();
Point::showCount(); //输出对象个数
Point b(a); //定义对象 b,其构造函数会使 count 增 1
cout<< "Point B:" << b.getX() << "," << b.getY();
Point::showCount() ; //输出对象个数
return 0;
}
与例5-4 相比,这里只是在类的定义中,将 showCount 改写为静态成员函数。于是在主函数中既可以使用类名也可以使用对象名来调用 showCount。
这个程序的运行输出结果与例 5-4 的结果完全相同。相比而言,采用静态函数成员的好处是可以不依赖于任何对象,直接访问静态数据。
以上内容摘自:《C++程序设计基础教程》郑莉 董渊 编著