C++学习之六、有效的内存管理
有效的内存管理
在程序中使用动态内存优点:
1. 动态内存可以在不同的对象与函数之间共享。
2. 动态分配的内存空间的大小可以在运行时确定。
预备知识:
int i = 7;
i实在栈上分配的。
int *ptr;
ptr = new int;
指针ptr在栈上,而ptr指向的内存在堆上。
int **handle;
handle = new int*;
*handle = new int;
handle指针在栈上,*handle指针在堆上,*handle指向的内存单元也在堆上。
使用new分配内存时是在堆上分配的,它会返回一个指向分配好的内存块的指针。需要使用delete显示释放。如果忽略了new的返回值,或者指针变量超出了作用域,那么内存就会变成孤立单元,因为你再也无法访问这块内存了。
malloc与new的区别:
new不仅仅是分配内存,而且还调用对象的构造函数。malloc只是预留一块固定大小的内存,它并不关心对象是什么。
free()与delete的区别:
使用free()时,并不会调用对象的析构函数。使用delete会调用对象的析构函数,并且会正确清除该对象。
数组:
|
作为经验:不要使用realloc(),这个很危险,因为用户定义的对象不能很好地尽享按位复制。
删除数组:
使用用于数组的new(new[])分配内存时,就必须用用于数组的delete(delete[])来释放内存。这个delete除了释放相关内存之外,还会自动撤销数组中的对象。
Simple *mySimple = new Simple[4];
//use mySimple
delete[] mySimple;
当然,只有当数组中的元素是纯对象是才会调用析构函数。如果是一个指针数组,则仍然需要单独地删除各个元素,就向单独分配每个元素一样。
Simple **mySimple = new Simple*[4];
for(int i = 0;i<4;i++)
mySimple[i] = new Simple();
//use mySimple
for(int i = 0;i<4;i++)
delete mySimple[i];
delete[] mySimple;
易做图栈数组:
易做图堆数组:
基于堆的易做图数组就想基于堆的一维数组一样,可以通过指针对其进行访问。区别在于,对于N维数组,需要N层指针。
下面代码编译不会通过,
char **board = new char[i][j]; //error
因为基于堆的易做图数组不像基于栈的易做图数组那样工作。为其分配的内存不是连续的,所以此做法是不正确的。正确的做法是:必须建基于堆的数组的一维下标分配一个连续的数组。该数组的每个元素实际上是指向另一个数组的指针,这个数组存储了对应第二维下标的元素。
遗憾的是,编译器不会自动给你分配子数组的内存。这就需要你明确分配了。释放时,delete不会自动删除子数组,也需要手动释放。
// new
char ** myArray = new char*[xSize];
for(int i = 0;i<xSize;i++)
myArray[i] = new char[ySize];
//delete
for(int i = 0;i<xSize;i++)
delete[] myArray[i];
delete[] myArray;
使用指针:
由于指针使用相对容易,所以很容易遭到滥用。因为指针仅仅是一个内存地址,所以在理论上可以手动修改。甚至可以做下面不该做的事:
char *p = 7;
上面代码建立了一个指向内存地址为7的指针,它可能是一个随机垃圾,也可能是应用中其他地方正在使用的一个内存。如果使用了不是用new分配的内存区域,最终会破坏与对象关联的内存,这样程序很容易崩溃。
用*对指针进行解除引用。
用&对变量取地址。
指针类型那个强制转换:
既然指针仅仅是内存地址,所以指针是弱类型的。指向XML文档的指针和指向整数的指针大小也是一样的。通常使用c风格的类型强制转换,编译器可以很容易地把任何类型的指针强制转换成另一种指针类型。
Document *docPtr = getDocument();
char *charPtr = (char*)docPtr;
static类型强制转换的安全性更高一些。编译器会拒绝对指向不同数据类型的指针完成static类型强制转换。
Document *docPtr = getDocument();
char *charPtr = static_cast<char*>(docPtr); //error
如果要强制转换类型的二个指针实际上是指向通过继承相关联的对象,编译器允许完成static_cast类型强制转换。
const指针:见const关键字的说明。
在实际中,很少需要保护指针。如果函数能够改变所传递的指针值,这也无关大碍。其作用仅限于这个函数内部,对于调用者而言,指针仍然指向它原来指向的地址。把指针设置为const,这对于说明指针用途更有意义,其实提供不了多少真正的保护。但是保护指针指向的值则是很常见,从而防止重写共享数据。
数组与指针的对应:
指针和数组之间存在某种重叠。在堆上分配的数组由指向第一个元素的指针引用。基于栈的数组使用数组语法([])来引用。
基于栈的数组的地址其实就是第0个元素的地址,数组名就是指向第0个元素的指针。只是这个指针不能改变。
可以函数传递基于栈或者基于堆的数组。在传递基于栈的数组时,编译器会自动把数组变量退化为指向数组的指针来处理。在传递基于堆的数组时,因为指向数组的指针已经存在,只是简单地按值传递给函数即可。如果函数取数组作为实参,并改变了数组中的值,这个函数实际上是修改了原始数组。,而不是数组的副本。其实这样是因为C++考虑效率问题,而没有采用复制数组,而是采用将其退化为指针来处理,因为复制数组需要花较多的时间,还可能消耗大量的内存空间。
字符串:
C风格的字符串:
千万记住C风格字符串后面还有一个占空间的’\0’;
字符串直接量:与字符串直接量相关联的内存位于内存的只读部分。
char *ptr = “hello”; //字符串直接量赋给变量,ptr指向了这个只读内存
ptr[1] = ‘a’; //不能这样做, 这是个字符串常量,不能修改
安全的做法就是:
const char *ptr = “hello”; //字符串直接量赋给const 变量
ptr[1] = ‘a’;
也可以使用字符串直接量作为基于栈的字符数组的初始值,因为基于栈的变量在任何情况之下都不可能引用其他地方的内存,所以编译器会负责把字符串直接量复制到基于栈的数组内存中。
char stackArray[] = &lsq
补充:软件开发 , C++ ,