C++感受10-Hello Object 生死版•下

搞懂以下三个重要知识点:

  1. 对象生命周期
  2. 对象内存模型
  3. 对象的可见性

 

ff12-HelloObject-生死版-下

1. 生命周期

只要是数据,就需要占用内存空间。程序将内存分成多个区,其中最重要的是栈区和堆区。放在栈区的数据,称为栈数据或栈对象;放在堆区的数据,称为堆数据或堆对象。二者最大的不同,在于数据的生命周期不同。

1.1 栈数据的生命周期

在栈区内存中的数据,它们什么时候死,是由所在代码结构决定的。最典型的,C/C++中,函数、流程结构(比如之前学习的 if/esle、while)等,所拥有的一对花括号,即形成一个语句块(也称代码块)。栈数据会在所在的最内层代码块结束出“死亡”;且遵循同一层级的栈数据,“先定义的后死,后定义的先死” 的原则。

  • 例1:
int main()
{
    Object o;
    return 0;    
}

 本例,o的生命周期始于定义,终于 main () 函数体的 } 之前 (即,在return 0 之后)。

  • 例2:
int main()
{
    Object o1;
    Object o2; 
}

01和02都将在函数体结束处死亡,但o1先定义故后死,o2后定义故先死。

  • 例3:
int main()
{  
     {
         Object o1;
     }

    Object o2;    
}

本例,o1 拥有独立的代码块,代码块处于 o2定义之前,因此 o1 将先生也先死。

  • 栈对象生命周期测试完整代码:
#include <iostream>

using namespace std;

struct Object1 
{ 
   Object1() { cout << "哥生" << endl; }
   ~Object1() { cout << "哥死" << endl; }
};

struct Object2
{ 
   Object2() { cout << "弟生" << endl; }
   ~Object2() { cout << "弟死" << endl; }
};

void test1()
{
   Object1 o1; 
   Object2 o2;
}

void test2()
{
  {  Object1 o1; }
   Object2 o2;
}

int main()
{
    test1();   
    cout << "~~~~~\n";
    test2();   
}

1.2 堆数据的生命周期

1.2.1 创建堆数据的语法

类型名 * 对象名 = new 类型名();

当不初始化初值,或类型(结构)的构造函数不需要入参时,() 可省略。
另外,很多情况下,上面的两处类型名完全相同,此时可使用 C++11 标准,将第一个类型名用 auto 代替,如:

auto* o = new Object(); 
或:
auto* o = new Object; 
内置类型数据,也可在堆中创建,并且可以 在上述语法中的 () 填写初始值 ,比如:
int* i = new int(12); // 创建 名为 i 的 int 类型堆变量,且初始值为 12 

更多例子如:

int* i = new int; 
int* age = new int(12); // 带入参构造 
char* c = new char(‘A’); 
double* money = new double; 
bool* sheLoveMe = new bool(false); 
auto* year = new int (2024); 

1.2.2 杀死堆数据的语法

一个堆数据创建出来,且分配了堆内存后,如果不释放,它将一直存活(直到程序退出)。“杀死”常规堆数据的语法是:

delete 对象名;

如(结构类型数据):

auto* o = new Object;

// 使用 o 

delete o;

 或(内置类型数据):

int i = new i(12);

cout << *i << endl; // 将输出 12 , *i 解释见后

delete i; // 杀死!

1.2.3 堆数据的生命周期

从 new 创建并获得堆内存开始,到被代码主动 delete 之间,堆数据都是“活”的(即一直在占用堆区的内存);下面的例子中,函数 foo()每被调用一次,都将在堆内存中占用一字节内存,并且在每一次调用结束后都未释放该内存,从而造成 “内存泄漏” 。

void foo()
{
   bool* b = new bool (false);
   std::cout << b << std::endl;
}

int main()
{
   foo();
}

2. 内存模型

 

2.1 栈对象内存模型

栈对象内存模型

 代码:

void foo()
{
   int age = 12;
}

foo() 被调用时,将创建一个类型为 int 的栈数据,该数据初始值为 12。此值存放在栈区;在特定系统下, int 占用 4个字节的内存。

一个字节的内存,类似于一间不可再分的房间;每一字节的内存都拥有一个地址,类似于房间的门牌号。图中 12 所存放的四个字节的内存地址为 8001~8004 。

实际内存地址数值通常比较大,此处为形像易理解,故意简化为类似门牌号码;另外,内存的存入次序,也不一定是如图中的从小到大排列。

通常称第一个字节的地址,为数据的存储地址,即图中 12 的存储地址为 8001。

2.2 堆对象内存模型

代码:

void foo()
{
    int* age = new int(12);
}

执行时,将首先在栈内存中占用固定大小的内存(64位系统下固定为8字节,32位系统下固定为4字节);同时在堆中申请并获得(至少)可存放一个 int 的堆内存(特定系统下为 4 字节),并将初始值 12 存放于其中,再将其地址存放于栈中分配的内存里,本例为 1126540。

此过程,至少涉及两个内存地址,一个在栈内存,即图中的 8001,一个在堆内存,即图中的 1126540。

当一块内存中存放的是另一块内存的地址,我们就称前一块内存指向后一块内存。在本例中,即地址为 8001 的栈内存,指向地址为 1126540 的堆内存。

此时,也可将两个地址视为两个数据,则称前一数据为指针数据或指针变量,即,示例中的 age 是一个指针变量,它指向的内容,则使用 “*age” 表示。

由于 age (即指针变量) 存储在栈中,因此也遵循栈区数据的规矩:会依据所在代码块的结束而自动回收。即:图示中的地址为 8001 的八字节栈内存,会在 foo() 函数的结束时,自动退还,而因为没有 delete ,未能归还的内存,是存放 12 的,四个字节的堆内存。

想要释放指针变量所指向的内存,如前所述,语法 delete 指针变量。示例如下:

void foo()
{
    int* age = new int(12);
    
    delete age; // 正确写法
    delete *age; // 错误写法
}

3. 可见区域

通过四个例子代码加以了解数据的可见区域。

  • 例1:
int test1()
{
   int i;
   int i;   // 错误
}

说明:同一生命周期内,存在完全同名的两个数据,非法。 

  • 例2:
int test2()
{
   int i;

   {
      i = 999; // 外部的i
      int i;   
      i = 888;  // 内部的i
   }
}

说明:嵌套(包括多级嵌套)的内部代码块,可以看见及访问外部代码块之前定义的数据(例中:i = 999),除非内部代码块出现同名数据,此时看见的内部的同名数据(例中:i = 888)。 

  • 例3:
int test3()
{
   {
      int i;
   }

   i = 999; // 错误
}

说明:外部的代码块,看不到哪位位于其前定义的内部代码块中定义的数据。 

  • 例4:
int test4()
{
   {
      auto* i = new int;
   }

   delete i; // 错误
}

说明:外部的代码块,看不到哪位位于其前定义的内部代码块中定义的数据,哪怕其前定义的内部代码块中定义的数据仍然活着…… 

 

  • 22
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南郁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值