存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。

下面列出 C++ 程序中可用的存储类:

  • auto:这是默认的存储类说明符,通常可以省略不写。auto 指定的变量具有自动存储期,即它们的生命周期仅限于定义它们的块。auto 变量通常在栈上分配。
  • register:用于建议编译器将变量存储在CPU寄存器中以提高访问速度。在 C++11 及以后的版本中,register 已经是一个废弃的特性,不再具有实际作用。
  • static:用于定义具有静态存储期的变量或函数,它们的生命周期贯穿整个程序的运行期。在函数内部,static变量的值在函数调用之间保持不变。在文件内部或全局作用域,static变量具有内部链接,只能在定义它们的文件中访问。
  • extern:用于声明具有外部链接的变量或函数,它们可以在多个文件之间共享。==默认情况下,全局变量和函数具有 extern 存储类。==在一个文件中使用extern声明另一个文件中定义的全局变量或函数,可以实现跨文件共享。

从 C++ 17 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。
从 C++11 开始,register 已经失去了原有的作用,而 mutable 和 thread_local 则是新引入的特性,用于解决特定的编程问题。

一.static 存储类

在 C++ 中,static 是一个多功能的关键字,作为存储类说明符时,它主要用于控制变量或函数的存储期(生命周期)作用域链接性,核心作用是改变实体的默认行为。下面从不同场景详细解释:

1)static 修饰变量:3 种核心场景

1. 局部静态变量(函数内的 static 变量)

  • 存储期:==从程序启动到程序结束(与全局变量生命周期相同),而非随函数调用创建、随函数返回销毁。==
  • 作用域:==仅在定义它的函数 / 代码块内可见(局部作用域)==。
  • 初始化:仅在第一次进入函数时初始化一次,后续调用不再重新初始化(默认零初始化,若显式初始化则按指定值初始化)。
    #include <iostream>
    void count() {
    static int num = 0; // 局部静态变量,仅初始化一次
    num++;
    std::cout << num << " "; // 每次调用递增
    }

    int main() {
    count(); // 输出 1
    count(); // 输出 2
    count(); // 输出 3
    return 0;
    }

2. 全局静态变量(文件内的 static 变量)

  • 存储期:整个程序运行期间(同全局变量)。
  • 作用域:仅在定义它的当前源文件(.cpp 内可见(限制了链接性,从 “外部链接” 变为 “内部链接”)。
  • 初始化:程序启动时零初始化(未显式初始化时)。
    // 文件 a.cpp
    static int global_static = 10; // 仅在 a.cpp 内可见

    // 文件 b.cpp
    extern int global_static; // 错误!无法访问 a.cpp 中的 static 全局变量

3. 类的静态成员变量(static 成员)

  • 存储期:属于类本身,而非类的某个实例,生命周期同程序。
  • 作用域:类的作用域内,可通过 类名::成员名 或对象访问(但需先在类外定义)。
  • 初始化:必须在类外单独定义(且只能定义一次),未显式初始化则零初始化。
    class MyClass {
    public:
    static int count; // 类内声明
    };

    int MyClass::count = 0; // 类外定义(必须)

    int main() {
    MyClass::count++; // 直接通过类名访问
    MyClass obj;
    obj.count++; // 也可通过对象访问
    std::cout << MyClass::count; // 输出 2
    return 0;
    }

2)static 修饰函数:限制函数的链接性

  • 作用static 修饰的函数(全局函数或类的非成员函数)仅在当前源文件内可见(内部链接),其他文件无法通过 extern 调用。
  • 用途:避免不同文件中同名函数的冲突,实现 “文件内私有函数”。
    // 文件 utils.cpp
    static void helper() { // 仅在 utils.cpp 内可见
    // ...
    }

    void public_func() {
    helper(); // 可调用
    }

    // 文件 main.cpp
    extern void helper(); // 错误!无法访问 utils.cpp 中的 static 函数

二. extern 存储类

1)extern 的核心功能:声明外部实体

C++ 中,实体(变量 / 函数)的使用需要经过 “声明” 和 “定义” 两个步骤:

  • 定义(definition):为实体分配内存,指定初始值(变量)或实现(函数),一个实体只能定义一次。
  • 声明(declaration):告诉编译器实体的类型和名字,不分配内存,可多次声明。
    extern 的作用就是显式声明一个 “已在其他地方定义” 的实体,让当前文件可以使用它。

2)extern 修饰变量:跨文件共享变量

当需要在多个源文件(.cpp)中使用同一个全局变量时,extern 是标准用法:

  1. 在一个源文件中定义变量(仅一次):
    无需 extern,直接定义(分配内存)。
  2. 在其他源文件中用 extern 声明变量(可多次):
    告诉编译器 “该变量已在别处定义,这里直接用即可”。
    // 文件 a.cpp(定义变量)
    int global_var = 100; // 定义全局变量,分配内存并初始化

    // 文件 b.cpp(声明并使用变量)
    #include <iostream>
    extern int global_var; // 声明:该变量在其他地方定义
    void print() {
    std::cout << global_var; // 正确:使用 a.cpp 中定义的变量
    }

    // 文件 main.cpp(声明并使用变量)
    extern int global_var; // 再次声明
    int main() {
    global_var = 200; // 修改变量
    print(); // 输出 200(跨文件生效)
    return 0;
    }
    注意:
  • extern 声明变量时不能初始化(初始化属于定义行为),例如 extern int x = 5; 是错误的,这会被编译器视为 “定义”,可能导致重复定义错误。
  • 如果变量仅在单个文件中使用,建议用 static 修饰(限制为内部链接),避免全局污染。

3)extern 修饰函数:跨文件共享函数

函数默认具有外部链接性(即可以被其他文件访问),因此通常不需要 extern 修饰。但 extern 可以显式声明 “该函数在其他文件中定义”,增强代码可读性。

// 文件 func.cpp(定义函数)
void add(int a, int b) { // 函数默认外部链接
return a + b;
}

// 文件 main.cpp(声明并使用函数)
extern void add(int a, int b); // 显式声明(可选,不写extern也可)
int main() {
add(2, 3); // 正确:调用 func.cpp 中定义的函数
return 0;
}

说明:

  • 函数声明时,extern 可以省略(因为函数默认外部链接),但加上 extern 更清晰地表明 “该函数定义在别处”。
  • 如果函数用 static 修饰(内部链接),则不能用 extern 在其他文件中声明(会导致链接错误)。