标准序列容器:vector,string,deque,list
标准关联容器:set,multiset,map,multimap
非标准序列容器:slist(单向链表),rope(重型string)
非标准的关联容器:hash_set,hash_multiset,hash_map,hash_multimap
标准的非STL容器:数组,bitset,valarray,stack,queue和priority_queue
1、如果需要在容器任意位置插入新元素,则考虑序列容器,关联容器做不到
2、如果不关心容器中的元素有排序关系,则哈希容器是一个可行的选择方案,否则,避免哈希容器。
3、如果你选择随机访问的迭代器,则对容器的选择限定为vector、deque、string。如果选择双向迭代器,那么你必须避免slist以及哈希容器的一个常见实现。
4、当发生容器中元素插入、删除操作,避免移动容器中原来的元素是否很重要?如果是,就要避免连续内存的容器。
5、如果元素查找速度是关键因素?如果是,就要考虑哈希容器、排序的vector和标准关联容器。
6、如果容器内部使用了引用计数,如果介意的话,则避免使用string,因为许多string的实现都使用了引用计数。
7、对插入和删除操作,你需要事务语义吗?也就是说在插入失败和删除的情况下,你需要回滚的能力吗?如果需要,你就要使用基于节点的容器。如果对于多个元素的插入需要事务语义,则需要使用list。
8、如果需要使迭代器、指针和引用变为无效的的次数最少,就要使用基于节点的容器。
9、如果序列容器的迭代器是随机访问类型,而且只要没有删除操作发生,且插入操作只发生在容器的末尾,则指向数据的指针和引用就不会变为无效。
容器遵循(copy in,copy out)的原则,但在c++11可以用emplace_back进行原地构造元素,而不需要先构造再复制的过程,其中省略了一次复制过程。
STL的实际目标是为了避免创建不必要的对象。
Widget w[maxNumWidgets];//创建了有maxNumWidgets个widget的数组
//每个对象都使用了默认构造函数来创建
std::vector<Widget> vw;
vw.reserve(maxNumWidgets);//创建了足够空间来容纳maxNumWidgets个对象,但并没有创建任何一个widget对象。
empty对所有标准容器都是常数时间操作,而一些list实现,对于size是耗费线性时间(其中涉及到list的链接问题,如果链接操作时常数时间,则size是线性时间,反之亦然)。
当你需要完全替换一个容器的内容是,你应该想到赋值。如果你想把一个容器拷贝到相同类型的另一个容器,那么operator = 是可选择的赋值函数。但当你想给容器一组全新的值时,你可以使用assign,而operator=则不能满足你的要求。
v2.assign(v1.begin()+v1.size()/2,v1.end());//100分写法
v2.clear();
copy(v1.begin()+v1.size()/2,v1.end(),back_inserter(v1)); //80分写法
v2.clear();
v2.insert(v1.begin()+v1.size()/2,v1.end()); //90分写法,相比80分写法,更加直接了当直观,且相对于循环insert少了大量的拷贝操作、函数调用
Note:几乎所有通过利用插入迭代器(insert iterator)的方式(即利用inserter、back_itsertor或front_itsertor)来限定目标区间的copy调用,其实都可以用被替换成对区间成员函数的调用。
标题所述的原因:
1、通过使用区间成员函数,通常可以少写一些代码。
2、使用区间成员函数通常会得到意图清晰和更加直接的代码
//区间创建:
container::container(InputIterator begin,InputIterator end);
//区间插入
//序列容器
void container::insert(iterator position,InputIterator begin,InputIterator end)
//关联容器 利用比较函数来决定插入的位置
void container::insert(InputIterator begin,InputIterator end);
//区间删除
//序列容器
iterator container::erase(iterator begin,iterator end);
//关联容器 区别在于返回值类型
void container::erase(iterator begin,iterator end);
//区间赋值
void container::assign(InputIterator begin,InputIterator end);
int f(double d);//声明为函数
int f(double (d)); //声明为函数,括号被忽略
int f(double );//声明为函数
同理
list<int> data(istream_iterator<int> (datafile),istream_iterator<int>());//也声明为函数data,其返回值为list
上述情况都涉及到了C++中一条普遍规律相符:尽可能解释为函数声明。
解决方法:避免使用匿名的istream_iterator对象(尽管使用匿名对象是一种趋势),而是给这些迭代器一个名称。
ifstream datafile("int.dat");
istream_iterator dataBegin(dataFile);
istream_iterator dataEnd;
list<int> data(dataBegin,dataEnd);
删除容器中元素为1963的操作。
1、针对连续内存的容器(vector、deque、string):
erase-remove方法
container.erase(remove(c.begin(),c.end(),1963),c.end());
2、针对list的方法
container.erase(1963);
3、针对标准关联容器(set、multiset、map或multimap)
c.erase(1963);
所需要的时间是对数时间的开销,它是基于等价的而不是相等的。
删除容器中满足判别式的元素
bool badvalue(int); //判别式
1、针对序列容器
c.erase(remove_if(c.begin(),c.end(),badvalue),c.end()); //vector、string、deque
c.remove_if(badvalue);//list
2、针对关联容器,采用循环的方法
AssocContainer<int> c;
...
for(AssocContainer<int>::iterator i=c.begin();i!=c.end();**什么都不做**){
if(badvalue(*i)) {
c.erase(i++);
}
esle c.erase(++i);
}
如果在删除的同时需要添加其他操作(如添加log信息),则应采用如下方法:
1、针对序列容器
AssocContainer<int> c;
...
for(AssocContainer<int>::iterator i=c.begin();i!=c.end();**什么都不做**){
if(badvalue(*i)) {
logFile<<"Erasing"<<*i<<'\n';
i = c.erase(i);//把erase的返回值赋给i,使i的值保持有效
}
esle c.erase(++i);
}
因为针对序列容器而言,erase操作不仅会使指向被删除元素的迭代器无效,也会使被删除元素之后的迭代器失效。因此采用erase操作的返回值,一旦erase操作完成,它是指向紧随被删除元素的下一个元素的有效迭代器。
2、针对关联容器
AssocContainer<int> c;
...
for(AssocContainer<int>::iterator i=c.begin();i!=c.end();**什么都不做**){
if(badvalue(*i)) {
logFile<<"Erasing"<<*i<<'\n';
c.erase(i++);
}
esle c.erase(++i);
}
1、你的分配子是个模板,模板参数T代表你为它分配内存的对象的类型。
2、提供类型定义pointer和reference,但是始终让pointer为T*,reference为T&
3、千万别让你的分配子拥有对象而不同的状态。通常,分配子不应该有非静态的数据成员。
4、传给分配子的allocate成员函数是那些要求内存的对象的个数,而不是所需的字节数。同时要记住,这些函数返回T*指针,即使尚未有T对象被构造出来。
5、一定要提供嵌套的rebind模板,因为标准容器依赖改模板。