首页 理论教育 简化C++STL精解

简化C++STL精解

时间:2023-10-25 理论教育 版权反馈
【摘要】:同样,list模板类也是一个容器。list支持前后两种移动方向。list和vector类似,没有提供对元素的随机访问。list模板类是定义于命名空间std中的,该类模板的声明形式为:使用list需要包含头文件<list>。3)list不提供下标操作符[]和at()函数。list模板类实现了标准C++数据结构中链表的所有功能。下面介绍list类模板的使用方法。

简化C++STL精解

同样,list模板类也是一个容器。list是由双向链表来实现的,每个节点存储1个元素。list支持前后两种移动方向。list和vector类似,没有提供对元素的随机访问。list的优势在于任何位置执行插入和删除动作都非常迅速,因为改变的仅仅是链接而已,所以在list中移动元素要比在vector和deque中快得多。list模板类是定义于命名空间(name space)std中的,该类模板的声明形式为:

978-7-111-51399-5-Chapter03-50.jpg

使用list需要包含头文件<list>。前面已经讲过,任意型别T只要具备可设置性和可复制性,即可作为list元素。模板的第2个参数可有可无,适用于指定内存模型。在模板中,第2个参数被指定了默认的内存模型为allocator。list的内部结构和vector不同,存在较明显的区别。

1)list不支持随机存取。

2)在list的任何位置执行元素的插入和移除都非常快,可以迅速实现。插入和删除动作不会影响指向其他元素的指针、引用、迭代器,不会造成失效。

3)list不提供下标操作符[]和at()函数。

4)list没有提供容量、空间重新分配等操作函数,每个元素都有自己的内存。

5)list也提供了特殊成员函数,专门用于移动元素。和同名的算法相比,使用这些函数速度更快。

list模板类实现了标准C++数据结构中链表的所有功能。一旦list定义了类对象,就可以完成链表操作。

下面介绍list类模板的使用方法。

1.list的定义和容量

(1)list的定义和构造函数

list模板类描述的对象控制是一个元素类型为T的可变长度序列。该序列以双向链表的方式存储。链表中的每个元素都包含一个类型为T的成员。list对象是通过存储于其内部的一个类A的分配器对象进行存储空间的分配和释放。该分配器对象必须拥有和模板类alloca-tor同样的外部接口

头文件list中定义了四种构造函数。

978-7-111-51399-5-Chapter03-51.jpg

978-7-111-51399-5-Chapter03-52.jpg

以上四种构造函数中,第1个是默认的构造函数,表示要创建空list对象。根据以上构造函数,list对象定义时一般有以下几种方法:

978-7-111-51399-5-Chapter03-53.jpg

以上几种定义list类型对象的方法中,第一种形式最简单。第一种示例可以创建1个空的list对象,其中可以容纳类型为A的元素,其名称为listname。例如,

978-7-111-51399-5-Chapter03-54.jpg

而第二种示例可以创建初始大小为size的list对象;第三种示例可以创建初始大小为size,每个元素初始值为value的list对象;第四种示例用复制构造函数从现有的list中创建新的list对象;第五种方法创建1个list对象,并从其他list对象中复制由迭代器指定范围的多个元素。

(2)元素的赋值

list模板类提供了两个成员函数push_front()和push_back(),用来把新的元素插入到list对象中。push_front()函数用来在容器中的头部插入新元素,由于vector在头部插入新元素的效率非常低,因此仅在list中存在该函数。push_back()函数用于在容器的底部插入新元素。还有另外两个函数pop_front()和pop_back()分别用于获取容器的“头”元素和容器的“尾”元素。这4个函数的原型分别为:

978-7-111-51399-5-Chapter03-55.jpg

与vector类模板不同的是,vector模板类只具有push_back()和pop_back();而list模板类具有4个相应的函数,充分说明list类型容器是双向链表。

(3)容器的容量

list对象作为容器,和其容量相关的成员函数包括resize()、size()和max_size()。其函数原型分别为:

978-7-111-51399-5-Chapter03-56.jpg

size()和max_size()函数的返回类型均为size_type型,即unsignedint类型。size()函数用于返回list对象(容器)中元素的个数;max_size()函数用于返回list对象的最大允许容量(一般是一个非常大整数)。resize()函数用于重新调整list对象的大小。

(4)迭代器相关

list模板类所包含的和迭代器相关的函数主要有begin()、front()、rbegin()、end()、back()和rend()。这几个函数的原型分别为:

978-7-111-51399-5-Chapter03-57.jpg

下面以例3-12说明以上4个知识点的使用方法。通过实例学习,希望读者对list类型容器的最基本知识有所了解,理解list的定义、容量、赋值方法以及该容器中和迭代器相关的成员函数。

