【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2

简介: 【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器

【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1:https://developer.aliyun.com/article/1617548

6.3 修改操作

通过迭代器或者 list 提供的访问接口,用户可以直接修改链表中的元素。由于 list 不支持随机访问,所以修改操作通常需要遍历元素。

方法名 功能描述
front() 返回 list 中第一个元素
back() 返回 list 中最后一个元素
迭代器 通过迭代器访问修改元素
6.3.1 示例:修改 list 中的首尾元素

通过 front()back(),可以分别访问并修改 list 中的第一个和最后一个元素。修改操作的时间复杂度为 O(1)。

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

int main() {
    list<int> lst = {1, 2, 3, 4, 5};

    // 修改第一个元素
    lst.front() = 10;

    // 修改最后一个元素
    lst.back() = 20;

    for (int val : lst) {
        cout << val << " ";  // 输出: 10 2 3 4 20
    }

    return 0;
}
6.3.2 示例:通过迭代器修改 list 中的元素

由于 list 不支持随机访问,修改中间位置的元素需要通过迭代器遍历找到目标位置。

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

int main() {
    list<int> lst = {1, 2, 3, 4, 5};

    // 使用迭代器修改第三个元素
    auto it = lst.begin();
    advance(it, 2);  // 移动到第三个元素
    *it = 30;

    for (int val : lst) {
        cout << val << " ";  // 输出: 1 2 30 4 5
    }

    return 0;
}
6.3.3 修改操作的常见问题
  • 效率问题:由于 list 是链表结构,访问中间元素时无法像 vector 一样通过下标随机访问,而是必须通过迭代器进行遍历,时间复杂度为 O(n)。
  • advance() 函数用于将迭代器向前或向后移动指定的距离,这是 list 中最常用的访问与修改元素方式之一。由于 list 不能通过下标随机访问,迭代器的使用显得尤为重要。
  • 避免无效访问:通过迭代器进行修改时,确保在修改过程中没有删除操作,否则迭代器可能失效,导致未定义行为。

第七章:list 的迭代器失效问题

list 的底层实现为双向链表,因此与 vector 不同,list 的插入和删除操作不会导致整体迭代器失效。具体来说:

  • 插入操作不会导致现有迭代器失效。
  • 删除操作仅导致被删除元素的迭代器失效,其他迭代器不会受影响。
7.1 删除操作导致的迭代器失效

删除操作会使指向被删除元素的迭代器失效,如果在删除元素后继续使用失效的迭代器,将会导致程序的未定义行为。因此,在执行删除操作后,我们必须重新更新迭代器。

7.1.1 示例:删除元素时正确的迭代器处理
#include <iostream>
#include <list>
using namespace std;

int main() {
    list<int> lst = {1, 2, 3, 4, 5};

    // 查找并删除元素3
    auto it = lst.begin();
    while (it != lst.end()) {
        if (*it == 3) {
            it = lst.erase(it);  // 删除元素并获取下一个有效迭代器
        } else {
            ++it;  // 继续遍历
        }
    }

    for (int val : lst) {
        cout << val << " ";  // 输出: 1 2 4 5
    }

    return 0;
}

在上面的代码中,erase() 函数会返回一个指向被删除元素之后的迭代器,因此我们使用该返回值继续遍历。这是一种常见的迭代器删除操作的最佳实践,可以避免迭代器失效问题。

7.1.2 错误示例:删除后不更新迭代器
#include <iostream>
#include <list>
using namespace std;

int main() {
    list<int> lst = {1, 2, 3, 4, 5};

    auto it = lst.begin();
    while (it != lst.end()) {
        if (*it == 3) {
            lst.erase(it);  // 删除元素,但未更新迭代器
            ++it;           // 错误:it 已经失效,导致未定义行为
        } else {
            ++it;
        }
    }

    return 0;
}


在这个错误的示例中,删除操作使 it 失效,但我们在下一个循环中继续使用了失效的 it,这会导致未定义行为,可能会引发程序崩溃。

7.1.3 相关文档

第八章:list 常见的其他修改操作

8.1 splice() 操作

splice()list 特有的操作,它允许我们将一个 list 中的元素直接拼接到另一个 list 中,而不会重新分配内存或复制元素。该操作非常高效,因为它仅修改链表的指针。

方法名 功能描述
splice(position, x) list x 的所有元素插入到当前 list
splice(position, x, it) list x 中的 it 指定的元素插入到当前 list
splice(position, x, first, last) x 中 [first, last) 区间的元素插入当前 list
8.1.1 示例:使用 splice() 操作
#include <iostream>
#include <list>
using namespace std;

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

    // 将 lst2 的元素拼接到 lst1 的末尾
    lst1.splice(lst1.end(), lst2);

    for (int val : lst1) {
        cout << val << " ";  // 输出: 1 2 3 4 5 6
    }

    cout << "\nList 2 size: " << lst2.size() << endl; // 输出: 0 (lst2 已被清空)

    return 0;
}

