lucky_fox

 
 

常用链接

  • 我的随笔
  • 我的评论
  • 我参与的随笔

留言簿

  • 给我留言
  • 查看公开留言
  • 查看私人留言

文章档案

  • 2006年2月 (1)
  • 2005年12月 (1)

搜索

  •  

最新评论

  • 1. re: 关于list容器的问题!
  • 评论内容较长,点击标题查看
  • --lucky_fox
  • 2. re: 关于list容器的问题!
  • 评论内容较长,点击标题查看
  • --lucky_fox
  • 3. re: 关于list容器的问题!
  • 评论内容较长,点击标题查看
  • --lucky_fox
  • 4. re: 关于list容器的问题!
  • 评论内容较长,点击标题查看
  • --lucky_fox
  • 5. re: 为什么Dev-C++可以编译执行,但是VC编译出错,为什么??
  • 谢谢paracelsus,打了sp6之后,问题确实解决了!
  • --lucky_fox

Powered by: 博客园
模板提供:沪江博客
C++博客 | 首页 | 发新随笔 | 发新文章 | 联系 | 聚合 | 管理

关于list容器的问题!

#include <iostream>
#include <list>
using namespace std;

int main()
{

    int ia[]={0,1,1,2,3,5,8,13,21,34,55,89};
    list<int> ilist(ia,ia+12);
    list<int>::iterator il_iter=ilist.begin();

    for(int i=1;il_iter!=ilist.end();il_iter++,i++)
    {

        if(i%2!=0)
            ilist.erase(il_iter);
    }
    il_iter=ilist.begin();
    for(;il_iter!=ilist.end();il_iter++)
        cout<<*il_iter<<"\n";
    return 0;
}
我是想删除list容器中所有奇数位置的元素!但程序执行时有错误!
我单步调试程序,在i=2后的下一个for循环有错误:Access Violation.
请问程序哪里有问题,该如何修正?
发表于 2006-02-13 10:44 lucky_fox 阅读(658) 评论(4)  编辑 收藏 引用
 
评论
# re: 关于list容器的问题!  回复  更多评论    
●vector 的元素删除

话头从 container 的元素删除说起。jyhuang 观察到「如果 vector 或 list
的最後一个元素符合删除条件,程式会有问题」。他给我这样一个片段:

template <typename T>
void print_elements(T elem) { cout << elem << " "; }

void (*pfi)(int) = print_elements; // 函式指标,做为 function object 用
...
int ia[7] = {0,1,2,3,4,5,6};
vector<int> ivec(ia, ia+7);
for_each(ivec.begin(), ivec.end(), pfi); // 0 1 2 3 4 5 6

for (vector<int>::iterator it = ivec.begin(); it != ivec.end(); it++) {
if (*it % 2) // 把奇数去掉
ivec.erase(it);
}
for_each(ivec.begin(), ivec.end(), pfi); // 0 2 4 6 正确!


如果把 array 和 vector 的初值改为:

int ia[8] = {0,1,2,3,4,5,6,7};
vector<int> ivec(ia, ia+8);

则上述程式会当掉。


●I don't think so...

我不相信 STL 会有这麽差劲的表现。所以多做了几次测试如下。

(1) 把 array 和 vector 的初值改为:

int ia[7] = {0,1,2,3,4,5,7};
vector<int> ivec(ia, ia+7);

程式结果为 0 2 4 7。结果不对,但不会当掉。

(2) 把 array 和 vector 的初值改为:

int ia[7] = {0,1,3,5,7,9,7};
vector<int> ivec(ia, ia+7);

程式结果为 0 3 7 7。错得离谱,但不会当掉。

(3) 把 array 和 vector 的初值改为:

int ia[7] = {0,2,4,6,0,4,7};
vector<int> ivec(ia, ia+7);

程式执行时会当掉。


我於是多做了一些观察,然後知道,上述这些动作根本上是完全
不对的。第一个 sample 之执行结果正确,完全是凑巧。

问题不在「最後一个元素是否符合删除条件」,而在对 iterator
特性的认识。当我们做了 ivec.erase(it) 动作,vector 便动态
减缩了一个元素(逻辑上,後继元素向前递补),而 it 不变。
之後 for 回圈的第三部份述句要求 it++,造成 it 跳过了一个元素...。

看实例:

(1) 初值为 {0,1,2,3,4,5,6}

第一次迭代,发现是偶数: ★ 以下以 e 表示 end()
0 1 2 3 4 5 6 e
^
第二次迭代,发现是奇数:
0 1 2 3 4 5 6 e
^
於是将 '1' 删除,vector 变成:
0 2 3 4 5 6 e
^
第三次迭代,发现是奇数(这时已发生错误,因为 '2' 被跳过,没有检查):
0 2 3 4 5 6 e
^
於是将 '3' 删除,vector 变成:
0 2 4 5 6 e
^
第四次迭代,发现是奇数(这时已发生错误,因为 '4' 被跳过,没有检查):
0 2 4 5 6 e
^
於是将 '5' 删除,vector 变成:
0 2 4 6 e
^
第五次迭代,发现 it == vec.end(),於是跳离回圈:
0 2 4 6 e
^
此时 vector 剩馀 0 2 4 6,阴错阳差地与正确结果吻合。


