现在的位置: 首页 > 综合 > 正文

<>读书笔记

2018年03月16日 ⁄ 综合 ⁄ 共 3514字 ⁄ 字号 评论关闭

     最近重读了Scott Meyers的《Effective STL》,对STL的认识又加深了许多,刚学C++时以为只要能把vector,string,list等用好就是会STL了,但工作中后发现其实并非如此,vector,string,list只是STL很小的一部分,并且把这很小的一部分用好也不是那么容易的的,迭代器,算法也是STL中不可忽视的一部分。

    严格的来说,Effective STL这本书是Scott Meyers从一些STL的相关书籍学习后写的一个读书笔记,写的通俗易懂,也很风趣,学习STL,看明白大师指点的50个条目,再去深入,也会很容易。下面,我将自己读完Effective STL的思考也写为一个笔记,采用问答的方式,希望能够将一些问题说清楚。

1. Effective STL中的STL是什么?什么是标准STL顺序容器,什么是标准STL关联容器。

书中的STL是指C++标准库中和迭代器有交互的那一部分标准库,包含标准容器,一部分的io库,函数对象,以及算法。不包括容器适配器(如stack,quene,priority_quene)等,因为他们缺少迭代器支持。


2. 什么是容器适配器?

容器适配器就是使容器向外提供特殊接口的一个模板,如stack<int>,通常基于deque, 提供FILO服务。


3. vector/string的内存是动态增长的,如何判断它什么时候会增长?

当size()==capacity()时,如果继续push_back,就会增长,一般以2为级数增长,增长时相关的所有迭代器,指向原来内存地址的指针均将失效,必须非常小心。


4. 在何种情形下,vector<char>可以用来代替string?

先说说C++ 标准库中string的一个重要特性:COW(Copy-On-Write);

什么是COW,通过下面的例子可以很清楚的知道:

<span style="font-size:14px;">#include <string>
#include <cstdio>

int main()
{
  std::string orig = "I'm the original!";
  std::string copy_cow = orig;
  std::string copy_mem = orig.c_str();
  std::printf("%p %p %p\n", orig.data(), copy_cow.data(),copy_mem.data());//copy_cow and orig share same memory

  copy_cow[3] = 'x';
  copy_mem[3] = 'x';
  std::printf("%p %p %p\n", orig.data(), copy_cow.data(),copy_mem.data());//copy_cow allocate new memory and had its own storage
  return 0;
}
</span>


同时由于string的实现形式有多种,通常情况下string采用了引用计数,对于一般的Linux平台,以我使用的系统为例,可以在/usr/include/c++/3.4.5/bits/basic_string.h看到系统的string的实现。

采用引用计数可以减少内存的分配和销毁,但是当string的实现采用引用计数时,在多线程情况下其带来的好处和加锁维护数据的一致性带来的性能损耗相比,得不偿失[1],这种情况下,有两种选择,禁用string的引用计数;或者采取vector<char>,这也是vector<char>的应用场景,其缺点是需要用vector本身的算法和STL算法来模拟string的大部分操作。


5. string的数据存储一定存储在连续存储空间吗?

C++标准并没有规定string的内存布局,但是它规定了所有string应该实现的接口。从Item15作者列举的4种实现可以看出,string的数据值本身,如string a("abc")中的“abc”一定是存储在连续空间的,但是整个string对象,如a,可能使用了多个数据块。


6. 清空一个vector/string时,调用erase和调用swap有什么区别?

erase后,vector的capacity没有变化,内存空间任然占用着;用空的对象与需要清空的对象进行swap,会清掉内存。

#include<iostream>
#include<vector>
using namespace std;
int main()
{
    vector<int> vec(5,1);
    vec.erase(vec.begin(),vec.end());
    cout<<"vec capacity = "<<vec.capacity()<<endl;


    vector<int> vec1(5,1);
    vector<int>().swap(vec1);
    cout<<"vec1 capacity = "<<vec1.capacity()<<endl;

    return 0;
}

7.对于map,插入一个元素,什么时候使用m[k]=v;什么时候使用m.insert(pair(k,v));

map的下标操作有两层意思,如果key不存在,就插入,如果存在,就更新;

  mapped_type&
      operator[](const key_type& __k)
      {
	// concept requirements
	__glibcxx_function_requires(_DefaultConstructibleConcept<mapped_type>)

	iterator __i = lower_bound(__k);
	// __i->first is greater than or equivalent to __k.
	if (__i == end() || key_comp()(__k, (*__i).first))
          __i = insert(__i, value_type(__k, mapped_type()));
	return (*__i).second;
      }

而insert就是简单的插入

insert(const value_type& __x)
      { return _M_t.insert_unique(__x); }

因此,当确定key不存在时,使用insert;当需要更新key对应的value时,使用[ ]. 这样可以取得最好的效率。


8. 对于map, 查找是否含有某个key,什么时候使用find,什么时候使用count?

一般来说,二者没有太大的区别,需要根据使用场景来区别,如果含有某个元素后还需要对其进行操作,最好用find;如果只是需要判断某个元素是否存在,使用count可以使代码更精练。

const_iterator
      find(const key_type& __x) const
      { return _M_t.find(__x); }

      /**
       *  @brief  Finds the number of elements with given key.
       *  @param  x  Key of (key, value) pairs to be located.
       *  @return  Number of elements with specified key.
       *
       *  This function only makes sense for multimaps; for map the result will
       *  either be 0 (not present) or 1 (present).
       */
      size_type
      count(const key_type& __x) const
      { return _M_t.find(__x) == _M_t.end() ? 0 : 1; }

9.  如何构造end of stream iterator

下面是一个构造并使用end of stream iterator的例子:

#include <iostream>
#include <fstream>
#include <numeric>  // for accumulate()
#include <iterator>  

using namespace std;

int main()
{
  ifstream myInt("data");
  istream_iterator<int> iter(myInt);
  istream_iterator<int> eos;  // end of stream iterator

  cout << "Sum of the data is "
       << accumulate(iter, eos, 0)
       << endl;
  return 0;
}

istream_iterator的默认构造函数产生的迭代器有一个默认值,当istream_iterator到达流的终点或是出错时,和该默认值相等,所以上面的例子中不用输入文件名就产生了一个文件流终点的末端迭代器。





抱歉!评论已关闭.