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

文章目录

  • 一. 变量执行顺序
  • 二. 虚拟地址空间
  • 三. 动态工厂创建对象
    • 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/

相关内容

热门资讯

最新或2023(历届)关于长城... 导语:下面分享一些关于长城的手抄报资料,希望对大家有所帮助!  【长城手抄报资料】  毛泽东诗词:不...
最新或2023(历届)小学生关... 小学生关于法制手抄报的图片模板  小学生关于法制手抄报图片1  小学生关于法制手抄报图片2  小学生...
mac删除文件夹它又自动恢复 ... 我们在使用电脑的过程中,需要不断地去清理电脑中不用或废弃的文件,从而保证...
最新或2023(历届)小学生关... 小学生关于法制手抄报的图片参考  小学生关于法制手抄报参考图片(1)  小学生关于法制手抄报参考图片...
最新或2023(历届)简单的法... 简单的法制手抄报的图片模板  简单的法制手抄报图片1  简单的法制手抄报图片2  简单的法制手抄报图...
图形视图框架QGraphics... QGraphicsView(图形视图) QGraphicsView提供了...
大数据学习(2) 大数据学习(2)0 数据仓库0.0 数据仓库基本概念0.1 数据仓库主要...
最新或2023(历届)小学生关... 小学生关于法制手抄报的图片模板  小学生关于法制手抄报图片1  小学生关于法制手抄报图片2  小学生...
【spring】javaCon... 目录一、xml形式二、javaConfig形式三、源码分析 一、xml形式 1.spring容器为...
最新或2023(历届)有关法制... 有关法制手抄报的图片模板  有关法制手抄报图片1  有关法制手抄报图片2  有关法制手抄报图片3  ...
最新或2023(历届)有关法制... 有关法制手抄报的图片参考  有关法制手抄报参考图片(1)  有关法制手抄报参考图片(2)  有关法制...
最新或2023(历届)初中生法... 初中生法制手抄报的图片参考  初中生法制手抄报参考图片(1)  初中生法制手抄报参考图片(2)  初...
最新或2023(历届)初中生法... 初中生法制手抄报的图片模板  初中生法制手抄报图片1  初中生法制手抄报图片2  初中生法制手抄报图...
2022湖北省赛 L 裸线段树 有人不会裸线段树有人没有pushdown调了两小时裸线段树早该414了L (codeforces.c...
最新或2023(历届)初中生法... 初中生法制手抄报的图片模板  初中生法制手抄报图片1  初中生法制手抄报图片2  初中生法制手抄报图...
Chapter2.3:线性表的... 该系列属于计算机基础系列中的《数据结构基础》子系列,参考书《数据结构考研复习指导》(王...
最新或2023(历届)初中生法... 初中生法制手抄报的图片参考  初中生法制手抄报参考图片(1)  初中生法制手抄报参考图片(2)  初...
最新或2023(历届)关于简单... 关于简单的法制教育手抄报的图片模板  关于简单的法制教育手抄报图片1  关于简单的法制教育手抄报图片...
最新或2023(历届)关于简单... 关于简单的法制教育手抄报的图片参考  关于简单的法制教育手抄报参考图片(1)  关于简单的法制教育手...
最新或2023(历届)中学生法... 中学生法制教育手抄报的图片模板  中学生法制教育手抄报图片1  中学生法制教育手抄报图片2  中学生...