C++之静态详解

C++中静态用于静态修饰的有静态局部变量,静态全局变量,静态成员变量,静态函数,静态成员函数等等,搞清楚这些之间的区别,不是一件很容易的事。


static的作用

static作为C和C++语句中的修饰符,可以修饰变量和函数,主要用于控制变量的存储方式和可见性。
在函数内部定义的变量,程序执行到它的定义的时,编译器为它在栈上分配空间,栈空间会随着函数调用结束而被释放。这样带来的问题是,有些变量需要保留它的值,或者我们希望能有一块地方存储我们希望不变的值,而不是每次都由栈来分配,这样也能提高一部分性能。
static因为是公共的,所以它在内存中只被存储一处,供所有对象使用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。


static的内部机制

static修饰的数据成员实在数据段定义的,数据段同样也存放了全局变量,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。


全局变量和静态全局变量的区别

先举个例子,有两个文件:main.cpp和test.cpp
main.cpp

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;
void print();
int main() {
extern char *msg;
printf("%s\n", msg);
print();
return 0;
}

test.cpp

1
2
3
4
5
6
7
#include <iostream>
using namespace std;
char *msg = "hello static";
void print() {
printf("%s\n", msg);
}

函数运行之后输出两行hello static,如果将msg的声明改成static char *,则会出现msg未定义的错误。
不同点:这段程序可以看出这两者之间的区别,非静态的全局变量的作用域是整个程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。
相同点:全局变量和静态全局变量存储的位置是相同的。


普通局部变量和静态局部变量的区别

静态局部变量和普通局部变量的存储方式不一样,静态局部变量被存放在数据段,而普通存储在栈中。静态局部变量只被初始化一次,下次调用根据上一次的值。


静态函数和普通函数的区别

静态函数和普通函数的区别类似静态全局变量和全局静态变量的区别。静态函数的作用域仅限于在本文件中,只有在源文件中使用的函数应该声明为static函数。普通函数的作用是整个程序的源文件中。


静态数据成员

C++中,类的内存存储空间被分为五部分:堆、栈、全局/静态存储区和常量存储区。静态数据成员是属于类的,而不是属于某个对象,因为其是是全局/静态存储区中分配的,只能存储一份,这也就决定了其不能指定属于某个对象。静态数据成员调用时,采用以下格式:
<类名>::<静态数据成员名>或<类对象名>.<静态数据成员名>
注意事项:

静态数据成员和普通数据成员一样遵从public,protected,private访问规则;

静态数据成员同全局变量相比,有两个优势:

  • 静态数据成员没有进入程序的全局名字空间,因此就不存在与其他全局变量名字起冲突的可能性;
  • 静态数据成员可以实现信息隐藏,静态数据成员可以是private的,对外不可见。

静态数据函数

与静态数据成员一样,可以定义类的静态数据函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。类的静态成员函数调用采用如下格式:
<类名>::<静态成员函数名> (<参数表>)
有关静态成员函数,可以总结一下几点:

  • 在类体外的函数定义不能指定关键字static;
  • 静态成员之间可以互相访问,静态成员函数可以访问静态数据成员和静态成员函数
  • 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
  • 静态成员函数不能访问非静态成员函数和非静态数据成员;
  • 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;

总结

总的来说,面向过程的static关键字的作用有以下三点:

  • 隐藏作用域
    当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。如果加了static,就会对其它源文件隐藏。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
  • 保持变量内容持久化
    存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。
  • 默认初始化为0
    其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加’\ 0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是’\0’。
    面向对象的static关键字修饰主要有数据成员和数据函数,作用如上所示。

参考资料:
C++中Static作用和使用方法
static作用(修饰函数、局部变量、全局变量)