static 就是让变量或函数"独立存在"------不依赖于某个对象,也不依赖于某次函数调用。修饰局部变量让它只初始化一次、生命周期变长;修饰全局变量/函数让它只能在当前文件可见;修饰类成员让它属于类而不是对象。
static 在 C++ 里有四种完全不同的用法,下面逐一拆解。
一、static 修饰局部变量------只初始化一次,生命周期变长
普通局部变量 vs 静态局部变量
cpp
void normalFunc() {
int x = 0; // 每次调用都创建,每次都初始化为 0
x++;
cout << x; // 每次输出 1(每次都从 0 开始加)
}
void staticFunc() {
static int x = 0; // 只在第一次调用时初始化一次,之后保留值
x++;
cout << x; // 第一次输出 1,第二次输出 2,第三次输出 3
}
int main() {
normalFunc(); // 1
normalFunc(); // 1
staticFunc(); // 1
staticFunc(); // 2
staticFunc(); // 3
}
普通变量 :每次进入函数都在栈上重新创建,函数退出就销毁。
静态局部变量:存储在静态区,第一次执行到声明时初始化,函数退出不销毁,直到程序结束才释放。
场景:统计某个函数被调用了多少次,排查设备断线重连的逻辑。
cpp
void onDeviceDisconnected() {
static int reconnectCount = 0; // 记录重连次数
reconnectCount++;
qDebug() << "设备断线重连次数:" << reconnectCount;
if (reconnectCount > 5) {
qDebug() << "重连次数过多,停止重试";
}
}
二、static 修饰全局变量/函数------限制在本文件可见
不写 static(默认外部可见)
cpp
// file1.cpp
int global = 100; // 全局变量,其他文件可以用 extern 访问
void helper() { // 全局函数,其他文件可以直接调用
// ...
}
加上 static(只在当前文件可见)
cpp
// file1.cpp
static int s_secret = 100; // 只有 file1.cpp 能访问,其他文件访问不到
static void internalHelper() { // 只有 file1.cpp 能调用
// ...
}
为什么要这样做? 防止多文件编译时的命名冲突。如果两个 .cpp 文件都定义了同名的全局变量,链接时会报错。加上 static 后,每个文件有自己独立的副本,互不干扰。
场景 :协议解析模块内部的辅助函数不想暴露给其他模块,用 static 限制在本文件使用。
cpp
// ProtocolParser.cpp
static bool isValidChecksum(const uint8_t* data, int len) {
uint8_t sum = 0;
for (int i = 0; i < len - 1; i++) sum += data[i];
return sum == data[len - 1];
}
// 这个函数只在 ProtocolParser.cpp 内部用,外部看不到
三、static 修饰类成员变量------属于类本身,不属于对象
普通成员变量 vs 静态成员变量
cpp
class Counter {
public:
int normal = 0; // 普通成员:每个对象各有一份
static int s_count; // 静态成员:所有对象共享一份
};
int Counter::s_count = 0; // ⚠️ 必须在类外单独定义!
int main() {
Counter c1, c2;
c1.normal = 10; // 只改 c1 的
c2.normal = 20; // 只改 c2 的
// c1.normal = 10, c2.normal = 20(互不影响)
Counter::s_count = 100; // ✅ 通过类名访问
c1.s_count = 200; // ✅ 也可以通过对象访问
cout << c2.s_count; // 输出 200(所有对象共享)
}
为什么必须在类外定义?
类声明只是"图纸",不会分配存储空间。静态成员变量所有对象共享,必须在 .cpp 文件中单独分配存储空间。
场景:统计全局测试总数、记录启动时间戳。
cpp
class TestManager {
public:
static int s_totalTests; // 所有测试共用计数器
static long long s_startTime;
};
int TestManager::s_totalTests = 0;
long long TestManager::s_startTime = QDateTime::currentMSecsSinceEpoch();
四、static 修饰类成员函数------属于类本身,只能访问静态成员
普通成员函数 vs 静态成员函数
cpp
class Calculator {
public:
int normalAdd(int a, int b) { return a + b + offset; } // 需要对象,能用 offset
static int staticAdd(int a, int b) {
// return a + b + offset; // ❌ 报错!静态函数不能访问普通成员 offset
return a + b; // ✅ 只能访问静态成员
}
private:
int offset = 10; // 普通成员
static int s_base; // 静态成员
};
int Calculator::s_base = 5;
int main() {
Calculator calc;
calc.normalAdd(3, 4); // 必须用对象调用
Calculator::staticAdd(3, 4); // ✅ 不需要对象,直接用类名调用
}
静态成员函数的特点:
- 没有
this指针(因为它不依赖任何对象) - 不能访问普通成员变量和普通成员函数(它们都依赖
this) - 可以访问静态成员变量和静态成员函数(它们属于类本身)
- 可以用类名直接调用(
ClassName::func())
场景:工具函数、配置管理、全局状态查看。
cpp
class RssiHelper {
public:
// 工具函数:不需要对象,直接用类名调用
static double convertToDbm(uint16_t rawValue) {
return rawValue / 2.0 - 120.0;
}
static bool isRssiValid(int rssi) {
return rssi >= -130 && rssi <= 0;
}
};
// 使用
double dbm = RssiHelper::convertToDbm(200); // 不用创建对象
五、static 和 const 的组合
cpp
class Config {
public:
static const int MAX_RETRY = 3; // ✅ 静态常量(整型可以类内初始化)
static constexpr double PI = 3.14159; // ✅ C++11 推荐写法
static const QString APP_NAME; // ❌ 非整型必须在类外定义
};
const QString Config::APP_NAME = "K21-Tool";
六、static 在单例模式中的应用
cpp
class Singleton {
private:
Singleton() {} // 私有构造,不让外面 new
public:
static Singleton* getInstance() {
static Singleton instance; // 第一次调用时初始化,之后一直存在
return &instance;
}
};
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
// s1 == s2(同一个对象)
}
这是 C++11 之后最安全的单例写法,利用了静态局部变量的特性。
七、重点总结
| 用法 | 存储位置 | 初始化时机 | 生命周期 | 访问方式 |
|---|---|---|---|---|
| 静态局部变量 | 静态区 | 第一次执行到声明 | 程序结束 | 只在函数内可见 |
| 静态全局变量/函数 | 静态区/代码区 | 程序启动 | 程序结束 | 只在当前文件可见 |
| 静态成员变量 | 静态区 | 类外定义时 | 程序结束 | 所有对象共享 |
| 静态成员函数 | 代码区 | 程序启动 | 程序结束 | 属于类,用类名调用 |