[下一篇: C++类和结构体中的静态static]
1. 概述
本篇主要讨论C++中的static(静态)关键字。static关键字在C++中有两个意思,这取决于上下文。第一种意思是在类或结构体外部使用static关键字。第二种意思是在类或结构体内部使用static关键字。
直白来说,类或结构体外面的static,意味着我们声明为static的符号,链接将只是在内部,这意味着它只能对你定义他的翻译单元可见。
然而类或结构体内部的static,意味着该变量实际上将与类的所有实例共享内存,这意味着该静态变量在你在类中创建的所有实例中,静态变量只有一个实例。类似的事情也适用于类中的静态方法。在类中,没有实例会传递给该方法。
这里我们不深入讨论静态static在类或结构体范围内的实际含义,本篇我们只关注在类和结构体外部的静态static。
2. 案例
1. 准备项目
准备一个简单的项目,我们有一个main.cpp文件,内容如下。

2. 开始案例
我们创建一个Static.cpp文件

我们要做的就是定义一个静态变量,我将使用惯例s_
来表示这个变量是静态的,并给这个静态变量赋值为5,static int s_Variable = 5;

它和其他变量一样,但它前面有static
关键字,这到底是什么意思呢?
它的意思是,这个变量只会在这个翻译单元内部链接。有关链接的内容可参考编译器是如何工作的、链接器是如何工作的。
静态变量或函数意味着,当需要将这些函数或变量与实际定义的符号链接时,链接器不会在这个翻译单元的作用域之外寻找那个符号定义。
下面用例子来解释下。

我们在Static.cpp文件中创建了一个静态变量s_Variable,并赋值了5。
然后我们到另一个cpp文件,也就是另一个翻译单元,main.cpp文件。
我们要给它一个和Static.cpp文件中静态变量一样名字的变量并赋值为10,如下

如果我们试着来打印这个变量

单文件编译main.cpp文件 ctrl + F7

可以看到,编译成功,不会有任何问题。
如果我们F5运行我们的代码

可以看到,控制台打印了10。
然而,如果我们回到Static.cpp文件中

将static关键字去掉

然后编译我们的项目,因为编译项目会有链接阶段


可以看到,我们会得到一个链接错误,因为这个s_Variable变量已经在另一个翻译单元重定义了。所以我们不能有两个同名的全局变量。
一种修改的方法是,让main.cpp文件中s_Variable变量的实际指向Static.cpp文件中s_Variable变量。我们可以这样做,去掉main.cpp文件中s_Variable的赋值,并标识这个变量为extern
,

使用extern
,意味着它会在外部翻译单元中寻找这个s_Variable变量。这被称为external linkage
或external linking
。
如果我们F5运行我们的代码

可以看到我们得到的值是5。因为他引用了Static.cpp中的s_Variable变量。

然而,如果我们在Static.cpp文件中的s_Variable声明为static。

这有点像在类中声明一个私有变量,其他所有的翻译单元都不能看到Static.cpp中s_Variable变量。链接器在全局作用域下,将不会看到这个变量。
也就是说,如果我们回到main.cpp文件
编译我们的项目

可以看到我们会得到一个无法解析的外部命令的错误。这是因为它在任何地方都找不到名称为s_Variable的整型变量。因为我们有效的标记了Static.cpp中s_Variable这个变量为私有的。
如果我们回到Static.cpp文件,和之前一样,我们再声明一个函数Function。

然后我们在main.cpp文件中也声明一个具有相同签名的函数Function

如果我们现在编译我们的项目。

会看到一个链接错误1 个无法解析的外部命令
,这是前面测试s_Variable变量时的错误。我们去掉main.cpp文件中s_Variable相关变量

我们在编译我们的项目

可以看到,我们将在链接阶段得到一个重复的符号错误,因为链接器找到了两个Function函数。
我们切换到Static.cpp文件,在Function函数前面加上static
。

我们编译项目

可以看到编译通过了,因为当链接开始时,链接器根本不会看到Static.cpp中的这个静态的函数。所以我们不会得到任何错误。
这就是C++中静态的全部含义。当你在类和结构体之外使用静态时,意味着,当你声明静态函数或静态变量时,这些静态函数和静态变量只会在它们被声明的C++文件中被看到。
如果你想在头文件中声明一个静态变量,并将该头文件包含在两个不同的C++文件中。那就会我们现在的例子一样,就是在两个翻译单元中都声明了相同的s_Variable变量为静态变量。因为,当你包含那个头文件时,它会复制所有内容并将其粘贴到C++文件中,你所做的是将一个静态变量放到两个不同的翻译单元中。
至于为什么要用static,考虑下为什么要在类中使用private?如果我们不需要变量是全局变量,那我们就应该尽可能的多使用静态变量。因为一旦我们在全局作用域下声明东西的时候,也就是没有static修饰时,那么链接器会跨编译单元进行链接,这就意味这我们已经创建了一个全局的变量。如果我们创建一个变量variable,而且是在全局作用域下,那么这个变量variable,将可以在任何地方使用,这可能会导致一些非常糟糕的bug。
所以重点是,要让函数和变量标记为静态的,除非我们真的需要它们跨翻译单元进行链接。
[下一篇: C++类和结构体中的静态static]
[下一篇: C++类和结构体中的静态static]: