『C/C++养成计划』变量的执行顺序动态工厂
创始人
2025-05-29 05:07:10
变量的执行顺序&动态工厂

文章目录

  • 一. 变量执行顺序
  • 二. 虚拟地址空间
  • 三. 动态工厂创建对象
    • 3.1. 为什么类内部的static变量必须在类外定义,但是方法可以直接在类内定义呢?
    • 3.2. static成员变量和static成员方法和main函数的执行顺序
  • 四. 参考文献

一. 变量执行顺序

  • 在C++中,对于 局部变量,当局部变量的生命周期结束时,会自动调用析构函数
  • 但是对于 全局变量和静态变量来说,其生命周期较长,其析构函数将会在什么时机进行调用呢?让我们来探究一下。我们先看下面的一段程序:
# include class Static {
public:Static() {std::cout << "Static constructor\n";}~Static() {std::cout << "Static destructor\n";}
};class Global {
public:Global() {std::cout << "Global constructor\n";}~Global() {std::cout << "Global destructor\n";}
};class Local {
public:Local(std::string var_name) {__var_name = var_name;std::cout << "Local:\"" << __var_name << "\" constructor\n";}~Local() {std::cout << "Local:\"" << __var_name << "\" destructor\n";}private:std::string __var_name;
};static Static static_var; // 静态变量
Global global_var;        // 全局变量void func(Local local) {Local local_in_func("local_in_func");return;
}int main() {std::cout << "first line in main\n";Local local_var_in_main("local_var_in_main");func(local_var_in_main);std::cout << "====================" << std::endl;{Local local_in_bracket("local_in_bracket");}std::cout << "====================" << std::endl;std::cout << "last line in main\n";return 0;
}
  • 执行结果:
Static constructor
Global constructor
first line in main
Local:"local_var_in_main" constructor
Local:"local_in_func" constructor
Local:"local_in_func" destructor
Local:"local_var_in_main" destructor
====================
Local:"local_in_bracket" constructor
Local:"local_in_bracket" destructor
====================
last line in main
Local:"local_var_in_main" destructor
Global destructor
Static destructor
  • 有一个有意思的是local_var_in_main的析构函数被执行了两次,但是其构造函数仅被执行了一次。
  • 原因分析:local_var_in_main 被传递到函数 func 中作为参数,这时候会发生一次拷贝构造,生成一个名为 local 的新对象,当函数执行完毕时,local 对象会被销毁,因此 Local: “local_var_in_main” destructor 会被输出一次。
  • 接着,local_var_in_main 所在的作用域结束,该对象也会被销毁,因此 Local: “local_var_in_main” destructor 会被再次输出一次。
  • C++ 中静态对象和全局对象在程序启动时自动执行构造函数的特性。

二. 虚拟地址空间

  • 虚拟地址空间的大小也由操作系统决定,32位的操作系统虚拟地址空间的大小为 2322^{32}232 字节,也就是 4G,64 位的操作系统虚拟地址空间大小为 2642^{64}264 字节,这是一个非常大的数,感兴趣可以自己计算一下。
    在这里插入图片描述
  • .data段(数据段):在程序编译时就分配好了内存,程序运行期间一直存在。静态变量和全局变量都是存放在这里的。
  • 栈区栈区是程序自动分配和释放的一块连续内存区域,存放局部变量、函数参数等数据。在函数执行时分配栈空间,在函数返回时释放栈空间,因此栈空间的分配和释放是由程序自动完成的。
  • 堆区:堆区是程序手动申请和释放的一块连续内存区域。程序可以使用 new 运算符申请堆空间,使用 delete 运算符释放堆空间。堆空间的分配和释放是由程序员手动完成的。

在这里插入图片描述

三. 动态工厂创建对象

#include 
#include 
#include 
#include 
#include // ------------------------------------------------------ 定义一个动态工厂类模板
template 
class DynamicFactory {
public:// 定义一个别名 CreateObjectFunc,表示创建对象的函数指针using CreateObjectFunc = Base *(*)();// 注册函数,将派生类类型名和创建派生类对象的函数指针关联起来void Regist(const std::string &typeName, CreateObjectFunc func) {creators_[typeName] = func;}// 创建对象函数,根据派生类类型名创建对应的派生类对象,并返回基类指针Base *Create(const std::string &typeName) {auto iter = creators_.find(typeName);// if (iter == creators_.end()) {//     return nullptr;// }// return iter->second();return iter == creators_.end() ? nullptr : iter->second();}// 单例模式,返回唯一的工厂对象static DynamicFactory &Instance() {static DynamicFactory instance;return instance;}private:// 将类型名和创建对象的函数指针关联起来std::map creators_;// 构造函数是私有的,只能通过 Instance() 方法获取对象DynamicFactory() = default;
};// ------------------------------------------------------定义一个动态创建对象的模板类,用于将派生类和基类关联起来
template 
class DynamicCreate {
public:// 构造函数中将派生类类型名和创建派生类对象的函数指针关联起来DynamicCreate() {DynamicFactory::Instance().Regist(typeid(Derived).name(), CreateObject);}// 创建派生类对象的静态方法,返回基类指针static Base *CreateObject() {return new Derived;}// 在全局对象中声明一个静态成员,以便在程序启动时执行 DynamicCreate() 构造函数struct Registor {Registor() {DynamicCreate();}};static Registor registor_;
};// 静态成员变量需要在类外定义
template 
typename DynamicCreate::Registor DynamicCreate::registor_;// ------------------------------------------------------ 定义一个基类
class StrategyBase {
public:virtual ~StrategyBase() = default;virtual void Execute() = 0;
};// 定义一个派生类
class StrategyA : public StrategyBase {
public:void Execute() override {std::cout << "StrategyA::Execute()" << std::endl;}
};// ------------------------------------------------------ 定义一个派生类
class StrategyB : public StrategyBase {
public:void Execute() override {std::cout << "StrategyB::Execute()" << std::endl;}
};// 将类型信息转换为字符串
template 
std::string getTypeName() {return typeid(T).name();
}DynamicCreate dynamicCreateStrategyA; // 使用 DynamicCreate 模板类将 StrategyA 和 StrategyBase 关联起来
DynamicCreate dynamicCreateStrategyB; // 使用 DynamicCreate 模板类将 StrategyB 和 StrategyBase 关联起来int main() {// 使用工厂对象创建 StrategyA 对象std::unique_ptr pStrategy(DynamicFactory::Instance().Create(getTypeName()));if (pStrategy) {pStrategy->Execute();}// 使用工厂对象创建 StrategyB 对象std::unique_ptr pStrategy1(DynamicFactory::Instance().Create(getTypeName()));if (pStrategy1) {pStrategy1->Execute();}return 0;
}

3.1. 为什么类内部的static变量必须在类外定义,但是方法可以直接在类内定义呢?

  • 类内部的 static 成员变量和方法都属于类的作用域,但它们的初始化和存储方式有所不同
  • 对于 static 成员变量,它们需要在程序运行前进行初始化,而且必须保证只有一个实例存在。因此,它们的内存空间需要在全局范围内分配,而不能直接在类内定义,否则会导致重复定义错误。
  • 而对于 static 方法,它们并不需要分配内存空间,因此可以直接在类内定义。在调用时,只需要使用 类名::方法名() 的方式进行调用即可,而不需要通过实例对象来调用。另外,需要注意的是,如果一个 static 成员变量是 const 类型的,那么它可以在类内初始化,因为在这种情况下,编译器会在编译期间对它进行初始化,并将它嵌入到代码中。例如:
  • 内存分配的角度理解 类内部的静态变量只是声明,实际的内存空间并没有被分配,因此需要在类外部定义,以便为其分配实际的内存空间。静态变量存放在数据段中,并且是全局唯一的,因此所有类对象可以共享它。因此,当静态变量被声明为类的成员时,该变量必须在类外部进行定义,以便在程序执行期间分配内存空间,并且可以被所有对象共享使用。而方法可以直接在类内定义,因为它们不需要被共享,它们只是类的成员函数。
class MyClass {
public:static const int kConstValue = 100; // 可以在类内定义并初始化static int sCount; // 必须在类外定义
};int MyClass::sCount = 0; // 在类外定义并初始化int main() {std::cout << MyClass::kConstValue << std::endl;std::cout << MyClass::sCount << std::endl;return 0;
}

3.2. static成员变量和static成员方法和main函数的执行顺序

  • static 成员变量的执行顺序
  • 静态成员变量需要在类外进行定义,并且需要初始化。一般情况下,静态成员变量的初始化会在程序启动时进行,也就是在 main 函数之前。如果某个静态成员变量没有被显式初始化,那么它将被默认初始化为 0。
  • static 成员方法的执行顺序
  • 静态成员方法属于类的成员方法,它可以不依赖于任何实例对象而被调用。因此,在程序运行过程中,static 成员方法可以随时被调用,它不需要等到 main 函数执行之后才能被调用
  • main 函数的执行顺序
  • main 函数是程序的入口,它需要在程序启动时首先被执行。在 main 函数被执行之前,程序会对全局变量和静态变量进行初始化,并分配它们所需的内存空间。然后,程序会调用 main 函数,并在 main 函数执行完成后退出程序。
  • 总的来说,static 成员变量的初始化会在程序启动时进行,main 函数需要在程序启动时首先被执行,而 static 成员方法可以随时被调用。

四. 参考文献

  • https://subingwen.cn/linux/file-descriptor/

相关内容

热门资讯

山东单独二胎生育证最新或202...   随着5月30日“单独两孩”政策在山东省落地,不少符合政策的夫妇已开始着急办理生育证了。6月3日,...
最新或2023(历届)北京市生...   北京生育保险政策调整之后,机关、事业单位和非京籍职工等都将可享受生育保险了,产假期间的工资标准不...
广东佛山市职工生育保险待遇申领...   各位佛山的职工,你们知道职工生育保险待遇的申领办法吗?以下包括办理条件、办理流程等相关信息。  ...
最新或2023(历届)广州生育...   社保卡看病报销比例 广州生育保险报销比例   2月20日上午10点,国务院新闻办公室举行新闻发布...
北京桥下空间大变样 【#北京桥下空间大变样#】#管理一座桥会涉及多少部门#北京的桥,如一道道长虹飞架,串起城市繁华。然而...