在 C/C++ 中,关键字 static
在不同的应用场景下,有不同的作用,这里总结一下,避免在使用时弄混。
我按照以下的逻辑来分类 static
的作用场景
cpp
文件也无法使用该函数)对于普通的全局变量来说,同一项目中的其他文件也可访问相同的全局变量,若为了限制全局变量在本文件中,则需要在这个全局变量上加一个 static
,这样该变量就只能在本文件可见。
我们先来演示一下,如何在一个文件中使用另一个文件的全局变量。
假设有 a.cpp
和 b.cpp
,我们在 b
中定义一个全局变量 staticValue
,然后在 a
中打印出来。
// b.cpp
// 全局变量
int staticValue = 10;
void staticMain() {
// ...
}
// a.cpp
#include
using namespace std;
// 必须通过 extern 关键字在整个项目搜索 staticValue
extern int staticValue;
int main()
{
cout << "在 a.cpp 中可见:" << staticValue << endl; // 在 a.cpp 中可见:10
}
显然我们成功在 a
文件中使用到了 b
文件内定义的全局变量。
现在,我希望将 staticValue 的全局可见性,限制在 b 文件内,不让其他文件也可以访问,于是我在 b 的 staticValue 前加上 static 关键字
// b.cpp
// 全局变量
static int staticValue = 10;
void staticMain() {
// ...
}
将代码如上修改后,当我们再次运行时,程序会在链接阶段报错,报错信息如下,根据描述可见在 a
的 obj
文件(汇编后生成的对象文件)里没有解析到 staticValue
这个变量,达到了我们限制全局变量在本文件内的目的
static 作用在局部变量上,即使离开变量作用域,也保存变量值。具体的效果就是延长了变量的生命周期,比如用作计数器。效果如下面的代码所示
可以看到,num
的值是逐渐累加的过程
#include
void count();
int main(void)
{
int i=0;
for (i = 0;i <= 5;i++)
{
count();
}
return 0;
}
void count()
{
/*声明一个静态局部变量*/
static int num = 0;
num++;
printf("%d\n",num); // 1 2 3 4 5 6
}
从编译器的内存分布上讲,被 static
修饰的变量会被保存在数据区中,而普通的变量是保存在栈区或者堆区中
在这种情况下,static
标记的变量可以在多个对象之间共享该变量,在底层上,因为 static
标记的变量并非存在于对象的内存空间,而是存在于数据区中(这涉及到 C 语言的内存布局)。
具体的效果如下面代码所示,我们声明了 3 个学生对象,分别是 zhangsan,lisi 和 wangwu,并在定义他们的时候给他们传入了各自的初始分数,最终求得三个人的总分。
#include
using namespace std;
class student {
private:
static int sumScore;
int myScore;
public:
student(int m):myScore(m){
sumScore += myScore;
}
int getSumScore() {
return sumScore;
}
};
int student::sumScore = 0; // 静态成员变量必须在类外被初始化
int main()
{
student zhangsan(10);
student lisi(20);
student wangwu(30);
cout << wangwu.getSumScore() << endl; // 60
return 0;
}
关于 static 在修饰成员变量时的注意事项
static
修饰的成员变量属于类,在对象间共享,因此某个对象修改了该变量,对其他对象也是可见的public
,private
和 protected
)它起到的效果和 static 作用在全局变量上类似,将函数的可见性限制在本文件内。
这里我们直接举被 static 修饰的函数的例子。
假设在 b.cpp 中我们定义了一个 bMain() 的函数,在 b.h 的头文件中进行声明,在 a.cpp 中引入这个头文件,并调用 bMain(),那么是可以正常调用的。
现在,我们在 b.cpp 中,在 bMain() 前加上一个 static 关键字,那么 a.cpp 就不能在调用了。
// b.cpp
#include
using namespace std;
static void bMain() {
cout << "bMain" << endl;
}
// b.hpp
void bMain();
// a.cpp
#include "b.hpp"
int main()
{
bMain();
return 0;
}
执行上述代码,我们可以看到和 “static 作用在全局变量上” 小节类似的报错问题
静态成员函数具有和静态成员变量类似的性质和作用,一般可以用这种方式来确定类创建了多少个对象
#include
using namespace std;
class student {
private:
static int sumObj;
public:
student() {
sumObj += 1;
}
static int getObjNum() {
return sumObj;
}
};
int student::sumObj = 0; // 静态成员变量必须在类外被初始化
int main()
{
student zhangsan;
student lisi;
student wangwu;
cout << "student 有几个对象? " << student::getObjNum() << endl; // 3
return 0;
}
可以看到,最终我们输出了 3 个对象
这里包括了函数模板和类模板,它们的逻辑都是一样的
#include
using namespace std;
template <typename T>
void fun()
{
static int i = 1;
cout << ++i << endl;
return;
}
int main()
{
// 前两个是相同的模板函数
fun<int>(); // 2
fun<int>(); // 3
// 这是不同的模板函数
fun<double>(); // 2
return 0;
}
类模板也是一样的道理
#include
using namespace std;
template<class T> class Test
{
public:
static int count;
Test(){};
~Test(){};
void add()
{
count++;
cout << count << endl;
}
};
template<class T>
int Test<T>::count = 0;
int main()
{
Test<int> a;
Test<int> b;
Test<double> c;
a.add(); // 1
b.add(); // 2
c.add(); // 1
}
【注】函数(类)模板是指的在文件中定义的,程序员自己写的模板部分。模板(类)函数是指在实例化时,编译器为我们生成的函数