Effective STL读书笔记-第一章

博客已搬家,请移步

EffectiveSTL Reading Notes(Chapter 1)

第一节 小心选择各类容器
标准STL顺序容器:vector string deque list
标准STL关联容器:set multiset map multiset
其它的基本上可以忽略了,本来STL容器用的就不多,另外有时候需要用vector来替代string,具体场景第13节会跟进。
另外容器还可以换一个角度进行区分,包括:连续内存的容器和离散内存的容器。
连续内存容器:元素被存储在一块或者多块内存上,每块内存包含多个元素,元素的插入和删除会带来同块内存上的别的元素的移位的额外操作,vector、string、deque都属于连续内存的容器。
离散内存容器:每块内存上之存储一个元素,元素的插入和删除没有额外开销,list和所有的关联容器都是此类。
对于如何选择容器,应该会考虑很多东西,读完这本书,应该就知道哪些要考虑了。

第二节 不要过分追求编写容器独立的代码
即,换一种容器之后,代码仍然能work,或者说是一种模板,容器可以作为模板参数。
这其实没有太大意义,因为容器既然各有特点,那说明各自的用场景都不一样,要相互替换的情况很少见了。由于不同容器的特性不一样,容器独立的代码可能会使得代码只能使用所有要支持的容器的那些公共的优点,却可能要注意所有容器的缺点。

第三节 保证容器中元素的拷贝操作轻量且正确
容器中保存的元素实例,并不是你塞进去的那个实例,而是会重新拷贝一份存放起来。同样,从容器中取元素,取到的也不是容器中的那个实例,而是容器中实例的一个副本。正是由于STL的这种方式,所以容器中的元素的拷贝操作必须需要是轻量且正确的。比如说自己实现的拷贝构造函数和赋值运算符,要保证正确性,以及轻量。
所有的对象,只要存放在容器中,被拷贝或者复制就是迟早的事情,比如说排序,插入等到做,都会伴随着大量的复制等操作。
STL之所以这么设计,其实是为了避免不必要的拷贝,比如说定义一个对象的数组ClassFoo array[10]时,会自动的构造10个Class类的对象,而如果定义一个vector则只有在需要的时候才会创建新对象,而创建对象的方法很可能就来自于插元素进去时的copy操作。

第四节 用empty()代替site()来判断是否为空

第五节 尽量用批量接口代替单个接口
以前觉得批量接口只是一个花哨的东西,不过由于STL的特殊性,批量接口确实有存在的意义。
1 减少函数调用
2 代码更清晰,简洁。
3 最重要,减少不必要的内存分配和移位,比如说批量的插入vector相比于单个插入vector。由于每次插入都会带来元素的重新移位,有可能带来内存的增长re-allocation操作,如果批量插入,移位和re-allocation只会做一次,而如果是循环的多次插入,可能就会做很多次。归根结底还是因为STL本身的实现机制决定的。
一般存在的批量接口:
批量构造:container::container(InputIterator begin, InputInterator end)
批量插入:对于顺序容器,一般是:void container::insert(iterator position, InputIterator begin, InputIterator end);
对于关联容器,一般是: void container::insert(InputIterator begin, InputIterator end);
区别在于关联容器元素的位置是内定的,不需使用者提供插入位置。
一些像push_bask, push_front等操作,一起通过BackInserter等进行copy的操作,都记得用批量的插入替换掉。
批量删除:对于顺序容器,一般是 iteartor container::erase(iterator begin, iterator end);
对于关联容器,一般是void container::erase(iterator begin, iterator end);
批量复制:void container::assign(InputIterator begin, InputIterator end)

第六节 从文件解释内容放到容器中
STL提供了直接从文件构造容器的机制,比如说文件中是一排int,那么使用istream_iterator就可以将文件中的一排int遍历一遍。于是使用批量构造器container::container(InputIterator begin, InputIterator end),就可以完成将文件内容直接构造到容器中,以list为例:
ifstream file(‘data.dat’);
list data>(istream_iterator(file), istream_iterator());
意在完成上述功能。
但事实上,这段代码并不会生效,因为C++中有条规则,任何可以被解释成函数声明的语句都将被认为是函数声明。而函数声明允许在声明时,在参数两边加上多余的(),即 void d(int (i))是一个合法的函数声明,另外函数声明是可以不提供形参的名字,即void g(void f())==void g(void (*f)())这个用来申明一个接受一个函数指针的函数的函数声明语句,也可以写成void g(void())==void g(void (*)());
因此:
list data>(istream_iterator(file), istream_iterator());
将被编译器认为是一个函数申明,第一个参数是istream_iterator类型的,形参名字是file,第二个参数是一个函数,这个函数返回istream_iterator并没有任何参数,这就是神奇发生的原因。
于是只要把istream_iterator对象的实例化单独出来既可以解决问题了。


你可能感兴趣的:(C/C++笔记)