面试题:静态变量的构造和析构时机

旅行   2024-11-04 08:13   广东  

欢迎关注本公众号,专注面试题拆解

分享一套视频课程《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.cppstatic std::string global1("First");
// file2.cppstatic 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电子书资料赠送

精彩文章合集

专题推荐

【专辑】计算机网络真题拆解
【专辑】大厂最新真题
【专辑】C/C++面试真题拆解

CppPlayer
一个专注面试题拆解的公众号
 最新文章