欢迎关注本公众号,专注面试题拆解
分享一套视频课程《C++百万并发服务器开发》,有需要的加我微信获取:fb964919126
面试题:静态变量的构造和析构时机
静态变量有3种:全局静态变量、静态局部变量、类中静态成员变量
构造时机:
01
全局静态变量的构造
class GlobalVar {
static int globalCount; // 全局静态变量
static std::string globalStr; // 全局静态对象
};
1、全局静态变量的构造发生在程序的主函数main()执行之前。
2、这些变量的构造顺序遵循它们在源文件中的声明顺序。即,如果一个源文件中有多个全局静态变量,那么它们将按照它们在文件中出现的顺序被构造。
class ConstructionOrder {
// 按声明顺序构造
static std::string str1; // 先构造
static std::string str2; // 后构造
class Init {
public:
Init() {
// str1已经构造完成
str1 = "Hello";
// str2已经构造完成
str2 = "World";
}
};
static Init initializer;
};
3、如果不同的源文件中定义了全局静态变量,那么这些变量的构造顺序是未定义的,这意味着编译器可能以任何顺序构造它们。
// file1.cpp
static std::string global1("First");
// file2.cpp
static std::string global2("Second");
// 不同文件中的全局变量构造顺序是未定义的!
02
静态局部变量的构造
void localStatic() {
static int localCount = 0; // 静态局部变量
static std::string localStr; // 静态局部对象
}
1、静态局部变量的构造发生在第一次控制流到达其定义处时。例如,如果一个静态局部变量位于函数内部,那么它将在该函数首次被调用时构造。
// 1. 基本示例
void example() {
static int count = 0; // 第一次调用时初始化
static std::string str = "hello"; // 第一次调用时构造
count++;
std::cout << count << std::endl;
}
// 2. 条件分支中的静态变量
void conditionalStatic(bool flag) {
if (flag) {
static int value = 42; // 只有当flag为true且是第一次执行到这里时才初始化
std::cout << value << std::endl;
}
}
2、一旦构造,静态局部变量在整个程序的生命周期内都存在,除非显式地被销毁或者程序终止。
3、C++11保证了静态局部变量初始化的线程安全性。
03
类中静态成员变量
class MyClass {
static int classCount; // 类静态成员
static std::string classStr; // 类静态对象
};
类的静态成员变量既不属于全局变量也不属于局部变量,而是一种特殊的静态存储持续时间的变量。
类的静态成员变量的作用域是类的作用域。这意味着它可以在类的所有实例之间共享,并且可以通过类名访问,而不需要创建类的实例。
类的静态成员变量的生命周期与整个程序的生命周期相同。它们在程序启动时被初始化,在程序结束时被销毁。
// 1. 类内声明
class StaticMemberExample {
private:
static int count; // 声明
static std::string str; // 声明
static const int constValue = 100; // const整型可以类内初始化
static constexpr int constExprValue = 200; // constexpr可以类内初始化
public:
StaticMemberExample() {
count++; // 可以在构造函数中使用
}
};
// 2. 类外定义(.cpp文件中)
int StaticMemberExample::count = 0; // 必须在类外定义
std::string StaticMemberExample::str = "hello"; // 必须在类外定义
const整型和constexpr类型的可以在类内进行初始化,其他的需要在类外进行定义。
关于构造顺序
在同一源文件中:
静态成员变量的初始化顺序遵循它们在源文件中的定义顺序。
即使这些变量在头文件中声明了多次,它们的初始化顺序仍然由它们在源文件中的定义顺序决定。
// 1. 不同静态成员的构造顺序
class ConstructionOrder {
private:
static std::string str1; // 在类外定义的顺序决定构造顺序
static std::string str2;
public:
// 初始化顺序依赖于定义顺序,而不是声明顺序
static void init() {
std::cout << str1 << str2 << std::endl;
}
};
// 在.cpp文件中的定义顺序决定构造顺序
std::string ConstructionOrder::str1 = "Hello"; // 先构造
std::string ConstructionOrder::str2 = "World"; // 后构造
在不同源文件中:
不同源文件中定义的静态成员变量的初始化顺序是未定义的。
编译器可以以任何顺序初始化这些变量,因此如果静态成员变量之间有依赖关系,可能会导致未定义行为。
初始化依赖问题
如果静态成员变量之间有依赖关系,即一个静态成员变量的构造函数中使用了另一个静态成员变量,那么需要特别注意定义顺序。
class InitDependency {
private:
static std::string str1;
static std::string str2;
// 1. 危险的初始化依赖
static std::string str3; // 依赖于str1和str2
};
// 初始化顺序很重要
std::string InitDependency::str1 = "Hello";
std::string InitDependency::str2 = "World";
std::string InitDependency::str3 = InitDependency::str1 + InitDependency::str2;
这段代码str3依赖于str1和str2,那么要确保str1和str2的初始化在str3之前。
更合理的做法是:
static std::string& getStr3() {
static std::string s = str1 + str2; // 延迟初始化
return s;
}
如果静态成员变量定义在不同的源文件中,初始化顺序是未定义的。因此,应尽量避免跨文件的静态成员变量之间的依赖关系,或者使用函数静态局部变量等技术手段来确保正确的初始化顺序。
析构时机
04
全局静态变量的析构:
全局静态变量的析构发生在程序正常终止时,即在main()函数返回后或者是调用了exit()函数后。
析构顺序是构造顺序的逆序。这意味着最后构造的全局静态变量最先被析构。
如果不同的源文件中定义了全局静态变量,那么这些变量的析构顺序同样遵循它们的构造顺序,即每个文件内部的变量按照逆序被析构,但不同文件之间的顺序仍然是未定义的。
05
静态局部变量的析构:
静态局部变量的析构也是在程序正常终止时,即在main()函数返回后或者是调用了exit()函数后。
和全局静态变量一样,静态局部变量也是按照构造的逆序被析构。
06
类静态成员变量的析构
类的静态成员变量的析构函数在程序正常终止时被调用,即在 main() 函数返回后或者是调用了 exit() 函数后。
析构顺序是构造顺序的逆序。也就是说,最后构造的静态成员变量最先被析构,最先构造的静态成员变量最后被析构。
如果不同的源文件中定义了静态成员变量,那么这些变量的析构顺序同样是未定义的。这意味着编译器可能以任何顺序析构它们。
总结
构造时机:
程序启动前
第一次使用前(局部静态)
按声明顺序(同一编译单元)
未定义顺序(不同编译单元)
析构时机:
程序结束时
与构造顺序相反
main函数返回后
CppPlayer
关注,回复【电子书】珍藏CPP电子书资料赠送
精彩文章合集
专题推荐