标准库提供了一个名为list的双向链表:
如果我们希望在一个序列中添加和删除元素的同时无须移动其他元素,则应该使用ist。对电话簿应用而言,插入删除操作可能非常频繁,因此Iist适合保存电话簿。例如
list<Entry> phone_book={{"David Hume",123456},{"Karl Poppe",123456},{"breafas sdas",456789}};
当使用链表时,我们通常并不想像使用向量那样使用它,即,不会用下标操作来访问链表元素,而是想进行“在链表中搜索具有给定值的元素”这类操作。为了完成这样的操作,我们可以利用“list是序列”这样一个事实(如4.5节所述):
int get_number(const string& s){for(const auto& x:phone_book)if(x.name==s)return x.number;return 0; //用0表示"未找到所需值"}
这段代码从链表头开始搜索s,直至找到s或到达 phone book的末尾为止。
我们有时需要在lst中定位一个元素。例如,我们可能想删除这个元素或是在这个元素之前插入一个新元素。为此,我们需要使用迭代器( Iterator):一个list迭代器指向list中的一个元素,它可以用来遍历list(它也正是因此得名)。每个标准库容器都提供begin()和end()函数,分别返回一个指向首元素的迭代器和一个指向尾后位置的迭代器(见4.5节和33.1.1节)。我们可以改写get number()函数,令其显式地使用迭代器遍历list,当然这个版本显然稍微有些繁琐:
int get_number(const string& s){for(auto p=phone_book.begin();p!phone_book.end();++p){if(p->name==s)return p->number;return 0; //用0表示"未找到所需值"}}
使用范围for循环的 get number(函数更简练、更不容易出错,但实际上,迭代器版本差不多就是编译器最终实现范围for的方式。给定一个迭代器p,”p表示它所指向的元素,++p令p指向下一个元素。当p指向一个类且该类有一个成员m时,p->m等价于(*p).m。
向list中添加元素以及从list中删除元素都很简单:
void f(const Entry& ee,list<Entry>::iterator p,list<Entry>::iterator q){phone_book.insert(p,ee); //将ee添加到p指向的元素之前phone_book.erase(q); //删除q指向的元素}
31.3.7节会介绍更多关于 insert()和erase()的内容。
上面这些list的例子都可以写成等价的使用 vector的版本,而且令人惊讶(除非你了解机器的体系结构)的是,当数据量较小时, vector版本的性能会优于list版本。当我们想要的只是一个元素序列时,我们可以在 vector和list之间选择。除非你有充分的理由选择list,否则就应该使用 vector. vector无论是遍历(如find()和 count()性能还是排序和搜索(如sort()和 binary search()性能都优于list。
