本期我们将讨论 C++ 中的 static(静态)。
static 关键字在 C++ 中有两个意思,这个取决于上下文。
简单了解
第一种情况是在类或结构体外部使用 static 关键字,另一种是在类或者结构体内部使用 static。
基本上我们可以这样理解,类外面的 static 意味着你声明为 static 的内容,将只是在内部链接,这意味着它只能对它所在的转换单元可见。
然而,类或结构体内部的静态变量 static 意味着该变量将与类的所有实例共享内存,这意味着该静态变量在你的类创建的所有实例中,静态变量只有一个实例。类似的事情也适用于类中的静态方法。
本期不深入讨论静态 static 在类或结构体范围内的实际含义,在之后的系列中我们会介绍。只关注在类和结构体外部的 static。
让我们通过一些代码来看一下。
我在一个完全空的 C++ 文件中定义了一个静态变量,使用惯例使用 S_ 来表示这个变量是静态的,将其值设置为 5。它整体上看起来和其他变量是一样的,只不过在它前面的是 static 关键字,这意味着这个变量只会在当前这个转换单元内部链接。
如果你还不知道 C++ 编译和链接是如何工作的,可以回去看一看 C++ 编译和链接的那一期,因为你真的需要了解发生了什么,然后才能学习本期的内容。
静态变量或函数意味着当需要将这些函数或变量与实际定义的符号链接时,链接器不会在这个转换单元的作用域之外的地方寻找那个符号定义。
静态变量
我们拿实际的代码来解释一下。
在第一个例子中,我们创建了一个静态变量,并将它设为 5,然后去另一个 C++ 文件,也就是另一个转换单元,一个带有main函数空白 C++ 文件。在这里创建一个全局变量。将它与之前静态变量设置一样的名字。其值设置为十,打印这个变量。
运行之后,我们可以看到,程序是可以编译的,不会遇到任何问题。
然后我们回到 Static.cpp 文件,删除了static 关键字。再次编译刚刚的代码。你会发现当它进入链接状态阶段时,会报一个链接错误。因为这个 s_Variable 变量已经在另一个转换单元中定义过了,当然是这样的,我们不能有两个同名的全局变量。
那么只有这一种调整方法了吗?当然不是。
我们可以这样修改。
这样操作意味着它会在外部转换单元中寻找 s_Variable 变量。这被称为 external linkage 或者 external linking。
现在运行代码也是没有问题了的。它可以输出 5,它引用了另外一个文件中的 s_Variable 变量的值。
这个时候如果我再次将另外一个文件中的 s_Variable 标记为静态。——这个过程有点像在类中声明一个私有变量,链接器在全局作用域下将不会看到这个变量。程序它还是会报错,因为我们已经做了有效的标记,这个变量已经是私有的了。
静态函数
接下来我们试一下函数 function。
我在 Static.cpp 文件中定义一个 function函 数。然后在 Main.cpp 中也定义一个具有相同签名的函数,返回值也是void。
编译一下这个程序。我们在链接阶段同样得到一个重复的符号错误。
回到 Static.cpp 文件中,使用 static 将它标记为静态的。链接器开始工作时,根本不会看到这个静态的函数。程序会顺利编译,不会得到任何错误。
这就是 C++ 中静态的全部含义。
当你在类和结构体之外使用静态时,它只是意味着你声明的静态函数或静态变量只会在它所在的文件中被看到。
如果你想在头文件中声明一个静态变量,将头文件包含在两个不同的 C++ 文件中。其实和上面的例子是一样的,都是在两个转换单元中都声明了相同的 s_Variable 变量为静态变量。当然,当你包含那个头文件时,它会复制所有内容并将其粘贴在 C++ 文件中。也就是将一个静态变量放到两个不同的转换单元中,这个你要留意一下。
最后的话
那么我们使用 static 的理由是什么呢?
你可以参考一下我们为什么要在类中使用 private?
如果你不需要变量是全局变量,你就需要尽可能多的使用静态变量。一旦在全局作用域下声明了东西,如果没有设定为static,链接器将会跨编译单元进行链接。这意味着你已经创建了一个完全全局的变量,它可以在任何地方使用,这可能会导致一些非常糟糕的 bug。
归根到底,全局变量是不好的。我不太喜欢用全局变量,也不建议你使用。除非你真的需要他们跨转换单元链接。
好了,本期的内容就是这些,下期再见。