内容简介:使用vector和string时需注意的问题
随着对STL的频繁使用和着迷,越发意识到更深层次理解STL的重要性。使用STL正确得到结果是一回事,而高效地使用STL得到结果又是另一回事。因此,拿起了Scott Meyers大神的《Effective STL》,结合自己平时遇到的问题,作一些总结和记录。
使用reserve避免不必要的重新分配
我们知道,STL容器最大的进步之一是它们会自动增长以便容纳下所放入的数据,只要没有超出它们的最大限制(可以使用max_size()成员函数查看)就可以。vector和string的增长过程是:
(1)分配一块大小为当前容量的某个倍数的新内存,在大多数实现中,vector和string的容量每次以2的倍数增长,即每当容器需要扩张时,它们的容量即加倍。
(2)把容器的所有元素从旧的内存复制到新的内存中。
(3)析构掉旧内存中的对象。
(4)释放旧内存。
可以看出,这个过程会非常耗时。因此,可以使用reserve成员函数来讲重新分配的次数减少到最低限度,从而避免了重新分配和迭代器/引用失效带来的开销。
因此,当一个元素需要被插入而容器的容量不够时,就会发生重新分配过程。比如:创建一个1到1000之间的vector
vector<int> ivec;
for(int i=1;i<=1000;i++)
ivec.push_back(i);
对于大多数STL实现,该循环在进行过程中将导致2到10次重新分配。而如果改用reserve。
vector<int> v;
v.reserve(1000);
for(int i=1;i<=1000;++i)
v.push_back(i);
那么,在循环过程中,将不会再发生重新分配。
所以,当能确切知道或大致预计容器中最终会有多少元素时,使用reserve。如果不知道,可以先预留足够大的空间,然后,当所有数据都加入以后,再去除多余的容量。
把vector和string数据传递给旧的C API
在使用C/C++时,我们发现很多时候还有旧的C API的身影,它们使用数组和char*指针而不是vector或string对象来进行数据交换。因此,在使用STL时,也必须处理好与旧的C API之间的关系。
比如,有一个vector v,而需要得到一个指向v中数据的指针,从而可把v中的数据作为数组来对待,那么只需要使用&v[0]就可以了。而对于string s,对应的形式是s.c_str()。
所以,对于给定的C API:
void doSomething(const int* pInts, size_t numInts);
可以这样使用:
if(!v.empty()){
doSomething(&v[0], v.size());
}
请注意,不要使用v.begin()来代替&v[0],因为begin的返回值是一个迭代器,不是指针;当需要一个指向vector中的数据的指针时,永远不应该使用begin()。(可以使用&*v.begin())
但是,上述方法对string却是不可靠的。原因如下:
(1)string中的数据不一定存储在连续的内存中;
(2)string的内部表示不一定是以空字符结尾的。
所以,对于给定的C API:
void doSomething(const char* pString)
可以这样使用:
doSomething(s.c_str())
避免使用vector
有两点问题。首先,它不是一个STL容器。其次,它并不存储bool。除此之外,一切正常。
中的一个T对象,那么可以通过取它的地址得到一个指向该对象的指针。
因此,如果vector
vector<bool> v; bool *p = &v[0];
使用了与位域一样的思想,来表示它所存储的那些bool;实际上它只是假装存储了这些bool。
位域与bool相似,它只能表示两个可能的值,但是在bool与看似bool的位域之间有一个重要的区别:可以创建一个指向bool的指针,而指向单个位的指针则是不允许的。
::operator[]需要返回一个指向单个位的引用,而这样的引用却不存在。
的时候,应该使用什么呢?
中的数据传递给一个期望bool数组的C API。
第二种方案时bitset。bitset不是STL容器,但它是标准C++库的一部分。与STL容器不同的是,它的大小(即元素的个数)在编译时就确定了,所以它不支持插入和删除元素。而且,它不支持迭代器。但是,与vector
string实现的多样性
几乎每个string实现都包含如下信息:
(1)字符串的大小,即所包含的字符的个数。
(2)用于存储该字符串中字符的内存的容量。
(3)字符串的值。
除此之外,一个string可能还包含:
(1)它的分配子的一份拷贝。
(2)对值的引用计数。
不同的string实现以不同的方式来组织这些信息。下面以4种不同的string实现方式来说明。它们来源于四种STL实现。
实现A
每个string对象包含其分配子的一份拷贝、该字符串的大小、它的容量以及一个指针,该指针指向一块动态分配的内存,其中包含了引用计数和字符串的值。
在该实现中,使用默认分配子的string对象是一个指针的4倍。若使用了自定义的分配子,则string对象会更大一些,多出的部分取决于分配子对象的大小。
实现B
在实现B中,string对象与指针大小相同,因为它只包含一个指向结构的指针。B的string所指向的对象中包含了该字符串的大小、容量和引用计数,以及一个指向动态分配的内存的指针,该内存中存放了字符串的值。
实现C
在实现C中,string对象的大小总是与指针的相同,该指针指向一块动态分配的内存,其中包含了与该字符串相关的一切数据:它的大小、容量、引用计数和值。
实现D
实现D的string对象是指针大小的7倍(仍然假定使用的是默认的分配子)。这一实现不使用引用计数,但是每个string对象内部包含一块内存,最大可容纳15个字符的字符串。因此,小的字符串可以完整地存放在string对象中,这通常被称为“小字符串优化”特性。而当一个string的容量超过15时,该内存的起始部分被当做一个指向一块动态分配的内存的指针,而该string的值就在这块内存中。
比较
(1)在以引用计数为基础的设计方案中,string对象之外的一切可以被多个string对象(如果它们有同样的值)。所以,实现A比实现B或C提供了较小的共享能力。尤其是,实现B和C可以共享string的大小和容量,从而减少每个对象存储这些数据的平均开销。
(2)创建一个新的字符串值可能需要零次、一次或两次动态分配。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Where Wizards Stay Up Late
Katie Hafner / Simon & Schuster / 1998-1-21 / USD 16.00
Twenty five years ago, it didn't exist. Today, twenty million people worldwide are surfing the Net. "Where Wizards Stay Up Late" is the exciting story of the pioneers responsible for creating the most......一起来看看 《Where Wizards Stay Up Late》 这本书的介绍吧!