在C和C + +中,s t a t i c都有两种基本的含义,并且这两种含义经常是互相有冲突的:
1) 在固定的地址上分配,也就是说对象是在一个特殊的静态数据区上创建的,而不是每次函数调用时在堆栈上产生的。这也是静态存储的概念。
2) 对一个特定的编译单位来说是本地的(就像我们在后面将要看到的,这在C + +中包括类的范围)。这里s t a t i c控制名字的可见性,所以这个名字在这个单元或类之外是不可见的。这也描述了连接的概念,它决定连接器将看到哪些名字。
1. 不属于类的静态成分
1.1. 静态变量
1.1.1. 修饰局部变量
这个变量作用域被局限于函数体内,然而和非static局部变量不同,这个变量的值被保留下来,而不会每次调用这个函数的时候都被初始化。
void fun( void ){
int var = 0;
static int svar = 0;
var ++;
svar++
}
函数fun在第一次被调用后var , svar的值都变成了1,而在第二次被调用后,var依然等于1 ,而 svar的值变成了2。
1.1.2. 静态全局变量
全局变量默认的是静态的(无需用static修饰)
file1.cpp
int gi ;
gi是全局变量,它自动是静态存储的。
1.1.2.1. 用static修饰修饰全局变量
把这个全局变量局限在本文件中(默认情况下全局变量可以被任何文件使用)
file1.cpp
int gi ;
file2.cpp
extern int gi ;
如果用static 来修饰file1.cpp 中的gi ( static int gi ;),则在file2.cpp中就不能使用gi了
1.2. 静态函数
static关键字使得这个函数只能被本文件里的其他函数调用。
2. 属于类的静态成分
2.1. 静态类成员(变量)
态数据成员被当作该类类型的全局对象对于非静态数据成员每个类对象都有自己的拷贝而静态数据成员对每个类类型只有一个拷贝静态数据成员只有一份由该类类型的所有对象共享访问。
静态数据成员在该类定义之外被初始化。与全局对象一样对于静态数据成员在程序中也只能提供一个定义这意味着静态数据成员的初始化不应该被放在头文件中而应该放在含有类的非inline 函数定义的文件中。
静态数据成员可以是各种数据类型(预定议数据类型和用户数据类型)。
静态数据成员可以是const,也可以是非const。
2.1.1. 非const静态类成员
头文件
class CB{
public:
CB(){ mi = 2; }
CB(int i){ mi = i; }
int mi;
};
class CA{
public:
CA(){ mi = 3; }
int mi;
static int smi;
static int smi2;
static CB ob;
static CB ob2;
};
实现文件
int CA::smi; //不能写成int CA::smi();
int CA::smi2( 5 );
CB CA::ob; //不能写成 CB CA::ob() ;
CB CA::ob2(5);
int main(int argc, char* argv[])
{
int i=0;
int j=0;
i = CA::smi;
i = CA::smi2;
j = CA::ob.mi;
j = CA::ob2.mi;
return 0;
}
2.1.2. const静态类成员
头文件
#include <string>
using namespace std;
class CB{
public:
CB(){ mi = 2; }
CB(int i){ mi = i; }
int mi;
};
class CA{
public:
CA(){ mi = 3; }
int mi;
static const CB ob;
static const CB ob2;
};
实现文件
const CB CA::ob;
const CB CA::ob2(5);
int main(int argc, char* argv[])
{
int j=0;
string name;
name = CA::name;
name = CA::name2;
j = CA::ob.mi;
j = CA::ob2.mi;
return 0;
}
2.1.3. 访问静态类成员
2种方式: 直接访问和通过类对象访问.
直接访问
即用被类名限定修饰的名字直接访问它。如上例子CA::name;
类对象访问
CA oa;
oa. name;
或者
CA* poa;
poa-> name;
2.1.4. static数据成员的一个特殊用途
static数据成员的类型可以是其所属类而非static 数据成员只能被声明为该类的对象的指针或引用.
//-----------头文件
class CA{
public:
static CA * instance() { return & soa; }
private:
CA(int i) : mi(i) {};
private:
int mi;
static CA soa;
};
//-----------cpp文件
CA CA:: soa(5);
这是因为static 数据成员并不占据类对象的存储空间。所以 sizeof CA的返回值为4
上面这个例子利用这个特性,很简单的就实现了单例模式.
2.1.5. 注意
1. 局部类不可以有static成员变量(但嵌套类可以有)
//--------局部类
void fun(){
class foo {
static int mi; //错误
}
}
//--------嵌套类
class outer{
class inner {
static int mi; //正确
}
}
2. 当使用static const int 类成员来定义类成员数组时,要看当前编译器是否支持这个特性。
class CA{
public:
int mi;
static const int size;
char sz[ size ];
};
2.2. 静态成员函数
在一个成员函数前加上static关键字就使得这个成员函数成为类的静态成员函数
静态成员函数有几个特别的地方:
不能声明为const 或volatile
没有this 指针因此在静态成员函数中隐式或显式地引用这个指针都将导致编译时刻错误
不能访问非静态数据成员
2.2.1. 访问静态类成员函数
3种方式: 直接访问和通过类对象访问.
class CA{
public:
CA(){ mi = 3; }
int mi;
static int fun();
};
int CA::fun(){
return 9;
}
直接访问
int i = CA:: fun();
类对象访问
CA oa;
int i = oa. fun();
或者
CA* poa;
poa-> fun();
通过函数指针来访问
int (*pfn)();
pfn = CA::fun;
i = pfn();
或者
pfn = oa.fun;
i = pfn();
3. 静态对象的内存分配
3.1. 函数内部的静态变量
C和C + +都允许在函数内部创建一个s t a t i c对象,这个对象将存储在程序的静态数据区中,而不是在堆栈中。这个对象只在函数第一次调用时初始化一次,以后它将在两次函数之间保持它的值。
3.2. 全局静态对象
在C++中,全局静态对象的构造函数是在m a i n ( )之前调用的,所以我们现在有了一个在进入m a i n ( )之前执行一段代码的简单的、可移植的方法,并且可以在退出m a i n ( )之后用析构函数执行代码。在C中要做到这一点,我们不得不熟悉编译器开发商的汇编语言的开始代码。
3.3. 静态对象的析构函数
静态对象的析构函数(包括静态存储的所有对象,不仅仅是上例中的局部静态变量)在程序从main() 块中退出时,或者标准的C库函数e x i t ( )被调用时才被调用。多数情况下m a i n ( )函数的结尾也是调用e x i t ( )来结束程序的。这意味着在析构函数内部使用e x i t ( )是很危险的,因为这可能陷入一个死循环中。但如果用标准的C库函数a b o r t ( )来退出程序,静态对象的析构函数并不会被调用。
我们可以用标准C库函数a t e x i t ( )来指定当程序跳出m a i n ( )(或调用e x i t ( ))时应执行的操作。
在这种情况下,在跳出m a i n ( )或调用e x i t ( )之前,用a t e x i t ( )注册的函数可以在所有对象的析构函数之前被调用。
静态对象的销毁是按它们初始化时相反的顺序进行的。当然只有那些已经被创建的对象才会被销毁。幸运的是,编程系统会记录对象初始化的顺序和那些已被创建的对象。全局对象总是在m a i n ( )执行之前被创建了,所以最后一条语句只对函数局部的静态对象起作用。如果一个包含静态对象的函数从未被调用过,那么这个对象的构造函数也就不会执行,这样自然也不会执行析构函数。