背景回顾
在《全局变量简介》中,我们介绍了全局变量;在《静态局部变量》中,又介绍了静态局部变量。这两类变量均具有静态存储期:它们在程序启动时创建,在程序结束时销毁,即使离开作用域也保留其值。
示例:
#include <iostream>
int generateID()
{
    static int s_id{ 0 }; // 静态局部变量
    return ++s_id;
}
int main()
{
    std::cout << generateID() << '\n'; // 1
    std::cout << generateID() << '\n'; // 2
    std::cout << generateID() << '\n'; // 3
    return 0;
}
类中的两种新 static 用法
类类型为 static 关键字带来了两种新用途:
- 静态成员变量(本节讨论);
- 静态成员函数(下节讨论)。
静态成员变量
与普通成员变量的区别
先观察一个普通类:
#include <iostream>
struct Something
{
    int value{ 1 };
};
int main()
{
    Something first{};
    Something second{};
    first.value = 2;
    std::cout << first.value << '\n';  // 2
    std::cout << second.value << '\n'; // 1
}
每个对象各自拥有普通成员变量的副本。
若将成员变量声明为 static,则该变量被所有对象共享:
#include <iostream>
struct Something
{
    static int s_value; // 声明为静态
};
int Something::s_value{ 1 }; // 定义并初始化
int main()
{
    Something first{};
    Something second{};
    first.s_value = 2;
    std::cout << first.s_value << '\n';  // 2
    std::cout << second.s_value << '\n'; // 2
}
静态成员不属于任何对象
尽管可以通过对象访问静态成员(obj.s_value),但即使未创建任何对象,静态成员也依然存在。它们的生命周期与程序相同,本质上是位于类作用域内的全局变量。
关键洞见
静态成员是位于类作用域中的全局变量。
因此,推荐使用类名加作用域解析运算符访问:
Something::s_value = 2;
最佳实践
使用 类名::静态成员 的形式访问静态成员。
定义与初始化静态成员变量
在类内声明静态成员变量时,仅向编译器告知其存在(类似前向声明)。
必须在类外全局作用域显式定义(并可初始化):
int Something::s_value{ 1 }; // 定义并赋初值
- 若未显式初始化,静态成员将零初始化。
- 该定义不受访问控制限制;即使变量在类中为 private,也可在类外定义。
- 非模板类:- 若类定义在头文件,静态成员定义通常放在对应 .cpp文件;
- 也可声明为 inline并放在头文件中,以支持仅头文件库。
 
- 若类定义在头文件,静态成员定义通常放在对应 
- 模板类:- 模板静态成员定义通常紧跟类模板定义放在头文件中(隐式 inline,不违 ODR)。
 
- 模板静态成员定义通常紧跟类模板定义放在头文件中(隐式 
- 切勿把非 inline静态成员定义放在头文件,否则多重包含会导致链接错误。
类内初始化静态成员
若静态成员为:
- const整型(含- char、- bool);
- const枚举类型;
则可在类内直接初始化:
class Whatever
{
public:
    static const int s_value{ 4 }; // 无需额外定义行
};
此外,C++17 起可声明 inline 静态成员:
class Whatever
{
public:
    static inline int s_value{ 4 }; // 类内定义并初始化
};
constexpr 静态成员自 C++17 起也隐式 inline,可直接初始化:
#include <string_view>
class Whatever
{
public:
    static constexpr double s_value{ 2.2 };
    static constexpr std::string_view s_view{ "Hello" };
};
最佳实践
将静态成员声明为 inline 或 constexpr,以便在类内完成定义与初始化。
静态成员变量示例:生成唯一 ID
#include <iostream>
class Something
{
private:
    static inline int s_idGenerator{ 1 }; // 共享计数器
    int m_id{};
public:
    Something() : m_id{ s_idGenerator++ } {}
    int getID() const { return m_id; }
};
int main()
{
    Something first, second, third;
    std::cout << first.getID() << '\n';  // 1
    std::cout << second.getID() << '\n'; // 2
    std::cout << third.getID() << '\n';  // 3
}
每个新对象获得递增的唯一 ID,便于调试区分。
静态成员的其他用途
- 共享查找表:如预计算数组。设为 static后,所有对象共用一份,节省内存。
- 类型推导:- 静态成员可使用 auto或 CTAD 进行类型推导;
- 非静态成员不允许,以避免潜在歧义。
 
- 静态成员可使用 
示例:
#include <utility> // std::pair
class Foo
{
private:
    // 非静态成员禁止 auto / CTAD
    // auto m_x{ 5 };          
    // std::pair m_v{ 1, 2.3 }; 
    // 静态成员允许
    static inline auto s_x{ 5 }; 
    static inline std::pair s_v{ 1, 2.3 }; 
};
小结
- 静态成员变量被所有对象共享,生命周期贯穿整个程序。
- 推荐通过 类名::成员访问。
- 必须在类外(或 inline/constexpr内)显式定义,避免多重定义。
- 可用于唯一 ID、全局计数器、共享查找表等场景,并可利用 auto/ CTAD 进行类型推导。