splice() 可以高效地将一个链表中的元素移动到另一个链表中,它不会复制元素,也不会破坏链表的连续性。

8.1.2 相关文档
8.2 merge() 操作

merge() 函数用于将两个已经排序好的 list 合并为一个有序的 list。它会自动按照升序或自定义的比较规则合并两个链表。

方法名 功能描述
merge(list& x) 将已排序的 x 合并到当前链表中
merge(list& x, Compare comp) 使用自定义比较函数 comp 合并 x
8.2.1 示例:使用 merge() 操作
#include <iostream>
#include <list>
using namespace std;

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

    // 合并两个已排序的链表
    lst1.merge(lst2);

    for (int val : lst1) {
        cout << val << " ";  // 输出: 1 2 3 4 5 6
    }

    return 0;
}

merge() 会将两个有序链表合并成一个新的有序链表,并且不会对原链表进行元素的复制,只是对链表节点进行了重新连接。

8.2.2 相关文档

第九章:list 的排序与去重

9.1 sort() 操作

list 提供了 sort() 函数来对链表进行排序。由于 list 不支持随机访问,因此它使用的排序算法是稳定的归并排序,性能为 O(N log N)。

方法名 功能描述
sort() 默认按照升序排序
sort(Compare comp) 使用自定义比较函数 comp 进行排序
9.1.1 示例:对 list 进行排序
#include <iostream>
#include <list>
using namespace std;

int main() {
    list<int> lst = {5, 2, 9, 1, 5, 6};

    // 对链表进行排序
    lst.sort();

    for (int val : lst) {
        cout << val << " ";  // 输出: 1 2 5 5 6 9
    }

    return 0;
}
9.1.2 使用自定义比较函数排序
#include <iostream>
#include <list>
using namespace std;

bool customCompare(int a, int b) {
    return a > b;  // 降序比较
}

int main() {
    list<int> lst = {5, 2, 9, 1, 5, 6};

    // 使用自定义比较函数进行降序排序
    lst.sort(customCompare);

    for (int val : lst) {
        cout << val << " ";  // 输出: 9 6 5 5 2 1
    }

    return 0;
}
9.2 unique() 操作

unique() 函数用于去除链表中相邻的重复元素。它会比较相邻的两个元素,如果它们相等,则删除后一个元素。

方法名 功能描述
unique() 移除相邻的重复元素
unique(BinaryPredicate p) 使用自定义的比较规则 p 移除相邻的元素
9.2.1 示例:使用 unique() 去重
#include <iostream>
#include <list>
using namespace std;

int main() {
    list<int> lst = {1, 1, 2, 3, 3, 4, 5, 5};

    // 去除相邻的重复元素
    lst.unique();

    for (int val : lst) {
        cout << val << " ";  // 输出: 1 2 3 4 5
    }

    return 0;
}
9.2.2 使用自定义规则去重
#include <iostream>
#include <list>
using namespace std;

bool customEqual(int a, int b) {
    return a % 2 == b % 2;  // 自定义规则:移除相邻的偶数/奇数
}

int main() {
    list<int> lst = {1, 3, 2, 4, 5, 6};

    // 使用自定义规则去重
    lst.unique(customEqual);

    for (int val : lst) {
        cout << val << " ";  // 输出: 1 2 5
    }

    return 0;
}


第十章:list 的其他操作

10.1 reverse() 操作

reverse() 函数用于将 list 的顺序进行反转。该操作不会创建新的链表,而是直接修改现有链表的链接顺序。

方法名 功能描述
reverse() list 中的元素顺序反转
10.1.1 示例:反转 list 中的元素
#include <iostream>
#include <list>
using namespace std;

int main() {
    list<int> lst = {1, 2, 3, 4, 5};

    // 反转 list 中的元素
    lst.reverse();

    for (int val : lst) {
        cout << val << " ";  // 输出: 5 4 3 2 1
    }

    return 0;
}

通过 reverse() 函数,原本顺序存储的元素将被反转,链表中的第一个元素变为最后一个,最后一个变为第一个。

10.1.2 相关文档
10.2 swap() 操作

swap() 函数用于交换两个 list 容器的内容。这个操作非常高效,因为 list 只交换内部的指针和相关数据,而不会实际移动或复制元素。

方法名 功能描述
swap(list& x) 交换当前 listx 中的元素
10.2.1 示例:交换两个 list 的内容
#include <iostream>
#include <list>
using namespace std;

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

    // 交换两个 list
    lst1.swap(lst2);

    cout << "List 1: ";
    for (int val : lst1) {
        cout << val << " ";  // 输出: 4 5 6
    }

    cout << "\nList 2: ";
    for (int val : lst2) {
        cout << val << " ";  // 输出: 1 2 3
    }

    return 0;
}

swap() 是一种非常高效的操作,尤其是在需要大量数据交换时,可以避免拷贝开销。

11.2.2 相关文档


