为什么要删除数组占用的内存?
我们先来看下面的一段代码:
const int INIT_CAPACITY = 1000000;
class Demo {
public:
Demo(); // constructor
string at(int i);
private:
string *bigArray;
};
Demo::Demo()
{
bigArray = new string[INIT_CAPACITY];
for (int i=0;i<INIT_CAPACITY;i++) {
bigArray[i] = "Lalalalalalalalala! ";
}
}
string Demo::at(int i)
{
return bigArray[i];
}
我们现在来看看我们的构造函数,每次调用我们的构造函数,我们需要使用多少内存呢?
首先这是一个字符串数组,每个数组中有20个char字符,加上字符串的开销30B,所以应该这样计算:
100000 x (20+30)B = 50MB
假若我们执行下面的main函数:
int main()
{
for (int i=0;i<10000;i++){
Demo demo; //我们这时候准备实例化10000个这样的对象
cout << i << ": "
<< demo.at(1234)
<< endl;
}
return 0;
}
在执行40次或者40次以后,我们的内存占用将达到甚至超过2GB!!!而我们一般的电脑内存也不过比这大一点点而已。。。
动态数组
为了说明动态数组为什么那么有用或者说多必要,让我们回顾一下我们上个专栏学过的一个STL的容器:vector!C++抽象编程——STL(4)——vector类
我们可以看看这个vector有什么特点:
1. 它能储存int类型的数据(当然,作为容器它可以存放的任意类型的数据,我们只要在尖括号里包含对应的数据类型就好了)
2. 它有很多可用而且方便的函数,比如size,empty,insert。
3. 我们可以任意添加我们想要添加元素的个数(数组是一定要有固定长度的)
4. 它能自动回收内存。
寄居蟹原理(Hermit Crabs)
寄居蟹是个非常有趣的动物,它们生活在在海底发现的空的贝壳里,寄居蟹常常吃掉贝壳等软体动物,把人家的壳占为己有,这就是寄居蟹的名字的由来。这是他们的生活方式:
1. 自然生长,直达它自身的壳不足以容纳它自身
2. 寻找下一个更大的贝壳
3. 将它的所有东西搬到新的贝壳里
4. 将旧的贝壳丢弃在海底
5. 更新它们的居住地址
6. 更新它的容量
现在,我们来模拟这个过程,对数组空间已满进行处理。当我们的数组中耗尽空间时,我们要分配一个比我们旧数组大的新数组,这样我们可以存储新的数据并且数据还在不断增长。这些“可生长的数组”,我们采取五个步骤实现。在这之前我们要先确定,如果我们要扩展数组,我们需要多少新的内存?答案是,旧数组的两倍。这是最高效的做法
我们在C++算法学习——经典的抽象设计——buffer(3)中首次用到了expand函数:
void EditorBuffer::expandCapacity(){
char * oldArray = array;
capacity *= 2;
array = new char[capacity];
for(int i = 0; i < length; i++){
array[i] = oldArray[i]; //复制
}
delete[] oldArray;
}
它反映了动态数组扩容的5步曲:
- 创建一个新的大小的数组(通常是旧数组大小的两倍)
- 将旧数组元素复制到新数组
- 删除旧数组(了解这里发生了什么的是关键!)
- 将旧数组指向旧数组的指针变量指向新数组(它是一个指针!)
- 更新数组的容量变量
追踪vector的扩容过程
这里还有问题,那就是我们什么时候需要扩容?(废话,当然是数组满了的时候) 那么怎么知道它什么时候满了呢?那就需要我们自己去追踪了。
假设我们的Vector的元素指针指向如下图的内存。 capacity= 5,count = 5(已满)。
要扩大容量,我们遵守我们的5步规则(黑体部分):
1. 申请一个可容纳10个元素的内存空间。假设为int型
2. 这里有个问题,我们这个内存空间是可选的吗?如果是,那么我们当然是最好紧跟着旧数组这样:
但是,这是不可选的,因为你申请的是容量为10的空间,而且是未使用过的,而上图前五个,你是已经在使用了。
3. 所以我们的内存空间是系统给我们的。
4. 申请后用我们定义的数组指针指向我们的这段内存。那么就是这句代码:
int *newElements = new int[capacity*2];
5. 复制旧数组
for(int i = 0; i < count; i++){
newElement[i] = element[i];
}
6. 删除原始的数组(意味着你不再能合法访问那段内存了)
delete []element;
7.改变指针指向
element = newElement;
下一篇,我们就来实现一下STL中的vector容器的基本操作!