【C++】C++ static关键字详解
- static的作用
1.隐藏
当我们编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,其他的源文件也能访问。如,我们有源文件source1.cpp定义了一个全局变量i和函数Func
1 //source1.cpp
2 int i = 0;
3
4 void Func()
5 {
6 printf("Hello World\n");
7 }
如果在另一个文件main.cpp中是可以引用i和Func的。然而,如果一个文件要使用在其他源文件定义的变量或函数,一定要在此文件件中包含相应变量或函数的声明。其中变量一定要用extern声明,函数的声明加不加extern没有影响。如下所示:
1 //source1.cpp
2 extern int i; //变量i的声明
3 void Func(); //函数的声明
4
5 int main()
6 {
7 printf("%d\n", i); //使用变量i
8 Func(); //使用函数Func()
9
10 return 0;
11 }
如果加上static前缀,就不能像上面的程序一样被其他程序使用。
1 static int i = 0;
2
3 static void Func()
4 {
5 printf("Hello World\n");
6 }
2. static的第二个作用是默认初始化为0,包括未初始化的全局变量与局部静态变量。其实,未初始化的全局变量也具备这一属性,因为未初始化的全局变量与未初始化的静态变量时存储在同一块区域内(BSS段)
3. static的第三个作用是保持局部变量内容的持久。
函数内自动变量,当调用时就存在,退出函数时就消失,但静态局部变量虽然在函数内定义,但静态局部变量始终存在着,也就是说它的声明期为整个源程序,其特点是只进行一次初始化且拒具有'记忆性'。
静态局部变量的生存期虽然为为整个源程序,但其作用域仍然与局部变量相同,即只能在定义该变量的函数内使用该变量。退出函数后,尽管该变量还继续存在,但不能继续使用它。
- 类中static的作用
静态数据成员:
C++重用了static这个关键字,并赋予了它与前面不同的含义:表示属于一个类而不是属于此类对象的任何特定对象的变量和函数。我们可以使用作用于操作符和类的对象、引用或指针来访问静态成员。
通常,非static数据成员存在于类类型的每个对象中。不管该类产生多少个对象,静态成员变量永远只有一个实例,而且在没有对象实例的情况下已经存在。不像普通的数据成员,static数据成员独立于该类的任意对象而存在;每个static数据成员是与类关联的对象,并不与该类的对象相关联。也就是说当某个类的实例修改了该静态数据成员变变量,其修改值为该类的其他所有实例所见。
静态数据成员和普通数据成员一样遵从public、protected、private访问规则。
可以通过作用域操作符从类直接调用static成员,或者通过对象、引用或指向该类类型对象的指针间接调用。
类的数据成员也存储在全局(静态)存储区。静态数据成员定义时要分配空间,所以不能在类声明中定义。
static数据成员可以声明为任意类型,可以是常量、引用、数组、类类型,等等。
因为静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义。这意味着它们不是由类的构造函数初始化。而一般来说我们不能在类的内部初始化静态成员(再定义体内初始化会出错)。相反的,必须在类的外部定义和初始化每个静态成员。和其他对象一样,要给静态数据成员只能定义一次。类似于全局变量,静态数据成员定义在任何函数之外。因此一旦被定义,就将一直存在于程序的整个生命周期中。
这个规则的一个例外是,只要初始化是一个常量表达式,整形const static数据成员就可以在类的定义体中进行初始化(不是定义)。然而,const static数据成员必须在类的定义体之外进行定义。只不过定义时,不在需要初始化。
保证对象正好定义一次的最好办法,就是将static数据成员的定义放在包含类的非内联成员函数定义的文件中。
double Account::interestRate = initRate();
像使用任意的类成员一样,在类定义体外部引用类的static成员时,必须指定成员在哪个类中定义的。然而,static关键字只能用于类定义体内部的声明中,定义不能标示为static。
类的静态成员即可以被类的静态方法访问,也可以被非静态方法访问。
static成员函数:
静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分,它为类服务而不是为某一个类的具体对象服务。
当在类外部定义静态成员时(不论是变量还是函数),不能重复static关键字,该关键字只出现在类内部声明语句中。
因为普通成员函数总是具体的属于某个类的具体对象,所以普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身。
与普通成员函数相比,静态成员函数由于不与任何的对象相关联,因此它不具有this指针。因而它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数与访问静态数据成员。
因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。也不能被声明为虚函数、volatile。
静态成员函数不需要必须经由类对象调用,也可以像静态数据成员一样使用类名、指向类对象的指针或引用调用:
1 class A { 2 public: 3 static void Func() { 4 cout<<"Hello World"<<endl; 5 } 6 7 static int i; 8 }; 9 10 int A::i = 9; 11 12 int main() 13 { 14 A a; 15 A* pa = &a; 16 A &ra = a; 17 18 A::Func(); //使用类型调用静态成员函数 19 a.Func(); //使用对象调用静态成员函数 20 pa->Func(); //使用对象指针调用静态成员函数 21 ra.Func(); //使用对象引用调用静态成员函数 22 23 cout<<A::i<<endl; //使用类型调用静态数据成员 24 cout<<a.i<<endl; //使用对象调用静态数据成员 25 cout<<pa->i<<endl;//使用对象指针调用静态数据成员 26 cout<<ra.i<<endl; //使用对象引用调用静态成员数据 27 28 return 0; 29 }
关于静态成员函数,可以总结为一下几点:
1. 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员以及静态成员函数。静态成员函数不能访问非静态成员函数和非静态数据成员,非静态数据成员可以任意地访问静态成员函数和静态数据成员;
2. 由于没有this指针的额外开销,因此静态成员函数与类的非静态程彦函数相比速度上会有些许的增长。
使用静态成员变量而不是全局变量有三个优点:
1)静态成员的名字是在类的作用域,因此可以避免与其他类的成员或全局对象名字冲突
2)可以实施封装。static成员可以是私有成员,而全局对象不可以。
3)通过阅读程序容易看出static成员是与特定类关联的。这种可见性可清晰地显示程序员的意图。 - 参考资料
1. 《C++ Primer 第五版》7.6
2. 《王道程序员求职宝典》
3. 《深度探索C++对象模型》