静态局部变量

关键字 static 是 C++ 中最易混淆的术语之一,主要原因在于它在不同上下文中具有不同含义。

在前面的课程中,我们了解到全局变量具有静态存储期:它们在程序启动时创建,在程序结束时销毁。
我们还讨论了 static 关键字如何赋予全局标识符内部链接,使其只能在定义所在文件中使用。

本课探讨将 static 关键字应用于局部变量时的效果。

静态局部变量

在《局部作用域简介》中,我们学到:默认情况下,局部变量具有自动存储期——在定义点创建,在离开代码块时销毁。

在局部变量前添加 static 关键字,会将其存储期由自动改为静态。此时变量在程序启动时创建,在程序结束时销毁(与全局变量一致)。因此,该变量在离开作用域后仍能保留其值!

通过示例最能直观体现自动存储期与静态存储期局部变量的区别。

自动存储期(默认)

#include <iostream>

void incrementAndPrint()
{
    int value{ 1 }; // 默认自动存储期
    ++value;
    std::cout << value << '\n';
} // value 在此处销毁

int main()
{
    incrementAndPrint();
    incrementAndPrint();
    incrementAndPrint();
    return 0;
}

每次调用 incrementAndPrint() 都会新建 value 并初始化为 1,递增后输出 2,随后销毁。程序输出:

2  
2  
2  

静态存储期(使用 static

#include <iostream>

void incrementAndPrint()
{
    static int s_value{ 1 }; // 通过 static 指定静态存储期。初始化仅执行一次
    ++s_value;
    std::cout << s_value << '\n';
} // s_value 作用域结束但不被销毁,仅无法访问

int main()
{
    incrementAndPrint();
    incrementAndPrint();
    incrementAndPrint();
    return 0;
}

s_value 在程序启动时创建。因其具有静态存储期,离开函数后仍保留值。首次调用时初始化为 1,后续调用不再重新初始化。输出:

2  
3  
4  

关键洞察
静态局部变量用于需要在多次函数调用间记住其值的局部变量。

最佳实践

始终初始化静态局部变量。它们仅在首次执行到定义时初始化一次,后续调用跳过初始化。

提示
如同用 g_ 前缀标识全局变量,常用 s_ 前缀标识静态(静态存储期)局部变量。

ID 生成器示例

静态局部变量最典型用途之一是唯一 ID 生成器。假设程序中存在大量相似对象(如僵尸或三角形),若发现缺陷,需区分具体对象。为每个对象分配唯一 ID 可极大简化调试。

int generateID()
{
    static int s_itemID{ 0 };
    return s_itemID++; // 返回当前值后递增
}

首次调用返回 0,第二次返回 1,依此类推。s_itemID 作为局部变量,不会被其他函数篡改。

静态局部常量

静态局部变量可加 constconstexpr。若某函数需使用常量值,但其创建或初始化代价高昂(如从数据库读取),使用 const/constexpr 静态局部变量可避免每次调用都重新初始化。

关键洞察
静态局部变量最适合用于避免每次函数调用都进行昂贵的局部对象初始化

不要用静态局部变量改变控制流

示例:

#include <iostream>

int getInteger()
{
    static bool s_isFirstCall{ true };
    if (s_isFirstCall)
    {
        std::cout << "Enter an integer: ";
        s_isFirstCall = false;
    }
    else
    {
        std::cout << "Enter another integer: ";
    }
    int i{};
    std::cin >> i;
    return i;
}

首次调用提示“Enter an integer:”,后续提示“Enter another integer:”。
此实现虽可行,但降低了代码可读性;若未阅读 getInteger() 实现,无法预知两次调用行为不同。

类比微波炉:按 +1 按钮第一次加 1 分钟,第二次加 1 秒,用户会困惑“未做任何改变,却行为不同”。函数亦应如此:相同输入应产生相同输出。

若需支持加法与减法,并保证提示一致,可将 s_isFirstCall 作为参数传入:

constexpr bool g_firstCall{ true };

int getInteger(bool bFirstCall)
{
    std::cout << (bFirstCall ? "Enter an integer: " : "Enter another integer: ");
    int i{};
    std::cin >> i;
    return i;
}

const 静态局部变量应仅在整个程序及其可预见的未来中唯一且无需重置时使用。

最佳实践

  • const 静态局部变量通常可以放心使用。
  • 非 const 静态局部变量一般应避免;若确需使用,确保变量无需重置且不影响程序流程。

提示
更通用的做法是将提示文本改为 std::string_view 形参,由调用者传入。

高级提示

若需多个独立实例的非 const 记忆变量(如多个独立 ID 生成器),可使用函数对象(参见 21.10 课《重载括号运算符》)。

小测验

问题 1
关键字 static 对全局变量有何作用?对局部变量又有何作用?

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"