1.什么是static

static 是 C/C++ 中很常用的修饰符,它被用来控制变量的存储方式和可见性。

1.1.static的引入

我们知道在函数内部定义的变量,当程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义为全局的变量,但定义一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅只受此函数控制)。static 关键字则可以很好的解决这个问题。

另外,在 C++ 中,需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见时,可将其定义为静态数据。

1.2.静态数据存储

全局(静态)存储区:分为 DATA 段和 BSS 段。DATA 段(全局初始化区)存放初始化的全局变量和静态变量;BSS 段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运行结束时自动释放。其中BBS段在程序执行之前会被系统自动清0,所以未初始化的全局变量和静态变量在程序执行之前已经为0。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。

在 C++ 中 static 的内部实现机制:静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。

这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的 main() 函数前的全局数据声明和定义处。

静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的”尺寸和规格”,并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。

static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

优势:可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

2.static的作用

2.1.全局静态变量

在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。

内存中的位置:静态存储区,在整个程序运行期间一直存在。

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化)。

作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

2.2.局部静态变量

在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。

内存中的位置:静态存储区。

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化)。

作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

size_t count_calls() {
static size_t ctr=0;
return ++ctr;
}

int main(int argc, char const *argv[])
{
for (size_t i=0; i<10; ++i) {
cout << count_calls() << " ";
}
return 0;
}

输出

1
1 2 3 4 5 6 7 8 9 10

2.3.静态函数

在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突。

warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰。

2.4.类的静态成员

在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用,而且不需要生成对象就可以访问该成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

class A
{
public:
int y; //普通变量成员
int &r; //引用成员变量
const int d; //常量成员变量
static int c; //静态成员变量

A():y(1), r(y), d(3){};
~A(){};

};

int A::c = 1; // 初始化静态成员变量
int main(int argc, char const *argv[])
{
A a;
A b;
a.c=2;
cout << a.c << endl; // 2
b.c=3;
cout << a.c << " " << b.c << endl; // 3 3
A::c=4;
cout << a.c << " " << b.c << endl; // 4 4
A c;
cout << c.c << endl; // 4
return 0;
}

由于声明为static的变量只被初始化一次,因为它们在单独的静态存储中分配了空间,因此类中的静态变量由对象共享。对于不同的对象,不能有相同静态变量的多个副本。也是因为这个原因,静态变量不能使用构造函数初始化

2.5.类的静态函数

静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。

在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

class A
{
public:
A(){cout << "running A()" << endl;};
~A(){cout << "running ~A()" << endl;};
static void f() {cout << "running f()" << endl;};

};

int main(int argc, char const *argv[])
{
A::f(); // 调用类的静态函数,不需要对象名
{
A a;
a.f();
}
cout << "END of main" << endl;
return 0;
}

输出:

1
2
3
4
5
running f()
running A()
running f()
running ~A()
END of main

在上面的程序中,类A的对象a在括号内是非静态的,所以出了括号之后,a的”生命“也就结束了。如果在括号内将类的对象声明为静态的会怎么样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

class A
{
public:
A(){cout << "running A()" << endl;};
~A(){cout << "running ~A()" << endl;};
static void f() {cout << "running f()" << endl;};

};

int main(int argc, char const *argv[])
{
A::f();
{
static A a;
a.f();
}
cout << "END of main" << endl;
return 0;
}

输出:

1
2
3
4
5
running f()
running A()
running f()
END of main
running ~A()

我们可以清晰地看到,a的析构函数是在main函数结束之后才调用的。这是因为,静态对象的范围是贯穿程序的生命周期。

3.总结

  • (1)在修饰变量的时候,static 修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。
  • (2)static 修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是 extern 外部声明也不可以。
  • (3)static 修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候自动初始化为 0。
  • (4)不想被释放的时候,可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用 static 修饰。
  • (5)考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用 static)

4.相关文章