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