C++之对象的生离死别
对象的生命历程
申请内存 -> 初始化列表 -> 构造函数 -> 参与计算 -> 析构函数 -> 释放内存
以上就是一个对象从创建开始到释放内存结束的全部历程。根据上面的描述很自然的我们就能想到以下几个问题:
- 申请内存?向谁申请内存,申请哪里的内存,申请的内存会有多大,怎么知道申请的内存够不够用?
- 初始化列表?初始化上面列表,哪些需要初始化,哪些不需要初始化,怎么判断呢?
- 构造函数?必须要有构造函数吗,没有行不行,只能有一个吗,构造函数和普通函数有什么区别?
- 参与计算?怎么参与计算,你现在在哪里,应该计算的地方又在哪里?你怎么和它进行计算?
- 析构函数?一定要有吗?没有行不行,不析构会有什么后果?
- 释放内存?释放内存给谁呢,它怎么知道我释放了,我就不释放会怎么样?
为了解决上述问题,我们需要先了解一下内存分区
内存分区
根据存储数据的类型,系统将不同类型的数据存储在不同的区域,作为 C++ 开发者,必须对内存的分区以及使用了然于心。
一般情况下,根据内存用途将内存划分为五个区:
内存区 | 用途解释 |
---|---|
栈区 | 存储函数的参数、局部变量等 |
堆区 | 由程序员分配、释放内存(跟数据结构中的堆毫无关系) |
全局区 | 存储全局变量和静态变量(也就做静态区 .bss 段和 .data 段) |
常量区 | 存储常量 (存放常量 .data 段) |
代码区 | 存储逻辑代码的二进制(存放代码 .text 段) |
从操作系统的本身来讲,以上存储区在内存中的分布是如下形式(从低地址到高地址):.text 段 --> .data 段 --> .bss 段 --> 堆 --> unused --> 栈 --> env
#include <iostream>
using namespace std;
class Plan
{
int wingCount;
public:
Plan(){wingCount = 0;}
~Plan(){}
int getWinCount(){return wingCount;}
};
int main()
{
//栈上实例化
Plan p1;
Plan p2;
Plan p3;
p1.getWinCount();
p2.getWinCount();
p3.getWinCount();
cout<<&p1<<endl;
cout<<&p2<<endl;
cout<<&p3<<endl;
//堆上实例化
Plan *q1 = new Plan();
Plan *q2 = new Plan();
Plan *q3 = new Plan();
cout<<&q1<<endl;
cout<<&q2<<endl;
cout<<&q3<<endl;
delete q1;
delete q2;
delete q3;
return 0;
}
ok,现在我们已经知道了内存分区,虽然知道的不多,但是已经有了回答问题的基础知识。下面我们来逐一解答一下吧。
申请内存?向谁申请内存,申请哪里的内存,申请的内存会有多大,怎么知道申请的内存够不够用?
- 向谁申请内存,很自然的想到,应该是向操作系统申请内存。总不能他妈的直接向内存条说:条哥,来点存子吧
- 申请哪里的内存,申请的内存是位于内存的堆区。
- 申请的内存大小取决于类实例定义的属性数量、类型以及数据量。可以通过查看类实例的内存占用,来确定申请的内存是否够用,其实这个只有在你内存不够用的时候你才知道,要是你申请的内存大,建议不要使用栈区实例化,用堆区,用的都说好。
初始化列表?初始化上面列表,哪些需要初始化,哪些不需要初始化,怎么判断呢?
- 初始化列表是一种在类实例化时将属性设置为初始值的方法。
- 需要初始化的属性是那些在类声明时定义的,但是未赋初始值的属性,它们必须在初始化列表中被赋值。
- 不需要初始化的属性是那些在类声明时定义时就赋了初始值的属性,它们不需要在初始化列表中赋值。
构造函数?必须要有构造函数吗,没有行不行,只能有一个吗,构造函数和普通函数有什么区别?
- 在C++中,可以使用构造函数来实例化类。构造函数是一种特殊的成员函数,它在创建新对象时被自动调用,主要用来初始化对象。
- 即使程序没定义任何成员,编译器也会插入以上的函数,也就是说当用户没有定义构造函数和析构函数时编译器会自动添加默认的构造函数和析构函数。懂我意思吧,这种东西你写不写他都有,与其让编译器写不如自己写,还方便一些
- 可以有多个,也就是重载,对应不同的情况
- 构造函数与普通函数的区别在于:
(1)构造函数在类对象被创建时自动调用,而普通函数需要手动调用。
(2)构造函数没有返回值,而普通函数可以有返回值。
(3)构造函数的函数名必须与类名相同,而普通函数的函数名可以与类名不同。
参与计算?怎么参与计算,你现在在哪里,应该计算的地方又在哪里?你怎么和它进行计算?
当类实例化的时候,它参与计算的地方是在类定义的位置,这是编译阶段发生的,在编译阶段,编译器会根据类的定义,生成函数的代码,以及一些变量的初始化,以及对类的对象的实例化,然后才能进行真正的计算。在运行阶段,我们才能真正的与这些实例进行计算和操作,这时候,我们可以通过调用实例的方法,来完成我们想要的结果。
好吧,以上是我乱说的,这个问题我也不会,有无董哥赐教
析构函数?一定要有吗?没有行不行,不析构会有什么后果?
这个的答案和上面那个构造函数的答案差不多
释放内存?释放内存给谁呢,它怎么知道我释放了,我就不释放会怎么样?
C++ 中类实例化之后,释放内存的过程是自动进行的,并由 C++ 的自动内存管理机制(比如析构函数)来完成。
当类实例化后,占用的内存空间可以被释放,以便被后续的程序使用,如果不释放,那么这部分内存将永久被占用,导致程序运行时内存不足,从而影响程序的正常执行。不过没关系,就算你不释放,等你这个程序的进程结束,依然尘归尘,土归土