鬼羽箭:关键字static总结
来源:百度文库 编辑:九乡新闻网 时间:2024/04/26 01:32:36
static关键字如果不被引入C++语言,那就违反了C++设计中对低级语言设计支持规则中的”没有无故与C语言不兼容规则”,原因很简单,C语言中存在static并发发挥着它良好的作用,所以C++同样引入static应该是理所当然的,而实际C++的做法是不仅引入static,而且对它在面向对象程序设计中进行扩充(导入静态数据成员和静态函数成员的概念),这就使static的概念得到了扩展,对于其中较新的静态成员变量(对象)和静态函数成员则应该重点更理解,下面分别从static的作用,在C++中的分类,以及一些比较典型的应用三个方面进行总结一下
一:静态(static)的作用
首先,一个很明显的作用就是解决了全局命名空间的问题,大家都知道全局变量是一处定义多处修改的,这当然是它的优势但,也确实解决了不少问题,但这对面向对象程序思想是相抵触的,它破坏的良好的封装性,污染了程序的名称空间,而对于这些缺点的,正是static能够解决的问题,我们可以在函数,结构,类中定义静态对象,当将变量定义为static时,编译器就会将变量的存储在程序的静态存储区(数据段)域而非普通的函数分配的栈空间上。
其次,static会将其修修饰的变量对象的可见性限制在本编译单元内(也就是后所以的具有文件作用域),使它成为一个内部连接。这与普通全局变量加入”extern”可以在多个文件中使用是相对应的。
二:静态(static)的分类
目前在C++语言有五种静态对象的类别,分别为静态局部变量(对象),静态全局变量(对象),静态函数(静态全局函数),静态成员变量(对象),静态成员函数。
静态局部变量,静态全局变量,以及静态函数的存在是对C语言兼容的结果,静态员成员变量是在C++引入类之后相对应存在的概念,静态成员函数则是为保持类的封装的前提下对静态成员变量进行更好方法而引入的一相概念,所以说静态成员函数就目前来讲是所以静态对象(包括C++所有的静态变量和静态成员函数)中最晚一个引入C++语言的。
1. 静态局部变量(对象)
静态局部变量(对象)通也是指在函数中定义的静态变量(对象),当函数第一次被调用时,程序会为它在程序的数据段分配存储空间,并对它进行初始,当函数调用完成退出时, 这个变量(对象)会保存此次函数调用后的状态, 但不会被其它函数使用, 当再次调用该函数时, 并不为它分配空间和初始化,而是直接命使用上次调用的结果。看看下面简单的程序:
////////////////////////////////////////////////////////////////////////////////////////
#include
#include
using namespace std;
void foo(bool i)
{
static int a=0;
int c=0;
if(i){
static int b=0;
++b;
cout <<"b="<< b <
}
++a;
++c;
cout << "a="<< a <
cout << "c="<< c <
}
int main()
{
foo(false); //变量,a,c初始化化并加1,所以a=1,c=1,此是b并没有初始化
//和分配空间,函数调用完全c的空间被回收,a由于是静态变量,
//所以并没有回收.
cout << "-----------------" <
foo(true); //变量a在上次调用的状态上加1,所以a=2,c重新初始分配空间并加1,
//所以c=1,变量b初始化并分配空间后加1,所以b=1.
cout << "-----------------" <
foo(false);//变量a在上次调用的状态上加1,所以a=3,c重新初始分配空间并加1,
//所以c=1,变量b保自己的状态,所以b=1.
cout << "-----------------" <
foo(true);//变量a在上次调用的状态上加1,所以a=4,c重新初始分配空间并加1,
//所以c=1,变量b在上次调用的状态上加1,所以b=2.
system("PAUSE");
return 0;
}
//////////////////////////////////////////////////////////////////
从程序的运行结可以看出局部静态变量只在第一次遇到是进空间的分配和构造,函数调用完成并不立即回收,而等程序运行的结束.正因为它只存在一共享空间地址(对象多线程来讲),所以在多线程序程序中为保正确的读取而需要对其进行锁定。
2. 静态全局变量(对象)
静态全局变量(对象)是指全局变量的基础上加上static修饰符,它同时具有文件作用域和静态生存期两种特性。具体来讲就是指只在定义它的源文件中可见而在其它源文件中不可见的变量(文件作用域)。它与全局变量的区别是: 全局变量可以再说明为外部变量(extern), 也就是指跨文件作用域,被其它源文件使用, 而静态全局变量却不能再被说明为外部的, 即只能被所在的源文件使用。
3. 静态函数(静态全局函数)
静态函数一般来说相对较少用到普通函数若未加static修饰,具有跨文件作用域,若加static修饰,与静态全局变量相类似也具有文件作用域,一般函数本身的一个目标就是实现共用(不管是函数定义文件还是被作为include对象的文件),若在函数前加上static主使得函数只能在本文件中使用,其它文件不能使用,我们当然不想这样做啦J,所以,在一般的代码中,很少函数静态函数(静态全局函数)不存在。
4. 静态成员变量(对象)
当在程序中这义静态变量(对象)时,那么对于这个类的所以对象都将只分配一个共享的存储单,简单的说,静态成员变量属于类范围,而不是具体的某个对象,相当于类范围内的全局变量(对象),当然也可以说是属于自身类型的所有对象,它的值是可以更新的。只要对静态成员变的值更新一次,所有对象存取者将是更新后的相同的值(多线程应该自行控制),这样可以提高时间效率。
静态成员变量(对象)以类为名称空间,为了更好的实现信息隐藏,可以将它设置为private,然后通过函数进行存取(后面应用中将会详细讲到),程序对类静态数据成员的访问会被转换为类内部的惟一extern实体的直接操作(至于如何被转换,不同的编译器将会有不同的算法,要详细了解请参阅《Inside C++ Object Model》),正因为这样,所以在类的内部可以直接定义自身类型的static成员,而不会造成类定义的无限递归,而非静态的自身类型则不行,只能是自身类型的指针或引用,其实这跟类定义结束是到大括号后的分号为止是有关的,在此之前将无法确定类的大小,而自身类型的指针或引用的大小则是确定(一个指针的空间)。如下面代码:
class A{
//……
private:
//……
static A a1;//ok,因为a1为静态成员对象。
A a2;//error此时不能确定A类型的大小。
A* a3;//ok 此时能确定A*类型的大小,一个指抽A类型的指针的大小
A& a4;//ok 此时能确定A&类型的大小,一个指抽A类型的指针的大小
};
与全局变量一样,静态成员变量也只能提供一次这义,类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以它的实初始化将不能被放在头文件中,而应该放入相就是类成员函数的实现文件中,也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。对于const类型的静态有序类型可以直接初始化,但其定义还要在类定义之外:
//头文件
class print{
private:
static const int count = 10;//直接初始化
string info[count];
};
const int print::count;//可以在头文件,也可以在实现文件,因为不会为常分配空间
5. 静态成员函数
静态成员函数的引入实际是为了更好存取静态成员变量(对象),静态成员变量(对象)并不局限于某个具体的对象,而普通成员函数则不为具体的某个对象服务的,那么当用普通成员函数进行静态成员变量(对象)存取就会造成将静态成员变量约束为具体的某个对象(原因是因为普通成员函数约束于具体的某个对象),使用静态成员函数将会很好的解决这些问题。所以静态成员函数具有静态成员变量(对象)相类似的特点,不管何种形式(一般包括具体对象,具体对象的指针或引用,::运算符,静态成员函数指针)的静态员函数的调用将会被转换成非成员函数(普通函数)的调用。
静态成员函数最大的特点就是没有this指针,从而导至下面一些特点:
1.不能直接存取类中的非态成员,只能直接存取静态成员变量(对象)。
从语言层次来说普通成员函数能够存取非静态成员靠是this指针,静态成员函数没有this指针,所以无法实现。
2.不能为const,volatile或virtual。
静态成员函数本来就不能存取非静态成员,所以const对它来说不有意义。virtual说明在一个类继承体系中不同类可能多个不同的实现实体,而静态成员函数则只有一个,所以static对virtual并不适合(个人理解,仅供参考,也有可能是错误码的噢J)
3.不需要约束于类对象才能被调用。
静态成员函数本身就是为了解决此问题而被提出的J.
4.可以作为回调函数(CallBack)。
静态成员函数摆脱的this指针的约束而成为普通的函数。
三:静态(static)几个典型的应用:
1. 代替menu的作用(menu hank)
大家都知道如果程序要在,要在类定义的内部直接初始化变量并使用对于以夜的编译器只能使用menu来达到这种效果,代码像这样:
class print
{
menu{count = 10 };//借助枚举来得到一个初始值
string info[count];
};
用MFC编写过程序代码的都应该知道这个功能,它就是通过menu来接初始化类中的IDD然后进行使用,看一面一段MFC产生的代码
///////////////////////////////////////////////////////////////////////
class CACETESTDlg : public CDialog
{
public:
//……其它成员函数和构造函数的定义
// 对话框数据
enum { IDD = IDD_ACETEST_DIALOG }; //使用menu直接初始化IDD,IDD_ACETEST_DIALOG是对话框的ID值
//……其他成员函数的定义。
};
//实现文件
//……
CACETESTDlg::CACETESTDlg(CWnd* pParent /*=NULL*/)
: CDialog(CACETESTDlg::IDD, pParent)//注:这里直接使用IDD初始化基类。
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
//……
////////////////////////////////////////////////////////////////////////////
上面这种直接初始并使用的功能是借助menu来实现的。但这有个不好之处就是使menu在这里丧失了本来的作用(menu本身的功能没有得到体现)但有了static之后可以在一个类中使用static常量来达到同样的效果,因为一个类的静态常量被当作一个编译时的常量,而又因为静态具有文件作用域的内部连接,所以不会产生重复定义的冲突。其代码示例在介绍静态成员变量(对象)时已经给出。
不知道大家有不有注意,menu只能使用整型,但类内部静态常量可以是任何类型,甚至抱用户自定义类型。
2. 单件模式
相大家都知道单件模式吧,如果不熟悉可以参考相产模式的书籍,单件模式保证一个类仅有一个实例存在,具体就是通过静态成员变量和静态成员函数以及类的访问层次相结合而实现的,具体代码如下:
//////////////////////////////////////////////////////////////////////////
class Singleton{
public:
static Singleton* Instance();//注:静态成员函数返回类的实例
virtual ~Singleton(){};
protected:
Singleton(){};
private:
static Singleton* _instance;//注:静态成员变量指向类的实例
};
//实现文件:
Singleton* Singleton::_instance = 0;//静态成员变量的初始化
Singleton* Singleton::Instance() //返回指向类实例的静态指针
{
if(_instance == 0)
_instance = new Singleton;
return _instance;
}
/////////////////////////////////////////////////////////////////////
静态成员函数加静态成员变量便可实现单件模式,其中静态成员函数控制返回类的实例,因为静态成员函数不能为虚函数,因此子类就不能多态的对它进行重定义,这就保证了只能过此接口对类进行使用。用静态成员变量指向那个唯一被实例化的对象,当然最实始被初始化为0,然后使用惰性初始化时行实例的建立,
如果将这些换成全局静态对象和全局静态函数,相信会有不少问题存在J.
3. 消除全局数据
大家都知道,大家都知道,引入全局数据确实在适当的时候能解决一些棘手的问题,但全局数据是跨文件作用域,使得变量的正确初始在有些情况下非常难控制(后面第4点将会讲到),全局变量一旦建立,要消除它是相当难,随着而来便是”名字空间污染”问题,不仅如此,全局变量将变量(对象)和算法代码绑定在一起,使得复用又相当困难。
对于全局变量所存在的这些问题,可以使用封装加静态访问的方法使问题得到优化
首先将全局变量放入一个类或结构中
然后将它们私有化并添加静态成员函数访问接口
例如有下面的全局变量
bool g_flag;
int g_size;
const char* in_modulename;
对于这些全局变量,运用上面的方法介绍的方法进行改进,可以产生下面的代码:
class Global
{
private:
static bool s_flag_;
static int s_size_;
static const char* s_in_modulename_;
private:
Global();//私有的构造函数,使类不能实例化
//....其它相关的函数
public:
//存入相关值
static void setFlag(bool flag){ s_flag_ = flag; }
static void setSize(int size) { s_size_ = size; }
static void setInmodulename(const char* in_modulename){ s_in_modulename_ = in_modulename; }
//取得相关值
static int getFlag() { return s_flag_; }
static int getSize() { return s_size_; }
static const char* getInmodulename() { return s_in_modulename_; }
};
相信这种重构方法通过引入类的封装和静态成员函数的访问来解决全局的问题应该是一个比较好的方法。
4. 解决初始相互依赖问题
如果你让对象A必须在对象B之前初始化,同时又让A的初始化依赖于B已经被初始化,同是A,B又在不同的编译单元,这是我们就无法控制正确的初始化顺序,解决办法还是有的(注,以下直入引用《think in C++》的一段例子,由于是总结,拷贝一段没问题吧J),
这时我们可用由Jerry Schwarz在创建i o s t r e a m库(因为c i n , c o u t和c e r r的定义是在不同的文件中)时提供的一种技术。这一技术要求在库头文件中加上一个额外的类。这个类负责库中静态对象的动态初始化。下面是一个简单的例子:
//DEPEND.H – Static initialization technique
#ifndef DEPEND_H_
#define DEPEND_H_
#include
extern int x;//Delarations,not definitions
extern int y;
class initializer{
static int init_count;
public:
initializer(){
cout <<"initializer()"<
if(init_count++ == 0)
{
cout <<"performing initialization"<
x = 100;
y = 100;
}
}
~initializer(){
cout<< "~initializer()"<
if(--init_count == 0){
cout << "performing cleanup" <
}
}
};
//The following creates one object in each
//file where DEPEND.H is included ,but that
//object is only visible within that file:
static initializer init;
#endif //DEPEND_H_
x、y的声明只是表明这些对象的存在,并没有为它们分配存储空间。然而initializer init 的定义为每个包含此头文件的文件分配那些对象的空间,因为名字是static的(这里控制可见性而不是指定存储类型,因为缺省时是在文件范围内)它只在本编译单元可见,所以连接器不会报告一个多重定义错误。
下面是一个包含x、y和init_count定义的文件:
//:DEPDEFS.CPP--Definition
#include “depend.h”
//Static initialization will force
//all these values to zero:
int x;
int y;
int initializer::init_count;
(当然,一个文件的init静态实例也放在这个文件中)假设库的使用者产生了两个其他的文件:
//:DEPEND.CPP—Static initialization
#include “depend.h”
和
//:DEPEND2.CPP –Static initialization
#include “depend.h”
int main()
{
cout << “inside main()” <
cout << “leaving main()” <
return 0;
}
现在哪个编译单元先初始化都没有关系。当第一次包含DEPEND.H的编译单元被初始化时,init_count为零,这时初始化就已经完成了(这是由于内部类型的全局变量在动态初始化之前都被设置为零)。对其余的编译单元,初始化会跳过去。清除按相反的顺序,且~initializer()可确保它只发生一次。这个例子用内部类型作为全局静态对象,这种方法也可以用于类,但其对象必须用initializer动态初始化。一种方法就是创建一个没有构造函数和析构函数的类,但用不同的名字的成员函数来初始化和清除这个类。当然更常用的做法是在initializer()函数中,设定指向对象的指针,并在堆中动态创建它们。
注:由于是总结,以后自己查阅方便,最后一点是直接取自《think in C++》是的一段,我觉得这个例子举的非常好!所以已直接拿过来了 :)
作者Blog:http://blog.csdn.net/cgsw12345/