C++(STL)的List解读

目录

list简介

list的几个特性

接口函数

1.默认成员函数

2.迭代器相关函数

3.容量相关的函数

4.成员访问相关的函数

5.modify系列

6.operation系列

7.重载在全局的函数


list简介

Lists are sequence containers that allow constant time insert and erase operations anywhere within the sequence, and iteration in both directions.

在C语言中,我们已经学过了一些基础的带哨兵位的双向链表,但是链表的实现比较“呆板”,因此C++中就出现了list。list是一个模板类,功能就类似双向链表。

list的几个特性

1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
其前一个元素和后一个元素。
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高
效。
4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率
更好。
5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list
的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间
开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这
可能是一个重要的因素)
下面我们就从list的几个接口函数来探索这些特点。

接口函数

1.默认成员函数

构造函数

构造函数主要是由三种构成:1)n个val      2)默认构造函数(全缺省)      3)用迭代器传参

	list<int> lt(10, 0);
	list<int> lt2;
	list<int> lt3(lt.begin(), lt.end());

拷贝构造

用于构造一个和原对象一样的“个体”

	list<int> lt(10, 0);
	list<int> lt2(lt);

赋值重载

起到赋值的作用

int main()
{
	list<int> lt(10, 0);
	list<int> lt2(10, 1);
	list<int> lt3(lt);

	lt3 = lt2;

	for (auto& ch : lt3)
	{
		cout << ch << " ";
	}

	cout << endl;
	return 0;
}

2.迭代器相关函数

主要是begin()、end()、rbegin()、rend()

迭代器的行为与指针相似,但不能说迭代器就等价于指针。迭代器可以用来遍历容器。

int main()
{
	list<int> lt(10, 0);
	list<int> lt2(10, 1);
	list<int> lt3(lt);

	lt3 = lt2;
	auto it = lt3.begin();
	
	while (it != lt3.end())
	{
		cout << *it << " ";
		++it;
	}

	cout << endl;
	return 0;
}

list的迭代器是双向迭代器,支持++、--、*(解引用)操作

rbegin和rend则是反向迭代器,可以将数据反向遍历。cbegin系列则是const修饰的迭代器。

但是cbegin系列实际上begin系列已经完成了const迭代器的重载

3.容量相关的函数

这部分函数主要是与容量相关的。但是不同于string和vector,他不含有reserve、capacity函数。因为list是由一个个节点构成,所以不存在扩容的概念。当需要额外的空间时,只需要额外获取节点即可。

1)empty

用来检测是否是空container

2)size

用来监视容量

3)max_size

用来检测能允许的最大空间。但是实践中意义不大,毕竟也没有工程会傻到用完所有空间。

4.成员访问相关的函数

front()函数返回第一个有效元素。

back()返回最后一个有效元素。

5.modify系列

这个系列主要是与增删查改有关。

1)assign函数

清除原先的空间、内容,并且重新分配内容,并给予适配的空间大小。

可以使用迭代器传参,也可以使用n个val传参。

2)头插、头删、尾插、尾删系列

主要是应用push_back 和 pop_back组合、push_front和pop_front组合

在插入时,只需要给出对应的数据。在删除时不需要传参。

3)insert

insert可以支持在任意位置插入数据,因此在传入参数时就需要传入迭代器用来指明对应的位置。

传入迭代器的位置之后,可以传入val、n个val和迭代器区间。

#include <iostream>
#include <list>

int main() {
    std::list<int> my_list = {1, 2, 3, 4};
    auto it = my_list.begin(); // 获取开始迭代器
    ++it; // 移动迭代器到第二个元素
    my_list.insert(it, 'a'); // 在第二个元素之前插入'a'
    for (const auto& elem : my_list) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
    return 0;
}
// 输出: 1 a 2 3 4

#include <iostream>
#include <list>

int main() {
    std::list<int> my_list = {1, 2, 3, 4};
    auto it = my_list.end(); // 获取结束迭代器
    my_list.insert(it, 3, 'b'); // 在列表末尾插入3个'b'
    for (const auto& elem : my_list) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
    return 0;
}
// 输出: 1 2 3 4 b b b

#include <iostream>
#include <list>

int main() {
    std::list<int> my_list = {1, 2, 3, 4};
    std::list<int> to_insert = {5, 6, 7};
    auto it = my_list.begin(); // 获取开始迭代器
    ++it; // 移动迭代器到第二个元素
    my_list.insert(it, to_insert.begin(), to_insert.end()); // 在第二个元素之前插入另一个列表的所有元素
    for (const auto& elem : my_list) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
    return 0;
}
// 输出: 1 5 6 7 2 3 4

4)erase函数

用来删除数据。可以删除某个数据,也可以删除某块区间的数据。

返回删除数据的下一个迭代器。

5)swap函数

用来交换两个list的空间和内容。