例3-12

978-7-111-51399-5-Chapter03-58.jpg

978-7-111-51399-5-Chapter03-59.jpg

注意:请关注第1行和3个不同的print()函数的使用方法。

978-7-111-51399-5-Chapter03-60.jpg

978-7-111-51399-5-Chapter03-61.jpg

978-7-111-51399-5-Chapter03-62.jpg

请关注例3-12源代码中的中文注释。例3-12还使用了模板函数和仿函数以及for_each()算法函数,还对list容器的大小进行了重新设置。为防止程序编译时显示过多的警告信息,在程序起始位置添加了“#pragma warning(disable:4786)”语句。读者应对照源代码和程序输出结果,认真理解其用法。

提示

①例3-12中,由于end()函数所指向的是容器中最后一个元素的后面的位置,因此程序中输出end()返回的数值时,并不是最后一个元素的数值。rend()函数同理。由此想到,在使用迭代器进行for循环时,一般循环终止条件是(iterator!=end()),即循环至end()返回值指向的位置时(恰好是容器中最后一个元素的后面),停止循环。

②Print_D(double&Ele)函数中,使用了cout的格式化输出。

例3-12的执行效果如图3-13所示。

2.list容器的基本成员函数

list容器最基本的应用包括判断list内容是否为空、元素的存取和访问、元素重置、交换两个list型容器的内容、元素的插入和删除等。

(1)判断容器是否为空

若list型容器中为空,则成员函数empty()返回true。empty()函数的原型为:

978-7-111-51399-5-Chapter03-63.jpg

978-7-111-51399-5-Chapter03-64.jpg

图3-13 例3-12的执行效果

例3-13

978-7-111-51399-5-Chapter03-65.jpg

由例3-13可知,使用empty()函数可以比较方便地判断容器是否为空。

(2)元素的存取和访问

list型容器不提供成员函数at()和操作符[],这对容器中的元素访问无疑是不便的。值得庆幸的是,还可以使用迭代器进行元素的访问。例如,

例3-14

978-7-111-51399-5-Chapter03-66.jpg

例3-14的执行效果如图3-14所示。

978-7-111-51399-5-Chapter03-67.jpg

图3-14 例3-14的执行效果

(3)元素重置

list型容器提供了可重置元素值的成员函数assign()。使用assign()函数可以修改容器中任意元素的数值,甚至可以修改多个连续元素的数值。assign()函数的原型为:

978-7-111-51399-5-Chapter03-68.jpg

下面举例说明其使用方法。

例3-15

978-7-111-51399-5-Chapter03-69.jpg

978-7-111-51399-5-Chapter03-70.jpg

例3-15涉及了assign()函数的两种用法。在需要初始化list型容器或者复制list型容器中的元素时,使用assign()函数是非常方便的。例3-15的执行效果如图3-15所示。

978-7-111-51399-5-Chapter03-71.jpg

图3-15 例3-15的执行效果

提示

学习assign()函数的使用方法时,请关注函数模板print()。关注一下即可,后面会专门讲解。

(4)交换两个list型容器的内容

list模板类提供了成员swap()函数,可用以实现两个list型容器(对象)的内容交换。通过swap()函数完成两个list型容器中内容交换的同时,两个参与交换的容器大小也发生变化。STL的算法库也提供了swap()函数。作为通用算法,swap()函数同样可以实现两个list型容器中的内容交换。如例3-16所示,在例3-15的基础上进行适当的修改,添加了用于容器内容交换的代码。

例3-16

978-7-111-51399-5-Chapter03-72.jpg

例3-16的执行效果如图3-16所示。

(5)元素的插入和删除

list模板类和其他型容器一样,提供了丰富而灵活的插入和删除元素操作函数。list型容器甚至可以在序列的开头和队尾灵活地插入和删除元素。涉及的成员函数主要包括push_back()、push_front()、pop_back()、pop_front()、insert()、erase()和clear()。push_front()和push_back()均可以轻松地将元素插入至序列中,pop_back()和pop_front()亦可以轻松地从首端或尾端将元素从序列中移除。这4个函数在前面章节已有涉及,此处不再赘述。本小节重点介绍insert()、erase()和clear()函数。

978-7-111-51399-5-Chapter03-73.jpg

图3-16 例3-16的执行效果

成员函数insert()的原型为如下3种型式:

978-7-111-51399-5-Chapter03-74.jpg

第一种形式的作用是把某个元素插入到指定位置;第二种形式的作用是把某个具体值的多个备份插入到list中迭代器所指的起始位置;第三种形式的作用是把指定范围的多个元素插入到list中迭代器所指的范围中。该成员函数的使用方法和vector型容器的同名成员函数类似。

