类的静态数据成员是类的数据成员的一种特例,采用static关键字来声明。对于类的普通数据成员,每一个类的对象都拥有一个拷贝,就是说每个对象的同名数据成员可以分别存储不同的数值,这也是保证对象拥有自身区别于其它对象的特征的需要,但是静态数据成员,每个类只要一个拷贝,由所有该类的对象共同维护和使用,这个共同维护、使用也就实现了同一类的不同对象之间的数据共享。静态数据成员可以被非静态成员函数调用,(外部)引用静态数据成员时,使用<类名>::<静态成员名>这种格式。
使用static关键字声明的函数成员是静态的,静态函数成员属于整个类,同一个类的所有对象共同维护,为这些对象所共享。静态函数成员具有以下两个方面的好处,一是由于静态成员函数只能直接访问同一个类的静态数据成员,可以保证不会对该类的其余数据成员造成负面影响;二是同一个类只维护一个静态函数成员的拷贝,节约了系统的开销,提高程序的运行效率。
#include <iostream>
using namespace std;
class CSell
{
public:
static void sell(int n, double price);
static double getSum();
static double getAvg();
private:
static double sum;
static double avg;
static int nCounts;
};
double CSell::sum = 0; //销货总数
double CSell::avg = 0; //销货平均数
int CSell::nCounts = 0; //销货件数
void CSell::sell(int n, double price)
{
nCounts += n;
sum += n * price;
avg = sum / nCounts;
}
double CSell::getAvg()
{
return avg;
}
double CSell::getSum()
{
return sum;
}
int main()
{
CSell::sell(5, 23.5);
CSell::sell(12, 24.56);
CSell::sell(100, 21.5);
cout<<CSell::getSum()<<endl;
cout<<CSell::getAvg()<<endl;
return 0;
}
1、静态数据成员在定义或说明时前面加关键字static。
2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:
<数据类型><类名>::<静态数据成员名>=<值>
这表明:
(1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。
(2) 初始化时不加该成员的访问权限控制符private,public等。
(3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。
3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。
4、引用静态数据成员时,采用如下格式:
<类名>::<静态成员名>
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
静态成员函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。如果静态成员函数中要引用非静态成员时,可通过对象来引用。
下面看一个例子:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
}
};
void main( void )
{
Point pt;
pt.init();
pt.output();
}
这样编译是不会有任何错误的。
下面这样看
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
}
};
void main( void )
{
Point::output();
}
这样编译会处错,错误信息:illegal call of non-static member function,为什么?
因为在没有实例化一个类的具体对象时,类是没有被分配内存空间的。
好的再看看下面的例子:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
}
};
void main( void )
{
Point::init();
}
这时编译就不会有错误,因为在类的定义时,它静态数据和成员函数就有了它的内存区,它不属于类的任何一个具体对象。
好的再看看下面的例子:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
x = 0;
y = 0;
}
private:
int x;
int y;
};
void main( void )
{
Point::init();
}
编译出错:
illegal reference to data member 'Point::x' in a static member function
illegal reference to data member 'Point::y' in a static member function
在一个静态成员函数里错误的引用了数据成员,
还是那个问题,静态成员(函数),不属于任何一个具体的对象,那么在类的具体对象声明之前就已经有了内存区,
而现在非静态数据成员还没有分配内存空间,那么这里调用就错误了,就好像没有声明一个变量却提前使用它一样。
也就是说在静态成员函数中不能引用非静态的成员变量。
好的再看看下面的例子:
#include <iostream.h>
class Point
{
public:
void output()
{
x = 0;
y = 0;
init();
}
static void init()
{
}
private:
int x;
int y;
};
void main( void )
{
Point::init();
}
好的,这样就不会有任何错误。这最终还是一个内存模型的问题,
任何变量在内存中有了自己的空间后,在其他地方才能被调用,否则就会出错。
好的再看看下面的例子:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
x = 0;
y = 0;
}
private:
static int x;
static int y;
};
void main( void )
{
Point::init();
}
编译:
Linking...
test.obj : error LNK2001: unresolved external symbol "private: static int Point::y"
test.obj : error LNK2001: unresolved external symbol "private: static int Point::x"
Debug/Test.exe : fatal error LNK1120: 2 unresolved externals
执行 link.exe 时出错.
可以看到编译没有错误,连接错误,这又是为什么呢?
这是因为静态的成员变量要进行初始化,可以这样:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
x = 0;
y = 0;
}
private:
static int x;
static int y;
};
int Point::x = 0;
int Point::y = 0;
void main( void )
{
Point::init();
}
在静态成员数据变量初始化之后就不会出现编译错误了。
再看看下面的代码:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
x = 0;
y = 0;
}
private:
static int x;
static int y;
};
void main( void )
{
}
编译没有错误,为什么?
即使他们没有初始化,因为我们没有访问x,y,所以编译不会出错。
C++会区分两种类型的成员函数:静态成员函数和非静态成员函数。这两者之间的一个重大区别是,静态成员函数不接受隐含的this自变量。所以,它就无法访问自己类的非静态成员。
在某些条件下,比如说在使用诸如pthread(它不支持类)此类的多线程库时,就必须使用静态的成员函数,因为其地址同C语言函数的地址兼容。这种铜限制就迫使程序员要利用各种解决办法才能够从静态成员函数访问到非静态数据成员。
第一个解决办法是声明类的所有数据成员都是静态的。运用这种方式的话,静态的成员函数就能够直接地访问它们,例如:
class Singleton
{
public:
static Singleton * instance();
private:
Singleton * p;
static Lock lock;
};
Singleton * Singleton::instance()
{
lock.getlock(); // fine, lock is static
if (!p)
p=new Singleton;
lock.unlock();
return p;
}
这种解决方法不适用于需要使用非静态数据成员的类。
访问非静态数据成员
将参照传递给需要考量的对象能够让静态的成员函数访问到对象的非静态数据:
class A
{
public:
static void func(A & obj);
intgetval() const; //non-static member function
private:
intval;
};
静态成员函数func()会使用参照obj来访问非静态成员val。
voidA::func(A & obj)
{
int n = obj.getval();
}
将一个参照或者指针作为静态成员函数的自变量传递,就是在模仿自动传递非静态成员函数里this自变量这一行为。
很喜欢API函数的那种调用方法,不论在哪个类中只要用“::API函数”就可以调用了。合理利用静态类型(static)可以实现与此相似的全局变量和全局函数。
静态变量和静态函数有如下性质:
若在一个类中用关键字static声明数据成员,则这个数据成员就只存在一个拷贝,无论该类创建了多少个实例,它始终只存在一个,即使该类的实例一个也没创建,它也存在。
若在一个类中用关键字static声明函数,该函数可以用“
类名::函数名
”方式访问,无需引用该类的实例,甚至这个类的实例可以不存在。
利用这个性质实现的全局变量和函数使用起来很方便。
值得注意的是,全局变量和全局函数最好集中封装,不要在文档、视图等类内部定义,这样用起来才有全局的感觉。
例:
1、添加一个没有基类的新类,设类名起为CPublic,姑且称之为公用类
单击“Insert”菜单下的“New Class”命令,选择“Class type”为“Generic Class”,在“Name”栏中填入类名“CPublic”,单击“OK”,则新类建立完毕。
2、包含公用类的头文件,使各个类都能访问它
CPublic的头文件应包含在应用程序类的头文件中,这样在其它类中引用CPublic类时就不需要再包含了。
Test.h:(应用程序类头文件)
#include "Public.h" //包含公用类头文件
class CTestApp : public CWinApp
{
…………
};
3、在公用类中定义全局变量和全局函数,均使用static修饰,静态变量还必须在类外定义和初始化
Public.h:(公用类头文件)
class CPublic
{
public:
CPublic();
virtual ~CPublic();
public:
static int x; //全局变量
static int time; //全局变量
static int f(int y); //全局函数
…………
}
在公用类中对静态变量进行初始化和定义函数体:
Public.cpp:(公用类程序文件)
int CPublic::x = 0; //初始化全局变量
int CPublic::time; //定义全局变量
CPublic::CPublic()
{
}
CPublic::~CPublic()
{
}
int CPublic::f(int y) //全局函数,这里不要再加static
{
y++;
return y;
}
4、全局量的使用
使用变量:
CPublic::变量名
使用函数:
CPublic::函数()
如在视图的某函数中访问变量x和函数f():
void CTestView::xyz()
{
CPublic::x = 0; //访问变量x
CPublic::time = CPublic::f(1); //访问函数f()
…………
}
在其它类中访问x、time和f()的方法与此相同。
5、几点注意:
① 由于静态量可独立于类存在,
不需要生成CPublic类的实例
。
②
静态数据成员的定义和初始化必须在类外进行
,如例中x的初始化;变量time虽然没有初始化,但也必须在类外进行定义。由于没有生成CPublic类的实例,所以它的构造函数和析构函数都不会被执行,在里面做什么工作都没有什么意义。
③ 如果静态函数需要访问CPublic类内的变量,这些变量也必须为静态的。因为非静态量在不生成实例时都不会存在。
如:
class CPublic
{
public:
int x; //内部变量
static int f(int y) //全局函数
{
x++;
return x;
};
…………
};
这里x虽为类内成员,但如果不生成CPublic类的实例,就会出现函数f()存在,而变量x不存在的问题。
总之,用没有实例的类管理全局量是一个不错的选择,它具有集中管理,使用方便的好处。当然,除非特别必要,全局量还是少用为好,一个好的编程者决不会随意滥用全局量的,一个封装做得不好的程序,在修改维护时会让你吃足苦头。
静态数据成员通常应该通过非内联函数来访问。
问题:
书上有一段话:如果要通过非静态函数来访问静态数据成员,应该使用非内联函数。同时我看到的程序如下:
#include <iostream>
using namespace std;
class Point
{
public:
Point(int xx=0,int yy=0) {X=xx;Y=yy;countP++;} Point(Point&p);
~Point(){countP--;}
int GetX(){return X;}
int GetY(){return Y;}
void GetC(){cout<<"Object id="<<countP<<endl;}
private:
int X,Y;
static int countP;
};
Point::Point(Point&p)
{
X=p.X;
Y=p.Y;
countP++;
}
int Point::countP=0;
int main()
{
Point A(4,5);
cout<<"Point A,"<<A.GetX()<<","<<A.GetY();
A.GetC();
Point B(A);
cout<<"Point B,"<<B.GetX()<<","<<B.GetY();
B.GetC();
}
void GetC(){cout<<"Object id="<<countP<<endl;} 中函数体直接放在类体内,是一种内联函数的隐式声明,这不是和书上“该使用非内联函数”的意思矛盾了?
答:
书上原话:因为编译器确保在调用任何一个非内联函数之前,会初始化静态数据成员,但是因为调内联函数的时候,可能没有初始化静态数据成员,所以这样操作(内联函数中调静态数据成员)是不安全的。
这个Point::countP使用全局初始化的。
在编译时候是在main运行之前就已经初始化了的。
所以即使写在内联函数里面也没关系。
另外,如果没记错的话,书中写的应该是“最好通过非内联函数访问”
之所以这么做的原因是为了避免内联成员在为初始化的时候就被使用了。
但是你看这段程序,全局初始化发生在main之前,
数据直接存储在EXE的数据段了。
所以不会发生初始化之前的访问的。
建议你用断点看看程序运行的顺序就知道了。
但是这是针对这个程序合理,如果程序大了的话,其他编译单元调用静态但远远可能会发生对为初始化变量的访问。
所以最好还是不要写在内联函数中。
静态成员函数可以直接访问静态数据和函数成员。而访问非静态数据成员必须通过参数传递方式得到对象名,然后通过对象名来访问。
class A
{
public:
static void f (A a);
private:
int x;
};
void A::f(A a)
{
cout<<x; //错误
cout<<a.x;//正确
}