分类: C/C++
2012-03-19 21:42:30
STL 中 vector 比较神奇的地方,就是它会自动的扩展自己的容量。而这些是在 C/C++ 的低级数据结构——数组里面是办不到的。
合适的扩张:
譬如,在 C++ 中,有如下的代码:
const int kSize = 10;
int ar[kSize];
数组 ar 的大小是在编译期间完成,它的内容是存在内存中的栈里面。顺便提一下,在 c 与 c++ 中,由编译器分配的内存是分配在栈( Stack )里面,而由程序员分配管理的内存是在堆( heap )里面。
数组就是相同类型的一种集合的其中一种表现形式。它具有快速,简单的特点。但是想进一步的扩展是比较困难的。譬如我想把上面的数组的大小改为 11 ,那明显是很难办到的。 STL 中的 vector 可以帮你解决这个问题。
譬如,在 C++ 中的下面的代码:
const int kSize = 10;
vector
for (int i = 0; i < kSize; ++i) {
iv.push_back(i);
}
那么容器 iv 中有是kSize个元素,同时,这个时候,我想在这个容器加进去一个元素,是非常方便的。譬如,我想加一个 int a = 100 的数进来, iv.push_back(a); 就行了。
是的,上面的代码确实是比较好,比数组灵活很多。但绝对不是最好的。这涉及到 vector 的内存分配机制。
先看 vector 的几个成员函数:
size ():这个函数是获得 vector 容器有多少个元素。
capacity ():这个函数获得 vector 容器还能在内存中可以容纳多少元素。(不会导致内存重新分配,下面会讲解这个)。
resize () : 强行把 vector 的元素个数扩展或者是减少到所指定的元素。
reserve ():把容器的容量置为所指定的元素。
vector 的内存分配机制:当向其插进元素,而超过它的容量的时候,它会以一种哲学的方式配置自己的容量。
什么是哲学的方式呢?按照上面的代码,当你想 iv 容器插入一个元素的时候,它可能分配 1 个大小的内存,也可能分配 2 个大小的内存,也可能分配 3 个大小的内存……在 GNU 的 g++ 中,它会以原来 2 倍的内存大小增加(请参考《 STL 源码剖析》)
同时,会进行一次的内存“搬移”,譬如假设上面已经有 5 个元素,他的容量也为 5 ,再向 vector 插入一个元素,这样会发生以下的动作:
1、 它必须重新分配适当的新的内存空间。
2、 把原来的数据“ copy ”过到新的空间,再插入一个元素。
3、 销毁原来的数据。
这是一个非常耗时的过程。
然而这个问题是怎么解决,不至于有这么多好耗时的动作呢? reserve ()成员函数。把上面的代码改为:
const int kSize = 10;
vector
iv.reserve(kSize);
for (int i = 0; i < kSize; ++i) {
iv.push_back(i);
}
只是在 vector 在分配内存的机制上面,预先保留,就可以减少:
1、 内存重新分配。
2、 内存“数据搬移”
3、 销毁原来的数据
三个动作。
合适的收缩:
问题的引进:现在容器 vector 存贮着 100000 人的数据,而我们只是根据某种质量,把关注点放在前十位。譬如:在这些人中,选出 10 个是长得最高的(只是需要简单的 sort 就行),其他的 99990 人都不关心,所以想从内存中删除。
代码 :
class Person {
……
}
vector
……
在删除 vector 的元素,一般采用 erase-remove 的方法,但是这个方法只能是减少元素,并不能减少容器本身所占用的内存空间。咋办?比你想象的更加简单,只是需要一句话:
vector
当你打印出 ip.capacity() 的前后的值,就发现完全是不同的。
string 可以采用和 vector 基本相同的方式来管理合适的内存,不会导致内存的多次分配造成的无谓的消耗,也不会导致内存的过大,导致内存的浪费。
在合适的收缩内存这一点上,不一定采用 vector ,或者是 map 处理更加好, map 是采用红黑二叉树的方式写成,可以按照某种质量自动排序,获取前 10 位。
请参考《 C++ primer 》,《 Effective C++ 》,《 Effective STL 》,《 STL 源码剖析》