#include <iostream>
#include <list>

int main() {
    std::list<int> list1 = {1, 2, 3, 4};
    std::list<int> list2 = {5, 6, 7, 8};

    // 输出交换前的列表
    std::cout << "Before swap:" << std::endl;
    std::cout << "list1: ";
    for (const auto& elem : list1) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;

    std::cout << "list2: ";
    for (const auto& elem : list2) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;

    // 交换两个列表的内容和空间
    list1.swap(list2);

    // 输出交换后的列表
    std::cout << "After swap:" << std::endl;
    std::cout << "list1: ";
    for (const auto& elem : list1) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;

    std::cout << "list2: ";
    for (const auto& elem : list2) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;

    return 0;
}

执行结果: 

Before swap:
list1: 1 2 3 4 
list2: 5 6 7 8 
After swap:
list1: 5 6 7 8 
list2: 1 2 3 4 

6)resize

用来调整size的大小。

当n<size时,会减小size到n;

当n>size时,会增大到n,并且将后续的内容初始化为val。

7)clear

用来清除空间和内容。list是带哨兵位的双向链表,clear()函数并不会清除掉哨兵位。

6.operation系列

1)splice函数

这是splice函数的介绍。介绍中写道将一个list中的元素转移到另一个链表中。注意:该转移不是单纯数据的转移,而是体现在节点的转移。

可以挪动整个list,可以挪动list的某个元素,也可以挪动某一段区间。

Transfers elements from x into the container, inserting them at position.

1.单元素移动

将单个元素从另一个 list 移动到当前 list 的指定位置之前。

#include <iostream>
#include <list>

int main() {
    std::list<int> list1 = {1, 2, 3, 4};
    std::list<int> list2 = {5, 6, 7, 8};

    // 将 list2 中的第一个元素(5)移动到 list1 的第三个位置之前
    auto it = list1.begin(); // 获取 list1 的开始迭代器
    std::advance(it, 2); // 将迭代器向前移动两位,指向第三个位置
    list1.splice(it, list2, list2.begin()); // 移动操作

    // 输出结果
    for (int elem : list1) std::cout << elem << ' ';
    std::cout << std::endl;
    for (int elem : list2) std::cout << elem << ' ';
    std::cout << std::endl;

    return 0;
}

list2.begin() 是源迭代器,在 splice 操作后它将失效,因为它指向的元素已经被移动到 list1 中。it 是目标迭代器,它在操作后仍然有效,并且指向移动后的元素 3。

输出结果:

1 2 5 3 4 
6 7 8 
 

2)范围移动

将另一个 list 中的一个范围的所有元素移动到当前 list 的指定位置。

#include <iostream>
#include <list>

int main() {
    std::list<int> list1 = {1, 2, 3, 4};
    std::list<int> list2 = {5, 6, 7, 8};

    // 将 list2 中的前两个元素移动到 list1 的末尾
    list1.splice(list1.end(), list2, list2.begin(), ++std::list<int>::iterator(list2.begin()));

    // 输出结果
    for (int elem : list1) std::cout << elem << ' ';
    std::cout << std::endl;
    for (int elem : list2) std::cout << elem << ' ';
    std::cout << std::endl;

    return 0;
}

代码中,迭代器iterator是一个对象,因此采用了匿名对象传参的方式。

输出结果:

1 2 3 4 5 6 
7 8 
 

3)整个容器移动

#include <iostream>
#include <list>

int main() {
    std::list<int> list1 = {1, 2, 3, 4};
    std::list<int> list2 = {5, 6, 7, 8};

    // 将整个 list2 移动到 list1 的末尾
    list1.splice(list1.end(), list2);

    // 输出结果
    for (int elem : list1) std::cout << elem << ' ';
    std::cout << std::endl;
    // list2 现在为空
    for (int elem : list2) std::cout << elem << ' ';
    std::cout << std::endl;

    return 0;
}

在这个例子中,list2 的所有迭代器在 splice 操作后都将失效,因为整个 list2 的内容都被移动到了 list1 中。目标迭代器 list1.end() 在操作后仍然有效,并且指向新插入元素之后的元素。

输出:

1 2 3 4 5 6 7 8 
 

总结:

在 splice 操作后,如果是从源 list 移动单个元素或一个范围,则源 list 中被移动元素之前的迭代器仍然有效,而被移动元素及其之后的迭代器失效目标 list 的迭代器在操作后仍然有效。如果整个源 list 被移动,则源 list 的所有迭代器失效

2)remove函数

用来移除特定的元素

 

示例代码

#include <iostream>
#include <list>

int main() {
    // 创建一个list
    std::list<int> lst = {1, 2, 3, 4, 2, 5, 2};

    // 打印原始list
    std::cout << "原始list: ";
    for (int i : lst) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // 移除所有值为2的元素
    lst.remove(2);

    // 打印修改后的list
    std::cout << "修改后的list: ";
    for (int i : lst) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    return 0;
}