成员函数erase()的原型为如下两种型式:

978-7-111-51399-5-Chapter03-75.jpg

同样,第一种形式的作用是删除迭代器指向的元素;第二种形式的作用是删除迭代器所定义的范围。该函数的使用方法和vector型容器的同名成员函数近似。

成员函数clear()相当于使用erase()函数删除序列中的所有元素,即erase(begin(),end())。修改例3-9,使之适用于list型容器。

例3-17

978-7-111-51399-5-Chapter03-76.jpg

978-7-111-51399-5-Chapter03-77.jpg

例3-17的执行效果如图3-17所示。

978-7-111-51399-5-Chapter03-78.jpg

图3-17 例3-17的执行效果

总结(www.xing528.com)

例3-17是在例3-9的基础上修改而来的(仅将头文件<vector>修改为<list>,将容器的定义语句vector<int>myvt修改为list<int>mylt)。请读者关注上述代码中的黑体字,并重点体会pop_font()、pop_back()、erase()和clear()的使用方法。

3.运算符函数

同样,list型容器也提供了大量的函数模板,即提供了大量运算符函数。常见的运算符函数包括operator ==、operator<、operator! =、operator<=、operator>、operator>=等。

(1)operator ==

operator[]主要用于读取list中的元素;而operator==则是判断语句,用于判断两个list型容器是否相等。如果相同,返回true;否则,返回false。其原型为:

978-7-111-51399-5-Chapter03-79.jpg

上述代码表明,参与比较的两个list对象的格式应该完全一样,不能使用两个类型不同的容器进行比较。

(2)operator<

operator<用于判断两个list型容器是否“前者小于后者”。如果“前者小于后者”,返回true;否则,返回false。其原型为:

978-7-111-51399-5-Chapter03-80.jpg

(3)operator!=

operator!=用于判断两个list型容器是否“不相等”。如果两者不同,返回true;否则,返回false。其原型为:

978-7-111-51399-5-Chapter03-81.jpg

(4)operator<=

operator<=用于判断两个list型容器是否“前者小于或等于后者”。如果“前者小于或等于后者”,返回true;否则,返回false。其原型为:

978-7-111-51399-5-Chapter03-82.jpg

(5)operator>

operator>用于判断两个list型容器是否“前者大于后者”,如果“前者大于后者”,返回true;否则,返回false。其原型为:

978-7-111-51399-5-Chapter03-83.jpg

(6)operator>=

operator>=用于判断两个list型容器是否“前者大于或等于后者”。如果“前者大于后者”,运算符返回true;否则,返回false。其原型为:

978-7-111-51399-5-Chapter03-84.jpg

下面使用例3-18说明以上6个运算符函数的使用方法。

例3-18

978-7-111-51399-5-Chapter03-85.jpg

978-7-111-51399-5-Chapter03-86.jpg

例3-18的执行效果如图3-18所示。

978-7-111-51399-5-Chapter03-87.jpg

图3-18 例3-18的执行效果

4.其他重要成员函数

list型容器具有一些特殊函数,如merge()、remove()、remove_if()、sort()、splice()和unique()。

(1)merge()函数和sort()函数

merge()函数可以将两个list型对象合并成一个list对象。该函数的功能在于把原型中的list型容器对象作为函数参数,插入到目标list(即函数的调用者)中。合并之后的容器中元素是按从小到大升序排列的。其原型为:

978-7-111-51399-5-Chapter03-88.jpg

list型容器还提供了具有排序功能的成员函数sort(),用于对list型容器中的元素进行排序。sort()函数默认的排序方式是从小到大。其原型为:

978-7-111-51399-5-Chapter03-89.jpg

以上两个函数的具体使用方法参见例3-19。

例3-19

978-7-111-51399-5-Chapter03-90.jpg

978-7-111-51399-5-Chapter03-91.jpg

在上述代码中,两个list型容器合并之后,新容器中的元素是自动排序的。

978-7-111-51399-5-Chapter03-92.jpg

上述语句实现了对容器中的元素降序排列。函数的参数“greater<int>()”是STL中的预定义仿函数。List型容器的成员函数sort()默认的排序方式是升序。

例3-19的执行效果如图3-19所示。

978-7-111-51399-5-Chapter03-93.jpg

图3-19 例3-19的执行效果

(2)remove()函数和remove_if()函数

删除list中的对象可以使用pop_back()、pop_front()、erase()、clear()等常见函数。List型容器还提供了remove()函数,其原型为:

978-7-111-51399-5-Chapter03-94.jpg

