C++ static关键字
static
关键字可用于声明全局范围、命名空间范围和类范围的变量和函数。 静态变量还可在本地范围声明。
先介绍几个概念:
-
静态持续时间,在程序启动时分配对象或变量,并在程序结束时释放对象或变量。
-
外部链接,变量的名称在用于声明变量的文件的外部是可见的。
-
内部链接,名称在用于声明变量的文件的外部是不可见的。
默认情况下,在全局命名空间中定义的对象或变量具有静态持续时间和外部链接。
static
关键字常用于以下情况:
- 在全局和/或命名空间范围 (在单个文件范围内声明变量或函数时,)
static
关键字指定变量或函数为内部链接,即外部文件无法引用该变量或函数。在声明变量时,变量具有静态持续时间,并且除非您指定另一个值,否则编译器会将变量初始化为 0。 - 在函数中声明变量时,
static
关键字指定变量只初始化一次,并在之后调用该函数时保留其状态。 - 在类内声明中声明数据成员时,
static
关键字指定该类的所有实例共享该变量的副本。 必须在文件范围内定义静态数据成员。 - 在类声明中声明成员函数时, 关键字指定该函数由类的所有实例
static
共享。 静态成员函数无法访问实例成员,因为该函数没有隐式this
指针。 若要访问实例成员,请使用作为实例指针或引用的参数来声明函数。 - 不能将联合成员声明为静态的。 但是,必须显式声明全局声明的匿名联合
static
。
以上前两条与面向对象(类)无关,也就是说在 C 中 static 同样有这些特性,而后三条则是针对类的,在 C++ 中才有这些特性。
C中的static
本小节介绍 C 中 static 关键字的作用,即不包含 C++ 中引入面向对象的特性(类)之后 static 的作用,该部分将在下一小节介绍。
内部链接
函数,变量均可被 static 修饰来实现内部链接。当同时编译多个文件时,所有未加 static 前缀的全局变量和函数默认都是外部链接的。举例来说明。同时编译两个源文件,一个是 hello_a.c
,另一个是 main.c
。
// hello_a.c
#include <stdio.h>
char a = 'A'; // a.c 文件内的全局变量
void printHello()
{
printf("Hello\n");
}
// main.c
#include <stdio.h>
void msg(); // 外部方法不声明会报警告
int main()
{
extern char a; // 外部变量必须先用 extern 关键字声明
printf("%c \n", a);
printHello();
return 0;
}
编译运行:
gcc main.c hello_a.c
./a.out
输出:
A
Hello
为什么在 hello_a.c 中定义的全局变量 a
和函数 printHello
能在 main.c 中使用?前面说过,所有未加 static 前缀的全局变量和函数都都是外部链接的,其它的源文件也能访问。此例中,a 是全局变量,printHello 是函数,并且都没有加 static 前缀,因此对于另外的源文件 main.c 是可见的。
如果加了 static,就会对其它源文件隐藏。例如在 a 和 printHello 的定义前加上 static,main.c 就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static 可以修饰函数和变量,将其对其他源文件隐藏起来,从而避免命名冲突。对于函数来讲,static 的作用仅限于该隐藏功能。
保持变量内容的持久
static的第二个作用是保持变量内容的持久,即static变量中的记忆功能和全局生存期。
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。之后再次运行到含有 static 关键字的初始化语句时不会再执行该语句。共有两种变量存储在静态存储区:全局变量和 static 变量,只不过和全局变量比起来,static 可以控制变量的可见范围。
PS:如果作为 static 局部变量在函数内定义,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
在下面的例子中,nStatic的值仅在初次调用 showstat 函数时初始化,之后在每次调用showstat函数时,nStatic的值都是保持的(而非重新初始化)。
// static1.cpp
#include <iostream>
using namespace std;
void showstat( int curr ) {
static int nStatic; // nStatic的值仅在初次调用时初始化,之后在每次调用showstat函数时,
// nStatic的值都是保持的
nStatic += curr;
cout << "nStatic is " << nStatic << endl;
}
int main() {
for ( int i = 0; i < 5; i++ )
showstat( i );
}
运行结果:
nStatic is 0
nStatic is 1
nStatic is 3
nStatic is 6
nStatic is 10
综合以上两点,有:
- 把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围
- 把局部变量(函数内)改变为静态变量后是改变了它的存储方式,从而改变了它的生存期
也就是说 static 这个关键字在不同的地方所起的作用是不同的。
默认初始化为0
static的另一个作用是默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加‘\0’;太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是 ‘\0’
。不妨做个小实验验证一下。
#include <stdio.h>
int a;
int main()
{
int i;
static char str[10];
printf("integer: %d; string: (begin)%s(end)\n", a, str);
return 0;
}
输出:
integer: 0; string: (begin)(end)
C++中的static
本小节主要是介绍在 C++ 中引入了面向对象的特性(类)之后,static 关键字的一些用途。当然之前的 C 中的 static 在 C++ 中也是成立的。
在类中声明 static 变量或者函数时,初始化时使用作用域运算符 ::
来标明它所属类。静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:
-
使用 static 关键字来修饰成员函数或变量,是指成为静态成员函数或变量。
-
类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。
-
static 关键字不能修饰虚函数。
-
由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“
nonmember
函数指针”。 -
由于静态成员函数没有this指针,所以就差不多等同于 nonmember 函数,结果就产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X W indow系统结合,同时也成功的应用于线程函数身上。 (这条没遇见过)
-
static 并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。
-
静态数据成员是静态存储的,所以必须对它进行初始化。 (程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)
-
静态成员初始化与一般数据成员初始化不同:
-
声明在类内,初始化赋值在类外;
-
初始时赋值前面不加 static,以免与一般静态变量或对象相混淆;
-
初始化时赋值不加该成员的访问权限控制符private,public等;
-
初始化时赋值使用作用域运算符
::
来标明它所属类;
所以我们得出静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值>
如:int myClass::num = 10
,在后面会有例子; -
-
为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志;
-
不论在类内声明的还是在成员函数中声明的 static 变量,都是整个类的所有实例对象共享一份副本。
以下是例程:
类内声明的static变量
// static2.cpp
#include <iostream>
using namespace std;
class CMyClass {
public:
static int m_i;
};
int CMyClass::m_i = 0;
CMyClass myObject1;
CMyClass myObject2;
int main() {
cout << myObject1.m_i << endl;
cout << myObject2.m_i << endl;
myObject1.m_i = 1;
cout << myObject1.m_i << endl;
cout << myObject2.m_i << endl;
myObject2.m_i = 2;
cout << myObject1.m_i << endl;
cout << myObject2.m_i << endl;
CMyClass::m_i = 3;
cout << myObject1.m_i << endl;
cout << myObject2.m_i << endl;
}
结果输出:
0
0
1
1
2
2
3
3
类 CMyClass
内的 static 变量 m_i
是整个类共享的,在该类的每个对象 myObject1
、myObject2
中都一样。
成员函数内声明的static变量
// static3.cpp
#include <iostream>
using namespace std;
struct C {
void Test(int value) {
static int var = 0;
if (var == value)
cout << "var == value" << endl;
else
cout << "var != value" << endl;
var = value;
}
};
int main() {
C c1;
C c2;
c1.Test(100);
c2.Test(100);
}
结果输出:
var != value
var == value
Ref:
cnblogs.com/songdanzju/p/7422380.html
https://docs.microsoft.com/en-us/cpp/cpp/storage-classes-cpp?view=msvc-170