或许我们曾遇到过一道面试题,给定一个vector矢量,删除这个容器中所有值等于3的元素。
第一次,手写循环删除:
vectorvec{1, 2, 3, 5, 3, 6, 3, 7, 3, 3}; for(vector ::iterator iter = vec.bengin(); iter != vec.end(); ++iter) { if((*iter) == 3) { iter = vec.earse(iter); --iter; } } for(vector ::iterator iter = vec.bengin(); iter != vec.end(); ) { if((*iter) == 3) { iter = vec.earse(iter); } else { ++iter; } }
上面的两种循环代码能够满足题目的要求,但实现确实是不忍直视。在调用earse函数之后要对迭代进行减一的目的是,上面的 iter = vec.earse(iter);其实已经将删除后iter后的下一个迭代器的值赋给了iter,这样做的目的是为了防止迭代器的失效。而在后面的循环中会对iter进行递增的处理。因此要先将iter进行递减一次,才能避免容器中的每个元素都能被循环到。
如果不先递减一次,则,相邻的两个元素值都为3的情况,后面的那个元素不会被移除。
stl库提供的算法
vec.earse(remove(vec.bengin(), vec.end(), 3), vec.end());
当然,上面这种方法适用与容器是连续内存型的。
如果容器是list,则更简单:
listlist; list.remove(3);
而对于关联容器来说,earse是最常用的方法。
同样的,我们延申一下,我们现在不会指定特定的值,而是满足某一个设定的条件:
bool removevalue(int value);
满足上面函数的条件时,比如说,删除值为3的倍数的元素等等。
同样的,stl提供了简单并且有效的方法。
vec.earse(remove_if(vec.bengin(), vec.end(), removevalue), vec.end());
对于list
list.remove_if(removevalue);
但是对于关联容器来说,就没有这样的直接了当了。
AssocContainerc; AssocContainer goodValue; remove_copy_if(c.bengin(), c.end(), inserter(goodValue, goodValue.end()), removevalue); c.swp(goodValue);
这种方法需要拷贝所有不被删除的元素到一个同样类型的容器中,然后通过swap方法进行对两个容器交换。如果容器的元素较多时,所需的拷贝消耗还是可观的,有时候,我们可能并不希望这么多的拷贝代价。
那么,我们怎样才能避免这样的拷贝代价,而直接从原来的容器中直接删除呢?好像我们只能手写循环了。我们最直接想到的解决方案如下:
AssocContainerc; AssocContainer ::iterator iter = c.bengin(); for(; iter != c.end(); ++iter) { if(removevalue(*iter)) { c.earse(iter); } }
但是仔细看一看,上面的代码能正常执行吗?不能,为什么呢?
因为容器中如果我们删除其中的一个迭代器,那么所有的迭代器就会失效。如果是标准的序列容器,earse的返回值是下一个容器,而关联容器的earse函数的返回值是void类型。
AssocContainerc; for(AssocContainer ::iterator iter = c.bengin(); iter != c.end();) { if(removevalue(*iter)) { c.earse(iter++); } else { ++iter; } }
上面的这段代码能够满足我们的要求,如果需要移除,则在移除之后将对迭代进行自增,否则则进行简单的自增,这样在每次循环开始之前,我们都已经对迭代器进行了自增。代码简单,但并不会是第一时间想到的。必然要经过几次的失败。
总结:
- 要删除容器中有特定值的所有对象:
如果是vector、string、deque,则使用earse-remove习惯用法
如果是list,则使用list::remove
如果容器是标准关联容器,则使用其earse成员函数 - 要删除容器中满足特定的判别条件的所有对象:
如果是vector、string、deque,则使用earse-remove_if习惯用法
如果是list,则使用list::remove_if
如果是标准关联容器,则使用remove_copy_if和swap,或者写一个循环来遍历容器的元素,但在earse迭代器之后要记得进行递增 - 要在循环内部做某些(除了删除)操作:
如果是标准序列容器,则写一个循环遍历,每次调用earse时,要用返回值更新迭代器
如果是标准关联容器,则使用remove_copy_if和swap,或者写一个循环来遍历容器的元素,但在
earse迭代器之后要记得进行递增
容器中存的是通过new()创建的指针的以后,析构容器之前一定要先delete掉new出来的指针。为什么呢?
虽然容器在析构的时候会析构他的每个元素,这也就让我们心安理得的不再考虑善后的事情。
而这样,就会出现问题。
虽然容器析构。调用了指针的析构,但这个析构是不做任何事情的,也就不会调用delete.
意味着我们new 的指针必须由我们自己选择合适的时机delete。
vectorvwp; vector ::iterator iter = vwp.begin(); for(; iter != vwp.end(); ++iter) { delete *iter; }
上面的方法可以删除析构,但存在的问题是,如果在某次析构的时候跑出异常,也就意味着会有内存的泄漏。
templatestruct DeleteObject : public unary_function { void operator(const T* ptr) { delete ptr; } }
for_each(vwp.bengin(), vwp.end(), DeleteObject());
这样,我们就能保证内存不会泄漏。但是呢?这样我们就得只能指针的类型,如果元素的类型是string的话就会有问题,因为string没有虚析构函数。
假设我们能够让编译器自己推导出我们传给它的指针类型,也就可以消除这个错误了。
struct DeleteObject {
template
void operator(const T* ptr)
{
delete ptr;
}
};
3、使用reserve函数减少内存分配的次数
如果能够确切或大致的知道容器有多少个元素,使用reserve函数则能够比较减少内存重新分配的次数。
比如:
vectorvec; for(int index = 0; index < 1000; ++index) { vec.push_back(index); }
如果我们按照常规的方式,上述这个过程需要重新分配内存11次。但是如果我们提前知道了我们需要容器的容量大小,使用reserve函数设置大小,内存分配的次数就会只有一次。
vectorvec; vec.reserve(1000); for(int index = 0; index < 1000; ++index) { vec.push_back(index); }
这样,在循环过程中就不会再进行内存的重新分配。
需要注意的是,如果调用的函数reserve的大小n比当前容器的size小的话,那么vector容器则会什么也不做,不会向英国函数的调用,而string可能会将自己的容量减为size和n中较大值,但是string的大小肯定是保持不变的。
所以,使用reserve改变容器的容量大小,通常不如使用swap技巧来的方便。
但是如果我们不知道容器的大小怎么办,我们也就没办法精确的知道reserve函数的参数应该赋值多少。
不要着急,上面说的swap技巧给我们提供了种方法。
- 我们首先使用reserve申请一份较大的内存
- 数据全部放进容器之后再使用swap将多余的容量空间删除掉
class Widget{...};
vector vec;
vector(vec).swap(vec);
上面这行代码的含义了就是,先vector(vec)创建一个临时变量,临时变量是通过vector的拷贝构造函数对vec进行了复制,复制之后的临时变量是不包含原先vec中空的容量的。也就是说,临时变量的 capacity 和 vec 的size 是一样大的。
然后通过swap将临时变量和 vec进行交换,也就实现了我们前面所说的出去vec中多余的容量。这种方法对string也是有效的。
string s; string(s).swap(s);
这种方法还可以用来清除一个容器,也就是说使用的临时变量是一个空的容器的时候就可以实现清除容器的功能



