堕_落羽吧 关注:16贴子:785
  • 11回复贴,共1

《Effective STL》学习笔记

只看楼主收藏回复

2. Vector 和 String
第13条:Vector 和 String 优先于动态分配的数组
每次当你发现自己要动态地分配一个数组时(例如想写"new T[..]”时),你都应该考虑用vector和string来代替(一般情况下,当T是字符类型时用string,否则用vector。)。
vector和string消除了上述的负担,因为它们自己管理内存。当元素被加入到容器中时,它们的内存会增长;而当vector或string被析构时,它们的析构函数会自动析构容器中的元素并释放包含这些元素的内存。


IP属地:广东1楼2022-09-07 22:23回复
    第14条:使用reserve来避免不必要的重新分配。
    关于STL容器,最了不起的一点是,它们会自动增长以便容纳下你放入其中的数据,只要没有超出它们的最大限制就可以(要知道这一最大限制,请调用适当的名为max_size的成员函数)。对于vector和string,增长过程是这样来实现的:每当需要更多空间时,就调用与realloc类似的操作。这一类似于realloc的操作分为如下4部分。
    1.分配一块大小为当前容量的某个倍数的新内存。在大多数实现中,vector和string的容量每次以2的倍数增长,即每当容器需要扩张时,它们的容量即加倍。
    2.把容器的所有元素从旧的内存复制到新的内存中。
    3.析构掉旧内存中的对象。
    4.释放旧内存。


    IP属地:广东2楼2022-09-07 22:23
    回复
      每当这些步骤发生时,vector或string中所有的指针、选代器和引用都将变得无效。


      IP属地:广东3楼2022-09-07 22:23
      回复
        reserve成员函数能使你把重新分配的次数减少到最低限度,从而避免了重新分配和指针/迭代器/引用失效带来的开销。但是,在解释reserve怎样做到这一点之前,我将简单概括一下4个相互关联、但有时会被混淆的成员函数。在标准容器中,只有vector和string提供了所有这4个函数。
        ■ size() 告诉你该容器中有多少个元素。它不会告诉你该容器为自己所包含的元素分配了多少内存。
        ■ capacity() 告诉你该容器利用已经分配的内存可以容纳多少个元素。这是容器所能容纳的元素总数,而不是它还能容纳多少个元素。如果你想知道一个vector有多少未被使用的内存,就得从capacity() 中减去size()。如果size和capacity返回同样的值,就说明容器中不再有剩余空间了,因此下一个插入操作(通过insert或push_back等)将导致上面所讲过的重新分配过程。
        ■ resize(Container::size_typen)强迫容器改变到包含n个元素的状态。在调用resize之后,size将返回n。如果n比当前的大小(size)要小,则容器尾部的元素将会被析构。如果n比当前的大小要大,则通过默认构造函数创建的新元素将被添加到容器的末尾。如果n比当前的容量要大,那么在添加元素之前,将先重新分配内存。
        ■ reserve(Container::size_typen)强迫容器把它的容量变为至少是n,前提是n不小于当前的大小。这通常会导致重新分配,因为容量需要增加。(如果n比当前的容量小,则vector忽略该调用,什么也不做;而string则可能把自己的容量减为size() 和n中的最大值,但是string的大小肯定保持不变。以我的经验,使用reserve Astring中除去多余的容量通常不如使用"swap技巧”。“swap技巧”是第17条的主题。)


        IP属地:广东4楼2022-09-07 22:24
        回复
          通过这一概括,应该很清楚的是,当一个元素需要被插入而容器的容量不够时,就会发生重新分配过程(包括原始内存的分配和释放,对象的拷贝和析构,选代器、指针和引用的失效)。因此,避免重新分配的关键在于,尽早地使用reserve,把容器的容量设为足够大的值,最好是在容器刚被构造出来之后就使用reserve。


          IP属地:广东5楼2022-09-07 22:24
          回复
            第16条:了解如何把vector和string数据传给旧的API。
            如果你有一个vector v,而需要得到一个指向v中数据的指针,从而可把v中的数据作为数组来对待,那么只需使用 &v[0] 就可以了。对于string s,对应的形式是 s.c_str() 。但是,请接着往下读。如同广告中经常用小号字标出真相一样,这里也会有一些限制。对于vector<int> v;表达式v[0]给出了一个引用,它是该向量中的第一个元素,所以&v[0]是指向第一个元素的指针。唯一麻烦的地方在于,v可能是空的。那么v.size()会是零,&v[0]则试图产生一个指针,而该指针指向的东西并不存在。这样的结果是不确定的。
            begin的返回值是一个选代器,不是指针,当你需要一个指向vector中的数据的指针时,永远不应该使用begin。如果为了某种原因决定用v.begin(),那么请使用&*v.begin(),这和&v[0]产生同样的指针,只是你要敲入更多的字符,而且别人想理解你的代码会更加困难。


            IP属地:广东6楼2022-09-07 22:24
            回复
              如果你想用来自CAPI中的元素初始化一个vector,那么可以利用vector和数组的内存布局兼容性,向API传入该向量中元素的存储区域


              IP属地:广东7楼2022-09-07 22:25
              回复
                这一技术只对vector有效,因为只有vector才保证和数组有同样的内存布局。这意味着,除了vector和string以外的其他STL容器也能把它们的数据传递给C API,你只需要把容器中的每个元素复制到vector中,然后传给该API。


                IP属地:广东8楼2022-09-07 22:25
                回复
                  第17条:使用“swap技巧”除去多余的容量。
                  为了避免向量仍占用不再需要的内存,你希望有一种方法能把它的容量从以前的最大值缩减到当前需要的数量。这种对容量的缩减通常被称为“shrink to fit”(压缩至适当大小)。
                  按下面的做法,你可以从contestants向量中除去多余的容量:
                  vector<Contestant>(contestants).swap(contestants) ;
                  表达式 vector<Contestant>(contestants) 创建一个临时的向量,它是contestants的拷贝:这是由vector的拷贝构造函数来完成的。然而,vector的拷贝构造函数只为所拷贝的元素分配所需要的内存,所以这个临时向量没有多余的容量。然后我们把临时向量中的数据和contestants中的数据做swap操作,在这之后,contestants具有了被去除之后的容量,即原先临时变量的容量,而临时变量的容量则变成了原先contestantsm肿的容量。到这时(在语句结尾),临时向量被析构,从而释放了先前为contestants所占据的内存。乌拉!shrink-to-fit。


                  IP属地:广东9楼2022-09-07 22:25
                  回复
                    同样的技巧对string也适用:
                    string s ;
                    string(s).swap(s);
                    作为题外话,swap技巧的一种变化形式可以用来清除一个容器,并使其容量变为该实现下的最小值。只要与一个用默认构造函数创建的vector或string做交换(swap)就可以了。


                    IP属地:广东10楼2022-09-07 22:25
                    回复
                      第18条:避免使用vector<bool>。
                      作为一个STL 容器,vector<bool>只有两点不对。首先,它不是一个STL容器。其次,它并不存储bool。除此以外,一切正常。既然vector<bool>应当被避免,因为它不是一个容器,那么当你需要vector<bool>时,应该使用什么呢?标准库提供了两种选择,可以满足绝大多数情况下的需求。


                      IP属地:广东11楼2022-09-07 22:25
                      回复
                        第一种是deque<bool>。deque几乎提供了vector所提供的一切(可以看到的省略只有reserve和capacity),但deque<bool>是个STL容器,而且它确实存储bool。当然,deque中元素的内存不是连续的,所以你不能把deque<bool>中的数据传递给一个期望bool数组的C API(见第16条),但对于vector<bool>,你也不能这么做,因为没有一种可移植的方法能够得到vector<bool>的数据(第16条中针对vector的技术对于vector<bool>不能通过编译,因为它们要求能得到一个指向vector中所含元素类型的指针。我不是已经提到过vector<bool>中并没有存储bool吗?)。
                        第二种可以替代vector<bool>的选择是bitset。bitset不是STL容器,但它是标准C++库的部分。与STL容器不同的是,它的大小(即元素的个数)在编译时就确定了,所以它不支持插入和删除元素。而且,因为它不是一个STL容器,所以它不支持选代器。但是,与vector<bool>一样,它使用了一种紧凑表示,只为所包含的每个值提供一位空间。它提供了vector<bool>特有的flip成员函数,以及其他一些特有的、对位的集合有意义的成员函数。如果你不需要选代器和动态地改变大小,那么你可能会发现bitset很适合你的需要。


                        IP属地:广东12楼2022-09-07 22:26
                        回复