内容简介:使用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)创建一个新的字符串值可能需要零次、一次或两次动态分配。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- JWT使用一些注意事项
- Laravel chunk 使用注意的问题
- 使用 Spring AOP 注意事项
- [译] MySQL UTF-8 使用请注意
- epoll事件驱动框架使用注意事项
- Java中Optional使用注意事项
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
New Dark Age
James Bridle / Verso Books / 2018-7-17 / GBP 16.99
As the world around us increases in technological complexity, our understanding of it diminishes. Underlying this trend is a single idea: the belief that our existence is understandable through comput......一起来看看 《New Dark Age》 这本书的介绍吧!