10.3 remove() 操作

remove() 函数用于从 list 中移除所有与指定值相等的元素。它会遍历整个链表,删除所有匹配的元素。

方法名 功能描述
remove(const T& val) 删除所有与 val 相等的元素
10.3.1 示例:移除指定值的元素
#include <iostream>
#include <list>
using namespace std;

int main() {
    list<int> lst = {1, 2, 3, 4, 2, 5};

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

    for (int val : lst) {
        cout << val << " ";  // 输出: 1 3 4 5
    }

    return 0;
}

remove() 函数会移除链表中所有等于指定值的元素。由于链表是双向的,这种操作不会导致大量的数据移动,只是修改指针指向。

10.3.2 相关文档
10.4 remove_if() 操作

remove_if() 函数根据给定的条件(谓词)移除链表中符合条件的所有元素。与 remove() 不同,它可以使用自定义的判断规则来删除元素。

方法名 功能描述
remove_if(UnaryPredicate p) 移除所有满足谓词 p 条件的元素
10.4.1 示例:使用 remove_if() 删除符合条件的元素
#include <iostream>
#include <list>
using namespace std;

// 判断条件:删除所有偶数
bool isEven(int n) {
    return n % 2 == 0;
}

int main() {
    list<int> lst = {1, 2, 3, 4, 5, 6};

    // 删除所有偶数元素
    lst.remove_if(isEven);

    for (int val : lst) {
        cout << val << " ";  // 输出: 1 3 5
    }

    return 0;
}

在这个例子中,remove_if() 根据自定义的谓词函数 isEven() 删除了链表中所有的偶数元素。

10.4.2 相关文档
10.5 emplace()emplace_back() 操作

emplace()emplace_back()list 提供的构造元素的方法,它们允许我们直接在链表中构造元素,避免不必要的复制操作。相比 push_back()emplace_back() 更加高效,尤其是在插入复杂对象时。

方法名 功能描述
emplace(position, args...) 在指定位置直接构造元素
emplace_back(args...) 在链表末尾直接构造元素,避免复制构造开销
10.5.1 示例:使用 emplace()emplace_back()
#include <iostream>
#include <list>
using namespace std;

struct Point {
    int x, y;
    Point(int a, int b) : x(a), y(b) {}
};

int main() {
    list<Point> points;

    // 在 list 中直接构造元素
    points.emplace_back(1, 2);  // 在末尾构造元素 (1, 2)
    points.emplace(points.begin(), 3, 4);  // 在起始位置构造元素 (3, 4)

    for (const auto& pt : points) {
        cout << "(" << pt.x << ", " << pt.y << ") ";  // 输出: (3, 4) (1, 2)
    }

    return 0;
}

emplace()emplace_back() 提供了更灵活和高效的插入方式,尤其在处理复杂对象时可以减少额外的构造和复制操作。

10.5.2 相关文档

第十一章:list 的内存管理

11.1 shrink_to_fit() 操作

list 不像 vector 那样需要经常处理容量管理和扩容问题,因为它的底层实现是链表,元素的插入和删除并不会影响容器的容量分配。但 STL 容器通常提供 shrink_to_fit() 函数来缩减不必要的内存开销,而 list 没有此函数,因为链表结构本身并不涉及到多余的容量分配问题。

写在最后

本文详尽介绍了 C++ STL 中 list 容器的各类操作。我们从基本的构造、元素访问、容量管理,到迭代器、修改操作、排序与去重等高级功能,深入讲解了如何使用 list 实现高效的插入、删除和操作。同时我们也讨论了 list 特有的操作如 splice()、merge()、remove() 等。


在 C++ 中,list 作为双向链表,非常适合频繁插入和删除元素的场景,但它不支持随机访问,这与 vector 的应用场景有所不同。在实际开发中,可以根据需要选择合适的容器来优化性能和提高程序的可读性。


💬 欢迎讨论:如果你有任何问题或建议,请在评论区留言。


👍 支持一下:如果你觉得这篇文章对你有帮助,请点赞、收藏并分享!你们的支持是我持续创作的动力!


以上就是关于【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

目录
打赏
0
8
8
0
51
分享
相关文章
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
59 2
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
144 73
4步实现C++插件化编程,轻松实现功能定制与扩展(2)
本文是《4步实现C++插件化编程》的延伸,重点介绍了新增的插件“热拔插”功能。通过`inotify`接口监控指定路径下的文件变动,结合`epoll`实现非阻塞监听,动态加载或卸载插件。核心设计包括`SprDirWatch`工具类封装`inotify`,以及`PluginManager`管理插件生命周期。验证部分展示了插件加载与卸载的日志及模块状态,确保功能稳定可靠。优化过程中解决了动态链接库句柄泄露问题,强调了采纳用户建议的重要性。
55 9
4步实现C++插件化编程,轻松实现功能定制与扩展(2)
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
71 3
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
108 1
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
10月前
|
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
1154 1
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等