6.10 - 静态局部变量
Key Takeaway
- 自动存储持续时间,会在变量定义时创建变量,退出语句块时销毁变量
- 静态存储持续时间,会在程序开始时创建变量,并且在程序结束时销毁。
- 具有初值0或者constexpr类型初始化值的静态变量,在程序启动时就会被初始化。而具有非constexpr类型初始化值的静态局部变量则会在第一次被定义时进行初始化(在后续的函数调用中,该变量不会被重新初始化)。
- 静态局部变量和全局变量一样都可以持续到程序结束前,但是它的可见范围更小,也就更安全。
- 对静态局部变量进行初始化。静态局部变量只会在第一次执行时被初始化,后面不会对其进行重复地初始化。
- 当变量创建或者初始化开销很大时,可以将其定义为静态const局部变量
- 除非变量永远不需要被重置,否则要避免使用静态局部变量
static
是 C++ 中最令人困惑的术语之一,这主要是因为static
在不同语境下的含义也是不同的。
在之前的课程中(6.4 - 全局变量),我们介绍过全局变量具有静态存储持续时间,也就是说全局变量会在程序开始时被创建,并且在程序结束时被销毁。
我们还介绍了 static
关键字可以使全局变量具有内部链接属性,也就是该全局变量只能在定义它的文件中使用。
在本节课中,我们会讨论static
对局部变量的影响。
静态局部变量
在 2.5 - 局部作用域 中,我们介绍过局部变量默认具有自动存储持续时间,也就是说,它会在定义时被创建,在语句块退出时被销毁。
对局部变量使用 static
关键字,可以将局部变量的存储持续时间从自动修改为静态。也就是说,这种局部变量会在程序开始时被创建,然后在程序结束后被销毁(就像全局变量一样)。这样一来,静态变量可以在离开作用域后继续保持它的值。
通过一个例子就可以很好的展示自动持续时间和静态持续时间的不同。
自动持续时间(默认):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
每次调用 incrementAndPrint()
的时候,变量value
都会被创建并且赋值为 1。incrementAndPrint()
会把value
递增为 2,然后将 2 打印出来,当 incrementAndPrint()
函数退出后,变量就会离开作用域并被销毁。所以,输出结果如下:
1 2 3 |
|
接下来,考虑 static 版本的程序。这两个版本程序的唯一区别就是接下来我们使用static
关键字将自动持续时间修改为静态持续时间。
静态持续时间(使用static
关键字):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
在这个程序中,因为 s_value
被定义为 static
类型,因此它会在程序启动时被创建。
具有初值0或者constexpr类型初始化值的静态变量,在程序启动时就会被初始化。而具有非constexpr类型初始化值的静态局部变量则会在第一次被定义时进行初始化(在后续的函数调用中,该变量不会被重新初始化)。因为 s_value
具有 constexpr 初始化值 1
,所以在程序启动时 s_value
就会被初始化。
当函数结束时, s_value
会超出作用域,但是它不会被销毁。每次 incrementAndPrint()
被调用时,s_value
的值仍然为之前的值。这样一来,程序的打印结果如下:
1 2 3 |
|
类似 “g_” 通常作为全局变量的前缀,“s_” 常被用来作为静态局部变量的前缀。
静态局部变量最常见的用法是最为一个唯一ID生成器。想象一下,如果有一个程序中包含了很多类似的对象(例如,在某个游戏中你被很多僵尸攻击,或者你希望模拟很多三角形)。这种情况下你几乎不可能定位是哪个对象出问题了。因此,如果你可以为每个对象都创建一个唯一的标识符,则可以在日后debug时轻松地将它们区别开来。
使用静态局部变量可以很轻松地创建唯一ID:
1 2 3 4 5 |
|
当函数第一次被调用时,静态局部变量被初始化为0。第二次调用时,该变量递增为了 1。每次调用该函数,静态局部变量的值都会比之前一次调用大1。你可以把这些值作为唯一ID赋值给对象。因为s_itemID
是局部变量,所以它并不会被其他函数“篡改”。
静态变量相对于全局变量具有一些优势(它们都不会在程序结束前被销毁),因为它只在定义它的块中可见。这样一来它就会更加安全(当你需要频繁修改它的值的时候)。
最佳实践
对静态局部变量进行初始化。静态局部变量只会在第一次执行时被初始化,后面不会对其进行重复地初始化。
静态局部常量
静态局部变量也可以被定义为常量。静态局部常量的典型使用场景是当你的函数需要一个常量值,但是创建或初始化该常量的开销非常大(例如该值是从数据库中读取的)。如果你使用一个普通的局部变量,那么这个变量就会在每次函数被调用时初始化。而使用const类型的静态局部变量,你就可以在第一次调用函数时初始化它,然后在后续的调用中对其进行重用。
不要使用静态局部变量来改变流程
考虑如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
输出如下:
1 2 3 |
|
这段代码的确能够按照预期工作,但是因为我们使用的是一个静态局部变量,这会使得程序难以理解。如果有人在不知道 getInteger()
函数的实现细节时,阅读 main
函数的代码,它完全不会认为两次调用 getInteger()
会产生不同的效果。但是两次调用该函数的确会产生不同的输出,这就好让人感到非常困惑,
1 |
|
假设我们想要为计算器添加减法功能,期望的输出结果如下:
1 2 3 4 5 6 7 8 |
|
此时你可能会考虑使用 getInteger()
再读取两个整数,就像做加法时一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
但是,这样并没有用,输出如下:
1 2 3 4 5 6 7 8 |
|
(输出的还是 “Enter another integer” 而不是 “Enter an integer”)
getInteger()
是不可重用的,因它具有某种内部状态(静态局部变量 s_isFirstCall
),而该状态并不能够从外部被重置。s_isFirstCall
并不是一个需要在整个程序中保持唯一的变量,尽管上面第一个程序能够正确运行,但是静态变量阻碍了我们对函数的重用。
实现 getInteger
的更好的方法是将 s_isFirstCall
作为参数传入。这样主调函数就可以根据需求选择打印的内容。
静态局部变量适用于该变量在整个程序中(或可预见的未来)需要保持唯一性且无需对其进行重置的情况。
最佳实践
除非变量永远不需要被重置,否则要避免使用静态局部变量