原始list: 1 2 3 4 2 5 2
修改后的list: 1 3 4 5
 

使用remove时,不需要进行sort(排序)

3)sort函数

用来完成排序工作。默认排序是升序

#include <iostream>
#include <list>
#include <algorithm> // 用于sort函数

int main() {
    // 创建一个list
    std::list<int> lst = {4, 1, 3, 5, 2};

    // 打印原始list
    std::cout << "原始list: ";
    for (int i : lst) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // 使用sort函数对list进行排序
    lst.sort();

    // 打印排序后的list
    std::cout << "排序后的list: ";
    for (int i : lst) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    return 0;
}

输出:

原始list: 4 1 3 5 2 
排序后的list: 1 2 3 4 5 
 

需要注意的是,sort在list有内置函数接口,在算法库中也有函数接口。

list内置:

算法库:

对于list对象,默认的迭代器是bidirectional迭代器(双向迭代器)

对于算法库中的sort,需要传入random迭代器(随机迭代器)

就迭代器而言,在功能上有const迭代器、非const迭代器、正向、反向迭代器;在性质上有随机迭代器、双向迭代器、单向迭代器。

就迭代器的权限而言:随机>双向>单项

随机支持:++ / -- / + / - 操作                (vector、string 、deque)

双向支持:++  / --                                       (list 、红黑树(map和set))

单向支持: ++                                         (单链表、哈希表)

因此随机迭代器可以用算法库(algorithm)函数中的sort,也可以用list中的sort

但是双向迭代器没法将权限上升到算法库中的sort,只能使用bidirectional专属的sort

sort内部也是消耗比较大的接口函数,因此sort函数尽量少用。

4)unique函数

用来完成去重工作。但是也有前提,即list必须是有序的。

示例代码:

#include <iostream>
#include <list>

int main() {
    // 创建一个list
    std::list<int> lst = {1, 2, 2, 3, 4, 4, 4, 5, 5, 6};

    // 打印原始list
    std::cout << "原始list: ";
    for (int i : lst) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // 使用unique函数移除连续的重复元素
    lst.unique();

    // 打印修改后的list
    std::cout << "修改后的list: ";
    for (int i : lst) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    return 0;
}

输出:

原始list: 1 2 2 3 4 4 4 5 5 6 
修改后的list: 1 2 3 4 5 6 
 

5)merge函数

merge是合并的意思,用来合并两个list对象

在C++标准模板库(STL)中,list容器的merge成员函数用于将两个已排序的list合并成一个新的已排序的list。这个函数特别适用于list,因为它可以高效地执行合并操作,而不需要额外的存储空间,因为它直接在现有的节点之间重新链接

以下是对merge函数的几个关键点的说明:

  1. 两个list都必须已排序:在调用merge之前,两个list必须根据相同的排序准则进行排序。

  2. 合并操作merge函数会将第二个list(参数x)中的所有元素合并到调用mergelist中。合并后的list仍然保持已排序状态。

  3. 效率:由于list的双向链表特性,合并操作是非常高效的。它不需要像数组或vector那样进行大量的元素移动。

  4. 第二个list的状态:在合并操作后,参数x指定的list变为空。

  5. 比较函数:如果不提供比较函数,则使用默认的operator<来比较元素。

示例:

#include <iostream>
#include <list>

int main() {
    std::list<int> lst1 = {1, 3, 5, 7};
    std::list<int> lst2 = {2, 4, 6, 8};

    // 确保两个list都已排序
    lst1.sort();
    lst2.sort();

    // 将lst2合并到lst1
    lst1.merge(lst2);

    // lst2现在应该是空的
    std::cout << "lst2的大小: " << lst2.size() << std::endl;

    // 打印合并后的lst1
    std::cout << "合并后的lst1: ";
    for (int i : lst1) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个例子中,我们首先创建了两个已排序的listlst1lst2。然后我们使用merge函数将lst2合并到lst1中。合并后,lst2变为空,而lst1包含了两个原始list的所有元素,并且仍然保持排序顺序。        

输出:

lst2的大小: 0
合并后的lst1: 1 2 3 4 5 6 7 8 
 

如果是乱序,采用merge函数之后,导致合并后的list也是乱序的。

std::list<int> lst1 = {5, 3, 1, 7};
std::list<int> lst2 = {8, 6, 4, 2};
如果我们直接合并这两个list:合并后的lst1将包含以下元素,且顺序是它们在原始list中的顺序:

输出:

5, 3, 1, 7, 8, 6, 4, 2

为了得到一个排序后的list,你需要在调用merge函数之前分别对两个list进行排序

6)reverse

用来逆置list

7.重载在全局的函数

第一部分是关系运算符的重载

第二部分是swap函数

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值