还是很喜欢effective部分的书,看了好几遍,这里把stl中和容器相关的一些基本的注意的点进行介绍总结,之后对迭代器等进行总结
1 对序列容器中需要逐个删除的时候,不能像关联容器那样事先对迭代器进行++操作,因为删除一个迭代器,会使他自己无效,后面的迭代器也无效,所以应该保存删除erase返回的下一个指针的值。而关联容器中并不会导致后面的迭代器无效的情况
2 在stl中如果对容器内对象使用了new操作,一定要释放掉,因为析构函数没办法完成,这里最好使用智能指针,但是不要使用auto_ptr的指针,因为他的拷贝,会使背被拷贝的对象赋值为NULL可能导致无法预料的问题
3 如果希望减少在替换容器的类型的时候需要修改的代码,那么应该把容器进行typedef的替换,同时最好直接隐藏到一个类当中,进行使用接口进行处理
4 使用区间成员函数,比如assign,区间的构造函数,区间的insert函数,区间的erase函数,都能比循环调用单元素对应的函数好,从函数调用,元素之间赋值位置变化,还有内存的分配问题进行考虑
5 对于序列容器,list容器,还有关联容器,删除元素的使用应该选用不同的删除函数,具体参见条款9,注意这里是删除某个单独的元素,如果是直接删除一个区间的数据,那么直接利用erase就可以
6 许多string的背后使用了计数的功能,消除没有必要的内存分配和字符拷贝,提高了效率。但是如果是在多线程的情况下,那么久必须注意读写的一致性问题
7 reverse函数只有vector和string有
他可以减少内存重新分配的次数
重点需要区分一下resize和reverse两个函数
resize:强迫容器改变到包含n个元素的状态,如果比现在的size()数量小,那么将末尾的数据析构,如果大那么利用默认构造函数进行初始化
reverse:强迫容器的容量为n,和capacity()进行对应,通常导致新的内存分配。如果n比当前的容量小,vector什么都不做,string将其设为size()和n中的最大值
一般在声明容器后就进行reverse的操作,可以预见大小的情况下,之后也就可以预估迭代器是否会失效
8 string有很多的实现方式
但是基本都必须存有下面几个部分的数据
字符串的大小
内存的容量capacity
字符串的值
分配子的一个拷贝
对值的引用计数
不同的实现方式可以会使器是否有引用计数,经常需要复制,或者多线程的情况下可能有用,同时如果是小字符串,是否会进行单独的油画,创建时候的动态分配内存的数量等,详情看第15条数据
9 如果有一个 C API的函数
void dosomething(const int* pInts,size_t numInts);
void dosomething(const char* pInts);
对于vector可以直接传入&v[0],一般不建议传入v.begin()因为他毕竟是一个迭代器,但是传入的时候需要判断容器是不是空的
对于string有一个专门的函数,s.c_str(),不需要判断是否为空
一般传入后,最好设为只读模式,如果进行了增删操作,那么可能会直接影响了之前的容器,导致一些不可预见的影响
如果是C函数初始化容器,都可以利用vector进行一个中间的转存的操作,详情见16条款
10 如果希望将vector,string中的一些多余的容量进行删除,erase主要是删除了容量的大小,但是并没有减少容量,为了压缩到适当的大小利用swap的方法
vector
string s; string(s).swap(s)
先通过创建临时变量,只会拷贝存在的元素,然后再交换
同时注意:并不是说之后的内存就刚好是元素的内存和,可能也会有一定的保留
同时:之前的迭代器和,指针,引用之类并不会失效
11 vector中最好不要使用vector
如果一定要用bool的容器,那么建议使用deque
12 在关联容器中 有两个概念,相等和等价
非成员函数的find函数一般是利用相等,operator ==基于数据的值是否相等eaqul_to函数
非成员函数的insert操作,因为要判断,是否有相同的key了,所以主要是对排序数据中的位置进行判断的,在其中的等价关系主要是通过operator < 进行判断的,less函数
每个标准容器利用用户自己定义的key_comp进行判断,默认的函数是less函数,可以自己设定,确定了之后只要利用成员函数进行访问,那么就能够将比较进行统一了。
而且最好在和排序相关的关联容器中使用等价的概念
因为如果相等的情况下插入两个其实等价的数据,但是在排序的时候又没办法分开排序,那么就不能按确定的方式访问key的value值了,类似于multiSet等
但是这里主要是提到了带排序的关联容器,对于不是标准的关联容器:
hash_set hash_multiset hash_map hash_multimap
这些关联容器并不是按照排序方式进行存储的
他们的比较函数就是equal_to
13 复习一下traits的应用
14 如果想打印一个容器中的值,可以用copy函数讲值放入一个输出流中
copy(ssp.begin(),ssp.end(),ostream_iterator
还有一个需要注意的地方就是在传入一些比较函数的时候其实传入的需要的是一个类型,而不是函数指针,如果是函数指针,直接声明一个函数就可以了。。。需要注意,如果是一个类型
那么一般选择的是一个struct类型后创建一个函数
struct Differ{
template
bool operator()(ptrtype p1,ptrtype p2)
{
return *p1 < *p2;
{
}
调用的时候 set
15 感觉21条建议真的是很晕啊,看了几遍才理清楚
主要是容器中虽然有一个等价的判断函数比如compare但是最后判断是否是等价还有一个外层的判断
!(compare(a1,a2)) && !(compare(a2,a1))
如果compare函数将两个相等的数判断为相等,那么最终会导致说这两个数不等价,错误
所以对相等的数,compare应该返回false,其实这里的compare应该理解为 一个值是否在另一个值的前面,如果两个值相等,那么他的值当然没有位置的关系,所以应该返回false
16 对于set map类型中如果需要修改对应的key
在map中类型是pair
在set中元素不是const的,因为类型可能是定义的struct或者 class的类型,在排序的过程中,根据某一元素进行排序,那么在修改的时候他是不能够修改的,但是对于别的部分,是可以进行修改的。
因为如果直接修改,可能会影响之前的排序情况
如果一定要进行改变,最好的是复制元素,修改,删除后重新插入
17 在考虑排序的vector的过程中:
因为map中是按照平衡二叉树进行存储的,如果只是想要提高查询的速度,那么选用散列的容器(常数时间)远远好于标准容器的对数时间,但是如果散列表比较小,可能会使查找的性能比较差
同时在map的存储中因为还有一些指针的额外开销,同时作用兄弟在无力上的存储也不相邻。所以如果量很大,比如跨越了多个内存页面的情况下可能会相对而言比较慢。
相对于排序的vector中,如果采用binary_search可能会更快,但是因为是vector的情况,所以必须要求数据的增删的操作很少,同时还得模拟pair 的一些操作,这里一个注意的地方就是,map中的key是const的,但是在vector中进行模拟的时候是不能定义为const,因为会有很多的拷贝操作等
18 对于map的操作,[]操作有不同的意思,如果key在原来的数据结构中没有,那么对应时插入操作,如果已经有了,那么对应的就是修改操作
为了效率高,
如果,确定知道是插入,最好利用insert操作。因为如果是[],是先插入一个构造函数对应的pair数据,然后再进行修改的数据,不如直接插入
如果是修改操作,那么就是直接进行[],因为如果是利用insert,是先将值[]修改了后insert