该函数可以删除list型容器中某个具体的元素。使用remove()函数不需要指定具体位置,只要告诉需要删除的元素的值,就可以直接删除所有相应的元素。这是和其他删除函数不同的地方。值得注意的是,使用remove()函数并不改变容器中元素的顺序。同时,由于在使用remove()函数时,会删除掉所有等于参数值(指定数值_Val)的元素,程序员要谨慎使用。

remove_if()函数是有条件地删除所有等于参数值的list型容器中的元素。其原型为:

978-7-111-51399-5-Chapter03-95.jpg

remove_if()函数仅删除满足条件“Pred pr”的相应元素。“Pred pr”参数是一元谓词。在Visual C++ 6.0中,Pred的定义如下:

978-7-111-51399-5-Chapter03-96.jpg

只有当Pred pr为true时,remove_if()函数才能正确执行删除操作。在visual C++ 6.0编译环境下,该函数的执行效果是删除和指定参数pr不相等的所有元素。请读者参考例3-20,仔细体会这两个删除函数的用法。

例3-20

978-7-111-51399-5-Chapter03-97.jpg

例3-20的执行效果如图3-20所示。

978-7-111-51399-5-Chapter03-98.jpg

图3-20 例3-20的执行效果

提示

学习remove()函数和remove_if()函数的同时,请读者详读这两个函数的功能。最重要的是关注成员函数remove_if()的使用。这不同于算法remove_if()的使用,算法remove_if()在使用时,要求更宽泛一些。

(3)splice()函数

前面讲述了容器合并成员函数merge()。该函数使用起来并不是非常灵活,反而具有一定的局限性。list型容器提供了另一个函数splice()。其原型为:

978-7-111-51399-5-Chapter03-99.jpg

第一种形式既可以把list型对象x插入在迭代器指针it指定的位置后面;第二种形式可以将x中的某个元素(first指向的元素)插入it的后面;第三种形式可以将x中某个范围(first,last)内的元素插入在it后面。值得注意的是,一旦合并完成,参数x中会减少相应数目的元素。因为这些元素已经转移走了。尤其是第一种形式,一旦合并函数splice()执行成功之后,参数x中将不包含任何元素——空容器。在例3-19的基础进行修改,使其可以体现splice()函数的用法。

例3-21

978-7-111-51399-5-Chapter03-100.jpg

978-7-111-51399-5-Chapter03-101.jpg

例3-21的执行效果如图3-21所示。

978-7-111-51399-5-Chapter03-102.jpg

图3-21 例3-21的执行效果

提示

学习list型容器的成员函数splice()时,要关注合并以后,参数x表示的容器中的元素数目。同时应该注意对于不同的编译系统,对于STL的容器模板可能略有差别。

(4)unique()函数

对于list型容器中存储的元素,可能存在连续的多个元素数值相等的情况,使用unique()函数会移除重复元素,仅留下1个。其原型有如下两种形式:

978-7-111-51399-5-Chapter03-103.jpg

splice()函数假定容器中元素是已排序的,因此所有相同的元素都是相邻的。不相邻的重复元素不能被移除。对于第二种函数形式,只有当元素满足条件表达式_Pred时,该元素才被删除。由以上可知,unique()函数并不能保证序列中元素值的唯一性,而仅仅是将相邻的重复元素保留一个。该函数的第二种原型:template<class BinaryPredicate>void unique(BinaryPredicate _Pred),其中参数BinaryPredicate _Pred在Visual C++ 6.0中其具体形式如下:

978-7-111-51399-5-Chapter03-104.jpg

所以,第二种形式所实现的功能是仅保留和第一个元素相等的元素,参数仅提供“函数”,即仅提供谓词。此时参数的实例化问题较突出。和前面的remove_if()函数有相似之处。

例3-22

978-7-111-51399-5-Chapter03-105.jpg

978-7-111-51399-5-Chapter03-106.jpg

例3-22的执行效果如图3-22所示。

978-7-111-51399-5-Chapter03-107.jpg

图3-22 例3-22的执行效果

提示

①使用list型容器的成员函数unique()时,尽量先使用sort()函数对容器中的元素进行排序;

②使用unique()函数的第二种形式时,参数仅是“谓词”,而不提供具体的数值!

(5)reverse()函数

reverse()函数可将容器中的所有元素与原来相反的顺序排列。其原型为:

978-7-111-51399-5-Chapter03-108.jpg

在使用reverse()函数时,不需要任何参数,仅调用即可。容器中的所有元素会按与原来相反的顺序排列。

总结

这里再次对list模板类做简要说明。list型容器是非常重要的一种模板类。遗憾的是,它没有提供操作符[]和成员函数at()。list型容器提供了很多可实现序列操作功能的函数。在学习以上内容的同时,读者应注意反复多读几遍,以充分掌握这些知识点。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