C++ STL List 基本接口使用方法详解与构造方法介绍
LIST

之前的博客已经讲解了 c++ STL 里的 string 以及 vector 基本接口的使用方式,并且还模拟实现了这些接口。接下来,我们开始学习 list 的基本接口使用方法吧。
List 基本接口介绍 前言
List 是一个带头结点的双向循环链表,已知头节点 _head 能够找到它的前驱节点(尾节点)以及后继节点(首节点 - 第一个节点)。然而,在寻找中间的某个节点信息时,就需要从 _head 位置开始进行遍历。
list 构造方法
本博客介绍的是 C++98 下的构造方法:
(1)构造一个空链表
使用指定的分配器(const allocator_type& alloc = allocator_type())创建一个列表。
(2)构造有 n 个值为 val 的链表
列出 (大小类型 n,具有值类型 & val = 值类型(),)
获取分配器类型的引用 alloc,其值为 allocator_type() 的返回值;
(3)区间构造方法
template < class InputIterator >
从 first 这个输入迭代器开始,到 last 这个输入迭代器结束进行遍历,逐个对元素进行操作。首先获取 first 指向的元素,然后判断是否到达 last 位置,如果未到达则对该元素进行相应处理,接着移动 first 到下一个元素继续上述过程,直到到达 last 位置为止。
const allocator_type& alloc = allocator_type());
(4)拷贝构造
list (const list& x);
为了便于后续的接口测试,我们来自定义一个打印函数:
template
void PrintMyList(const list& L)
{
for (auto e : L)
cout << e << " ";
cout << endl;
}
接下来看看如何进行使用这些构造接口:
void my_test0()
{
list L1;
list L2(5, 3);
vector v{ 1,2,3,4,5,6,7,8 };
list L3(v.begin(), v.end());
list L4(L3);
}
list 析构方法
在使用库中的接口信息,链表使用结束是会自动进行销毁操作的。
容量相关
元素获取
迭代器
(1)正向迭代器
在 vector 中进行回顾时,我们提及迭代器的作用是便于获取容器中间的所有元素。基于此,在正向迭代器中,begin()所指向的是第一个元素的位置,而 end()所指向的是最后一个有效元素的下一个空间的位置。
在 list 中,因为 list 是带头双向循环链表,所以把 begin() 设定在首节点的位置,把 end() 设定在最后一个有效元素的下一个空间的位置,也就是头节点的位置。
(2)反向迭代器
与正向迭代器刚好相反:
我们来测试一下遍历 list 链表:
元素的修改
(1)尾插尾删、头插头删接口测试
(2)任意位置插入接口测试
insert
在 pos 位置插入单个值为 val 的元素:
该函数用于在指定位置插入一个元素。它接受一个迭代器 position 表示要插入的位置,以及一个常量引用 val 表示要插入的值。通过调用这个函数,可以在容器的特定位置添加一个新的元素。
1)insert 在第一个元素之前插入新元素:
从图中能看出,在链表的头部插入元素能够成功。同时,打印链表信息也是可行的。
回顾
在 vector 容器里,我们提及了“迭代器失效”这一情况,即因为插入元素或者删除元素,使得容器的底层空间发生变化,进而导致迭代器所指向的位置失效。那么,在 list 中是否也会出现迭代器失效的情况呢?我们可以通过打印迭代器所指向位置的元素来进行尝试:
从打印信息得知,在 pos 位置之前插入新元素时,迭代器没有失效。
2)insert 在尾部插入新元素:
3)insert 在指定元素之前插入新元素:
在此段代码里我们运用了查找算法,要对 list 链表从开头开始进行遍历。
int val=4; //要进行查找的元素
auto pos = L1.begin();
while (pos != L1.end())
{
if (*pos == val) //找到该元素位置
break;
++pos;
}
假如代码里有多处查找操作,若使用普通的遍历操作,会让代码效率大幅降低,还会导致代码冗余。所以在 C++ 库中为我们提供了一个全局的查找接口 find,这个接口能在 vector 容器或 list 链表中进行查找操作。
template
在给定的范围 [first, last] 中查找值为 val 的元素,返回一个指向该元素的 InputIterator 迭代器;如果未找到,则返回 last。
因此上述查找我们直接可以使用一行代码来进行替换:
在 L1 的.begin() 到 L1 的.end() 这个范围内去查找 4 ,找到的位置赋值给 auto 类型的 pos 。
在 pos 位置插入 n 个值为 val 的元素:
插入元素的函数,其参数包括迭代器位置、元素个数以及要插入的值。它可以在指定的迭代器位置处插入指定个数的指定值元素。具体来说,第一个参数是迭代器位置,用于指定插入的位置;第二个参数是要插入的元素个数;第三个参数是要插入的元素值。通过这个函数,可以方便地在容器中插入指定数量的特定元素。
在 pos 位置插入某个给定区间中的元素
template < class InputIterator>
插入元素到指定位置,插入的元素范围由两个输入迭代器指定,一个起始迭代器和一个结束迭代器。可以将元素插入到容器中的指定位置。具体来说,通过给定的起始迭代器和结束迭代器所确定的范围内的元素将被插入到由指定位置的迭代器所指示的位置之前。
(3)在任意位置删除接口测试
删除某个位置元素:
iterator 执行删除操作(iterator 位置);
可以发现,删除元素会导致删除位置的迭代器失效。然而,后续的元素能够正常进行打印。在上一篇博客中我们知道,在 vector 容器中插入或删除元素都会导致迭代器失效,并且在操作(插入或删除)之后无法获取到后续元素。而 list 只有在删除元素时才会导致迭代器的失效。
为避免发生迭代器失效,我们只需将删除后的元素位置进行返回就可以了。
删除区间中元素:
iterator 执行删除操作,删除从 first 到 last 范围内的元素;iterator 用于指定要删除的元素范围的起始和结束位置;通过调用 iterator 的 erase 函数来实现元素的删除操作。
(4)swap 交换接口测试
list L1{ 1,2,3,4,5,6 };
list L2{ 9,8,7,6,5 };
L1.swap(L2);
(5)resize 接口测试
修改有效元素的个数并进行 resize 操作,用 val 来填充多余的空间。
定义一个名为 resize 的函数,它有一个参数 n 为 size_type 类型,还有一个可选参数 val 默认为 value_type()类型的值;这个函数用于调整某种数据结构的大小为 n ,并可以用 val 来填充新增加的元素。
(6)clear 接口测试
清空所有有效元素:
其他相关操作
(1)remove 移除所有值为 val 的元素
从某个数据容器中把值 val 给去除掉 ;
(2)remove_if 移除满足条件的元素
template < class Predicate>
该函数用于移除满足特定条件的元素;它接受一个谓词作为参数;通过这个谓词来判断元素是否应该被移除;如果元素满足谓词条件,则将其从容器中移除。
(3)sort 对 list 中元素进行排序
sort 默认是按照升序进行排序的:
若想要指定排序方式,我们可以自定义一个函数:
//定义排序方式函数
判断是否满足 My_Compare 条件,即比较两个整数 left 和 right 的情况。如果满足比较条件则返回 true,不满足则返回 false。
{
//return left < right; // 小于方式---------升序排序
return left > right; //大于方式----------降序排序
}
(4)merge 合并两个链表
默认方式进行合并
void merge (list& x);
当调用 merge 接口时,我们发现触发了异常。这主要是由于编译器不知道以何种方式对这两个链表进行合并。所以,在进行链表合并操作之前,我们需要先对链表进行一个排序操作。
自定义合并链表的方式(降序为例)
template < class Compare>
这个函数名为 merge,它接受一个 list 类型的参数 x 和一个名为 comp 的比较函数,用于合并相关数据。
//定义排序方式函数
bool My_Compare(int left, int right)
{
//return left < right; // 小于方式---------升序排序
return left > right; //大于方式----------降序排序
}
(5)reverse 链表的逆置
直接调用接口即可:
关于 list 库中接口的测试就到此结束啦。小伙伴们只有自己动手去测试,才能够理解得更深刻呢。
下一节我们来介绍与 list 相关接口的模拟实现。(提示:要注意理解迭代器的使用内涵。)
敲黑板:
多练多理解,多试错多调试才能够进步,加油吧!