C++中static用法小结

前言

  static 是 C++ 中很常用的一个关键字,它的用法也很多,时常会将其弄混,索性做个小结,以免以后忘记了或者继续弄混 (。・ω・。)。

预备篇

  首先要了解程序中数据的存储形式,一般而言数据的存储形式有三种:

  • 栈区(stack)—— 由编译器自动分配释放,一般用来存放函数的参数值,局部变量的值等;
  • 堆区(heap)—— 由程序员分配释放,对应于对象的 new 或 malloc 和 delete 或 free,若程序员忘记释放,则在程序完全退出之后由操作系统回收;
  • 静态存储区(static)—— 在编译时由编译器分配,在程序完全退出时由操作系统回收,一般用来存放全局变量和 static 变量。

  一般1声明的变量默认(如果变量类型,eg: int, double, ... 等,前不加 static 或其它关键字)都是 auto 2的,其一般存放在栈区,生存周期就只在包围其的 { } 内,在包围其的 { } 外就无法使用该变量。而 static 存放在静态存储区,其生存周期是全局的,它要等整个程序完全退出时才会销毁,在程序运行过程中,每次调用 static 变量都保持上一次调用结束后的值

类中篇

静态成员变量

  类中的静态成员变量被该类的所有实例共享,也可以不通过类的实例使用,在使用时首先需要对其初始化,也必须对其进行初始化,因为类中的静态成员变量只是声明,而且,类中的静态成员变量和普通静态变量一样是在程序初始化的时候分配的,在程序完全退出时由操作系统回收。具体用法如下:

1
2
3
4
5
6
7
8
class Test
{
private:
static int s_value; // 注意,这里不能初始化!因为其不属于类对象,只属于类作用域,独立于该类的任何实例
};

// 在cpp中或类定义体外必须对它进行定义和初始化,因为在程序编译时首先执行的就是对其初始化并分配内存:
int Test::s_value = 0; // 注意,这里没有static的修饰!

总而言之就是:类中的静态成员变量可以简单理解为一个名为 Test::s_value 的全局变量,被所有该类的实例共用,但独立于该类的任何实例,只属于该类作用域,在类的定义中能且只能被声明,不能在类定义体中进行初始化,必须要在类定义体外被定义和初始化

静态成员函数

  类中的静态成员函数和类中的静态成员变量有点类似,其在实现时不需要再加 static 修饰,同样能被该类的所有实例复用,同样只属于类作用域中的全局函数,同样不需要类的实例即可调用。类中的静态成员函数不能访问类的普通成员变量,只能访问类的静态成员变量(可以参考 C++静态成员函数访问非静态成员的几种方法 中的小 trick 访问普通成员变量,但非特殊情况不建议这么做)。具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test
{
private:
static void func(int i);

// 静态成员函数调用非静态成员变量方法
static void staticTest(Test *t)
{
t->value += 1;
}
private:
int value;
};

// 在cpp中可以不通过类的实例进行调用:
void Test::func(int);

总而言之就是:类中的静态成员函数可以简单理解为一个名为 Test::func(int) 的全局函数,能被该类的所有实例复用,但独立于该类的任何实例,只属于该类作用域,可以不通过类的实例进行调用,也可以像普通成员函数一样通过类的实例进行调用

特定范围篇

  为了使全局变量或函数只在特定 cpp 文件中起作用,需要在 cpp 文件中相应变量或函数前添加static 修饰,如下表:

类型.h 文件中.cpp 文件中
全局变量不使用 static 修饰,使用 extern 修饰使用 static 修饰
全局函数不使用 static 修饰使用 static 修饰
  • 如果在头文件中声明 static 全局变量,则在包含该头文件的每个 .cpp 文件中都会生成一个独立的同名变量,而这种写法没有任何意义;如果在 .cpp 文件中不使用 static 声明全局变量,则该全局变量可能会被其它 .cpp 文件共享,也可能不会,造成该变量的不确定性;所以如果该全局变量要被所有 .cpp 文件共享,则需要在头文件中声明 extern 全局变量(eg:extern int g_value; // 注意,不要初始化值!),再在某个 .cpp 文件中单独进行定义和初始化(仅一次)(eg:int g_value = 0; // 不要extern修饰!),如此即可在每个 .cpp 文件中共享该全局变量;而若只想在单个 .cpp 文件中使用全局变量,则需要在该 .cpp 文件中全局范围类声明和定义 static int g_value = 0;,如此可保证该变量能且只能被该 .cpp 文件使用。
  • 如果在 .cpp 文件中不使用 static 声明全局函数,则该全局函数可能会被其它 .cpp 文件共享,也可能不会,这样在别的 .cpp 文件调用同名函数时可能会出现问题;而在头文件中使用 static 声明全局函数同样没有任何意义;所以如果要被多个 .cpp 文件复用,就将其声明移到头文件中,且不需要 static 修饰,而若只想在特定 .cpp 文件中使用该全局函数,则需要在声明时添加 static 修饰。

最后,若是在 .hpp 文件中,则需要去除全局对象,将全局函数封装为类的静态方法。

  PS:若在函数中使用 static 修饰变量,则该函数无法做到线程安全,在程序运行过程中,每次调用该函数,函数内的 static 变量都将保持上一次调用结束后的值,所以在函数中慎用 static 变量,除非需要这个特性

后记

  写这篇文章的初衷在于时常需要 static 时老是忘记或弄混它的用法,不得不去网上查找,虽说网上的相关资料也有很多,但在找的时候还是有点麻烦,毕竟有很多不是自己需要的,而且自己总结一下对其理解又更深一些,下次要用时也能马上找到自己所需。

参考资料

[1] c/c++ static 用法总结(三版本合一)

[2] C++中static的用法总结

[3] C++ 类中的static成员的初始化和特点

[4] C++静态成员函数访问非静态成员的几种方法


  1. 这里的一般是指局部变量,若为全局变量则默认为 extern ,局部变量没有默认初值,其初值不确定,一般需要人为明确的赋初值,而全局变量默认初值为 0 ,一个比较好的编程习惯是声明一个变量就对其进行初始化(赋初值),尽量少用全局变量,全局变量显示声明 extern。↩︎

  2. ※注:这里的 auto 与 C++11 中的意义不同,这里的 auto 指的是变量的存储形式,而不是 C++11 那种可以当做任意的变量类型,eg: int, double, std::vector<std::vector<double>>, ...... ,与其对应的还有 extern 和 register 关键字,其中 register 关键字基本不用 。↩︎