http://ieee802.blog.hexun.com/13942394_d.html
static关键字除了在函数体和模块中使用以外,还可用于类中,具体应用见上文。其中用于修饰类静态成员时,则不论该类对象有多少,所有对象公用一份存在于公用内存中的静态成员(注意:这个内存区域既不是在堆中,也非栈中,而是在编译过程中由编译器创建)。在使用时需要注意的是类静态数据成员除了在类中形式说明之外,必须在使用前在对象说明之外作定义性说明,因为对象说明无法为类静态数据成员分配内存。对于类静态函数成员而言,则该函数只能涉及其它类静态成员时,否则该函数不具有任何确切含义,因为不同对象非静态成员取值可能不同。
下面我们看一段代码:
class LogicFunctionList {
private:
struct LogicFunctionElm {
LogicFunction *m_function;
struct LogicFunctionElm *m_next;
};
static struct LogicFunctionElm *head;
public:
static void insert(LogicFunction *f);
static void remove(LogicFunction *f);
static LogicFunction *find(char *name);
};
LogicFunctionList::LogicFunctionElm *LogicFunctionList::head = NULL;
void LogicFunctionList::insert(LogicFunction *f)
{
struct LogicFunctionElm *oldhead=head;
head = new struct LogicFunctionElm;
head->m_function = f;
head->m_next = oldhead;
}
void LogicFunctionList::remove(LogicFunction *f)
{
for (LogicFunctionElm **elm=&head; *elm; elm=&((*elm)->m_next))
{
if ( (*elm)->m_function == f)
{
LogicFunctionElm *next = (*elm)->m_next;
delete (*elm);
(*elm) = next;
break;
}
}
}
LogicFunction *LogicFunctionList::find(char *name)
{
for (LogicFunctionElm *elm=head; elm; elm=elm->m_next)
{
if (0 == strcmp(name, elm->m_function->m_name) )
{
return elm->m_function;
}
}
return 0;
}
类LogicFunctionList定义了一个静态成员变量和3个静态函数变量,主要完成对不同逻辑功能对象的链式管理,我们可以注意到静态数据成员head在外部作了定义性说明。
那么对于这段C++代码,我们这里主要关注它的那几个方面呐?
首先,由于这个类的成员全部采用了static关键字,它不再具有可创建多个对象实体的功能,丛这点来说,它的实际角色是一个全局变量的作用,由此可见,static关键字的使用为这段代码的应用限定了一个使用场合:作为系统中全局唯一的逻辑功能对象的链式管理器使用(使用C语言中的struct也可以实现相同功能)。需要注意的是使用全局变量会整加模块的耦合性,降低代码的通用性,不易修改和维护;而通过类静态成员进行封装,可以克服这些缺点,同时可以为静态成员添加上相应的访问控制属性。
其次,丛类的继承和派生的角度看,其派生类和基类共享类中静态类函数成员的同一份公共拷贝,但类静态数据成员head由于是private类型,则不能被派生类继承(既基类中的静态成员必须是public或protected成员,才能被其派生类继承为派生类的静态成员),这里的继承是指派生类是否对基类私有成员可见。如果不考虑前一条所说的对特定应用场合的设计要求的话,我们认为这个类的设计是存在一定的缺陷的,它只在struct结构功能的基础上实现了一定程度的信息隐藏功能,但失去了良好的可扩展派生性,因此我们把head改为protected类型可能会更好一些。
再次,丛对于该类的使用上看,由于该类成员均为静态成员,其由编译器创建,不依赖于具体对象的创建和销毁,因此在实际上使用中可以无需创建类对象,使用时可以通过LogicFunctionList::insert()方式来调用即可工作。
我们可以通过这段代码来考虑静态成员函数一个应用技巧:
#include <iostream>
using namespace std;
class A{
public:
A(){ count++; } //当产生一个实例的时候计数器加一
~A(){ count--; } //当销毁一个实例的时候计数器减一
int GetInstanceCount(){ return count; }
private:
static int count;
};
int A::count = 0;
当创建该类对象实体时,由于count为所有对象实体的唯一一份拷贝,从而可以记录当前创建的所有对象数量。这一技巧可以运用到很多方面,如对象实体的互斥关系,引用计数等相关方面。
在C++设计中存在一些情况要求一个类只能有一个实例,并提供一个全局访问点,这个要求实际上就是C语言中的全局变量的作能,这点在嵌入式设计中出现的尤为频繁,如实际物理设备,特定的物理端口等,而采用全局方式通常会对程序的命名空间造成污染,而且从安全性考虑,同时全局变量方式只能保证程序可以访问一个对象,却不能避免实例化多个对象(这将导致内存丢失问题),此时在类的设计中采用static关键字可以避免这种情况。
class CSingleObj
{
public:
static CSingleObj* Instance();
virtual ~ CSingleObj ();
protected:
CSingleObj ();
private:
static CSingleObj * m_instance;
};
该类的简单实现方式如下:
CSingleObj* CSingleObj::m_instance = 0;
CSingleObj* CSingleObj::Instance()
{
if (m_instance == 0)
{
m_instance = new CSingleObj;
}
return m_instance;
}
由于这里类构造函数为保护类型,因此当其它部分试图直接实例该类时会给出编译错误的信息,从而保证了单个实例的创建。此外考虑到实例对象有可能是一个复杂设备,因此单纯依靠声明为static类型,并通过自动初始化来创建实例并不能够有效的初始化该设备,而采用这种方式可以在程序运行中动态计算其初始化条件,通过修改Instance()成员函数中的代码,我们也可以支持创建不同子类型的单个对象。
这里注意到使用了静态成员函数,对于设计类的多线程操作时, 由于POSIX库下的线程函数pthread_create()要求是全局的, 普通成员函数无法直接做为线程函数, 可以考虑用Static成员函数做线程函数。
通过对static关键字的不同使用方式,我们也可以将上面的类设计出另一种实现方式:
CSingleObj* CSingleObj:: Instance()
{
static CSingleObj obj;
m_instance = &obj;
return m_instance;
}
这种实现方式也同样实现了保证单个实例的创建,然而弊端很多,首先,它是依赖static变量自动初始化来创建实例,对于需要依赖其余条件创建的实例无法适用;其次,由于采用static关键字,导致无论是否使用的所有单个对象都要被创建。
这里举出该例只是为了说明static关键字的一个值得注意的用法:局部静态变量。
static局部静态变量是分配在静态存储区, 在程序整个运行期间都不释放,其作用域同局部变量一致,但生存期为全局性,初始化工作在所处模块在初次运行时进行,如不赋值则初始化为0, 且只操作一次,在第二次调用进入时, 能保持第一次调用退出时的值。
需要注意的是这种static的这种特性导致如每次调用时如果不赋值的话将破坏了程序的可重复性, 造成不同时刻至运行的结果可能不同,此外static的全局唯一性也将导致每次调用时, 该变量都指向同一块内存, 从而造成程序的不可重入性,在多线程程序设计或递归程序设计中, 尤其需要特别注意这个问题,对于多线程和递归程序设计,由于使用的是同一内存区,可能会导致程序运行结果错误(对于普通局部变量由于存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样,依据这一特点可以保证可重入程序的设计安全)。
相对于静态局部变量而言,在C语言设计中如果声明为静态全局性质则static的含义不是指存储方式,而是指对变量或函数的作用域仅局限于本文件(所以又称内部函数),需要注意的是对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。