STL功能很强大,但是说到删除元素,不少人会觉得有些上头。
删除元素,第一印象可能就是erase和remove。
remove
remove,在STL不止一个,有全局std::remove函数,list容器中,还有一个名为remove的成员函数;两个函数的名称完全一样,很多人傻傻分不清。
有以下几点区别:
1、std::remove,适用于很多容器,list只是其中之一;而list的成员函数remove,既然是成员函数,肯定是它独享咯!
2、std::remove,其实并没有真正从容器中删除元素;list的成员函数remove,将list中满足条件的元素真正删除了!
这是最重要的两点,其他类似参数不一样、返回值不一样等等,就不赘述了!上代码:
template<typename T>
void PRINT_ALL(const T& rColl, const char* pRemind)
{
std::cout << pRemind;
for (const auto& ele : rColl)
std::cout << ele << " ";
std::cout << std::endl;
}
template<typename T>
void InitData(T& rValue)
{
rValue.clear();
rValue = { 1, 5, 4, 4, 7, 4, 2 };
}
int _tmain(int argc, _TCHAR* argv[])
{
using std::cout;
using std::endl;
std::list<int> lstInt;
InitData(lstInt);
PRINT_ALL(lstInt, "原始数据:");
std::remove(lstInt.begin(), lstInt.end(), 4);
PRINT_ALL(lstInt, "std::remove后:");
InitData(lstInt);
PRINT_ALL(lstInt, "原始数据:");
lstInt.remove(4);
PRINT_ALL(lstInt, "调用成员函数remove后:");
system("pause");
return 0;
}
截图如下(截图更方便阅读):
这里,因为经常要打印,将打印函数封装了一下,还封装了一个初始化数据的函数,执行结果如下:
显而易见,std::remove并没有删除容器中的元素,但似乎又有些变化。list的成员函数remove,成功删除元素!
所以,对list来说,直接用其成员函数remove即可!除了remove外,list还有remove_if成员函数,对于简单删除元素来说,基本满足绝大部分要求。
但是!!!vector呢?它没有remove的成员函数,怎么办?
只能从std::remove来想办法啦!来看看std::remove函数的原型:
有一个返回值!!!啥意思呢?
说人话就是:返回的是没有被删除的最后一个元素的位置,从first到这个返回值,就是不等于val的所有元素的序列。
请注意上面标红的那一句,first表示的是容器迭代器的起始位置,换句话说,只要保留从first到返回值这个区间的元素,就能达到我们的目的。再转换一下思维,如果删除了从这个返回值到end的元素,是不是就可以了?
如何删除一个区间呢?需要另一个函数,erase!
erase
不管是list容器,还是vector容器,都有成员函数erase,原型如下:
可以说长得一样,都有两种形式。
接收一个迭代器作为参数,表示删除指定迭代器位置的元素;以[first,last)为区间的两个迭代器,删除这个区间内的所有元素。
单参数的暂时先不管,双参数的似乎可以满足上面remove的要求,删除std::remove返回值到end这个区间的所有元素,试试看:
所有其他代码都不变,就修改了划线的那一句,执行结果如下:
果然可以满足需求!
erase还有一个单参数的形式,这个也是很多公司面试时经常考的一个点!
看如下代码:
int _tmain(int argc, _TCHAR* argv[])
{
using std::cout;
using std::endl;
std::list<int> lstInt;
InitData(lstInt);
PRINT_ALL(lstInt, "原始数据:");
std::list<int>::iterator iter = lstInt.begin();
for (; iter != lstInt.end(); ++iter)
{
if (4 == *iter)
lstInt.erase(iter);
}
PRINT_ALL(lstInt, "erase后:");
system("pause");
return 0;
}
这里,没有调用std::remove,直接遍历lstInt中的所有元素,删除值为4的元素。看起来好像很好,可是一运行,程序直接崩溃!
为何?原因就出在这个erase上。
执行完lstInt.erase(iter);后,iter这个迭代器就失灵了,在对其做++iter操作,肯定会出现问题。
怎么办?看erase函数有个返回值,其解释如下:
啥意思?最后一个被删除的元素的下一个元素。
OK!问题简单了,lstInt.erase(iter);后iter失灵,但是返回值是被删除的元素的下一个,那把返回值赋值给iter,不就行了?
修改程序,如下:
执行后,结果如下:
不崩溃,但结果也不对,还有一个4没删除掉!
为何如此?
原始数据为 1 5 4 4 7 4 2
当遇到第一个4时,iter = lstInt.erase(iter);,第一个4被删除,iter赋值后指向第二个4,重新执行循环语句,会++iter,iter的指向又变了,到了第二个4之后也就是7的位置。这第二个4,就成了漏网之鱼。
如何解决?
分析一下,当iter指向的元素等于指定元素时,执行iter = lstInt.erase(iter)后,其实,已经做了一次后移操作,iter指向的就是被删除元素的后一个,所以,这时候不应该再++iter,只有当iter指向的元素不等于指定元素时才后移。
很容易看出来,这就是一个if....else的条件判断,修改代码如下:
这里,做了两处修改,一处是从for循环里把++iter去掉了;另一处,是加了一句else ++iter。
执行结果如下:
成功!!
总结一下:
1、如果是list容器,直接调用成员函数remove/remove_if删除元素;
2、调用std::remove,想要删除元素,必须配合erase成员函数才行,将std::remove返回的迭代器至end全部删除;
3、如果遍历元素删除,要小心erase函数,删除后的迭代器不可用。