(2) 初值为 {0,2,4,6,0,4,7}

第一次迭代,发现是偶数: ★ 以下以 e 表示 end()
0 2 4 6 0 4 7 e
^
第二次迭代,发现是偶数:
0 2 4 6 0 4 7 e
^
第三次迭代,发现是偶数:
0 2 4 6 0 4 7 e
^
第四次迭代,发现是偶数:
0 2 4 6 0 4 7 e
^
第五次迭代,发现是偶数:
0 2 4 6 0 4 7 e
^
第六次迭代,发现是偶数:
0 2 4 6 0 4 7 e
^
第七次迭代,发现是奇数:
0 2 4 6 0 4 7 e
^
於是将 '7' 删除,vector 变成:
0 2 4 6 0 4 e
^
第八次迭代,it 越过了 end(),造成越界错误。回圈何时结束,未可知也:
0 2 4 6 0 4 e ? ? ? ? ? ?
^

我的结论:

iterator 只适用於在「不更动 container 布局」的条件下,
对 container 做巡访动作。否则,对 iterator 的取值行为,
直观下极易误用。

我记得《C++ Primer》中提过这个观念,但一下子找不出页数。


●应该怎麽做?

要解决上述问题(在 vector 中删除符合某条件的所有元素),
应该使用泛型演算法 remove_if() 再搭配 vector 的 erase():

it = remove_if(ivec.begin(), ivec.end(), bind2nd(modulus<int>(), 2));
ivec.erase(it, ivec.end());


●list 的元素删除

面对 list,不可以直接对其 iterator 做算术运算,因为 list
的元素并不连续储存於记忆体中(《C++ Primer》p.266)。

那麽是不是应该延用泛型演算法 remove_if() 呢?不!
《C++ Primer》p.607 说:

-- quote ---
由於 list 不支持随机存取,merge(), remove(), reverse(), sort(),
和 unique() 等泛型演算法最好不要施行於 list objects 身上。
上述每一个演算法在 list 之中都有对应的成员函式可用:

* list::merge() 将两个排序过的 lists 合并在一起。
* list::remove() 将「与某数值相等」的元素移除掉。
* list::remove_if() 将「与某条件相符」的元素移除掉。
* list::reverse() 将 list 中的元素逆向排列。
* list::sort() 对 list 元素排序。
* list::splice() 将某个 list 的元素搬移到另一个 list 上。
* list::unique() 删除某一元素的连续副本。
-- unquote ---

所以,我们应该这麽做:

int ia[7] = {0,1,2,3,4,5,6};
list<int> ilist(ia, ia+7);
ilist.remove_if(bind2nd(modulus<int>(), 2));
lucky_fox 评论于 2006-03-03 12:09
# re: 关于list容器的问题!  回复  更多评论    
根据上面的提示对程序做了如下的修改:
#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <algorithm>
#include <functional>
using namespace std;

int main()
{

int ia[]={0,1,1,2,3,5,8,13,21,34,55,89};
list<int> ilist(ia,ia+12);
list<int>::iterator il_iter=ilist.begin();
ilist.remove_if(binder2nd(modulus<int>(),2));
for(;il_iter!=ilist.end();il_iter++)
cout<<*il_iter<<"\n";
return 0;
}
但是编译有错
error C2955: 'binder2nd' : use of class template requires template argument list
该如何更正呢?
lucky_fox 评论于 2006-03-10 11:11
# re: 关于list容器的问题!  回复  更多评论    
这样好像又可以了
int ia[]={0,1,1,2,3,5,8,13,21,34,55,89,144,233};
list<int> ilist(ia,ia+14);
list<int>::iterator il_iter=ilist.begin();

int ct=ilist.size();
for(int i=0;i<ct;i+=2)
{

il_iter=ilist.erase(il_iter);
if(il_iter!=ilist.end())
il_iter++;
}
il_iter=ilist.begin();
for(;il_iter!=ilist.end();il_iter++)
cout<<*il_iter<<endl;
lucky_fox 评论于 2006-03-11 00:06
# re: 关于list容器的问题!  回复  更多评论    
比较好的解决方法是考虑利用erase的返回值来作为删除后下一元素的位置!
int ia[]={0,1,1,2,3,5,8,13,21,34,55,89,144};
list<int> ilist(ia,ia+12);
list<int>::iterator il_iter=ilist.begin();
for(int i=1;il_iter!=ilist.end();i++)
{

if(i%2!=0)
{
il_iter = ilist.erase(il_iter);
}
else
{
il_iter++;
}

}

il_iter=ilist.begin();

for(;il_iter!=ilist.end();il_iter++)
{
cout<<*il_iter<<"\n";
}

return 0;
lucky_fox 评论于 2006-03-12 23:18
 
刷新评论列表

只有注册用户登录后才能发表评论。
网站导航:   博客园   博客园最新博文   博问   管理