关于Complex类和String类的定义方式,请参见:
C++学习4:详解不带指针的类(侯捷Complex类为例)
C++学习5:详解带指针的类(侯捷String类为例)
关于内存相关内容如下:
代码部分:
Complex c1(4,5);//所有花括号之外的,存在栈中
{
Complex c2(1,2); //存在栈中
static Complex c3(1,2); //存在栈中
Complex *p = new Complex(3) //存在堆中
delete p;
}
1 基础部分
1.1 对象类型及生命期
1、c2也可以称为自动对象(auto object),因为离开作用域会被自动清理,析构函数会被自动调用。
2、那么,c3对应的静态对象(static object),就是离开作用域不会被清理的对象,直到程序结束。
3、还有一种如c1称为全局对象(global object),在main结束后才会消失。
4、p指向的内存在堆区,遇到delete才会结束其生命期,释放内存空间。
1.2 栈 Stack
是作用域上的一块内存空间,函数本体内声明的对象都是在栈中。如c1、c2、c3,离开作用域后自动消失。
这部分相对简单,也无需过多操作,这里不赘述了。
1.3 堆 Heap
是操作系统上的一块全局的内存空间,通过new动态取得的都在堆中。如p,需要手动delete掉,如果没有delete,会造成内存泄漏(离开作用域后,这块内存依然存在,没有将其还给系统,且无法对其进行控制。非常危险,一定要极力避免)。
1.3.1 new的底层含义
new一个东西的过程为:先分配内存空间(memory),再转型,最后进行构造。
Complex* pc = new Complex(1,2);
编译器会将其转化为类似下面的语句:
Complex* pc
void* mem = operator new( sizeof(Complex) ); //分配内存
pc = static_cast<Complex*>(mem); //转型
pc->Complex::Complex(1,2); //构造函数
1、在分配内存时,operator new是c++中一个特殊的带空格的函数,内部调用了malloc(n)函数。这步过后,pc会指向一个具有两个连续“double”类型大小的一段内存空间。
2、static_cast是类型转换函数,这一步在这里可以不必详究。
3、类型名(::后的Complex)和类名(::前的Complex)相同,可见这是个构造函数。pc指针指向这个构造函数,就是把这段内存空间内的值初始化为构造的值。由于成员函数都有一个隐藏的this指针指向调用他的对象,这一行代码应该用如下方式去理解
pc->Complex::Complex(/*this,*/ 1, 2);//这里的this就是pc,返回pc这根指针
经过以上三步,new的操作就得以完成。
1.3.2 delete的底层含义
delete一个东西的过程为:先调用析构函数,后释放内存(memory)。
//String* ps = new String(1,2);
delete ps;
编译器会将其转化为类似下面的语句:
String::~String(ps); //析构函数
operator delete(ps); //释放内存
1、调用析构函数,是为了把指针指向的动态分配的那部分内存先杀掉,至于字符串本身,也就是ps只是一根指针而已。
2、释放内存用了operator delete函数,这也是c++中一个特殊的带空格的函数,内部调用了free(ps)函数。这里把字符串本身,也就是ps这根指针杀掉。
1.4 动态分配所得的内存块(VC中)
1.4.1 分配正常对象
new一个Complex并不是简单的获得两个double的8个字节,new一个String并不是简单的获得一个指针的4个字节:
Complex* pc = new Complex(1,2);
String* ps = new String("hello");
如图:
亮绿色的是真正占有的位置
砖红色是cookie是必要的,用于计入整个类型的长度,析构时确定是否回收。00000040就是64的16进制数,给出去了则末尾数值为1。
灰色的部分是调试(Debug)模式中所必需的,Release模式不需要。
由于占有的空间必须是16的倍数,为了让最后4个bit都为0,让16进制最后一位保证为0,绿色的部分是补位,补到离52最近的64,则补了3块;补到离12最近的16,则补上1块。
1、Debug模式下:new一个Complex占64个字节:
2、Release模式下:new一个Complex占16个字节。
3、Debug模式下:new一个String占48字节。
4、Release模式下:new一个Complex占16个字节。
心中自有丘壑!!!
1.4.2 分配数组对象
new[]和delete[]是对应的,一定要互相搭配好。这两个称为array new和array delete
Complex* pc = new Complex[3];
String* ps = new String[3];
如图:
灰色的是真正占有的位置
白色是cookie是必要的,不仅首尾计入整个类型的长度,还用一个整数记录数组的长度。
橙黄色的部分是调试(Debug)模式中所必需的,Release模式不需要。
绿色的部分是补位。
如果new[]不搭配delete[],会发生内存泄漏:
String s1 = new String[3];
delete[] s1; //正确搭配:唤起三次析构函数
//delete s1 //错误搭配:唤起一次析构函数
正确搭配:唤起三次析构函数,正确告诉了编译器删除掉三个。
错误搭配:唤起一次析构函数,少了两个,内存泄露了后两个位置。