C++初阶之一篇文章教会你list(理解和使用)(上)

简介: 在C++标准库中,std::list 是一个双向链表容器,用于存储一系列元素。与 std::vector 和 std::deque 等容器不同,std::list 使用链表的数据结构来组织元素,因此在某些操作上具有独特的优势和性能特点。以下是关于 std::list 的详细介绍:

什么是list

在C++标准库中,std::list 是一个双向链表容器,用于存储一系列元素。与 std::vectorstd::deque 等容器不同,std::list 使用链表的数据结构来组织元素,因此在某些操作上具有独特的优势和性能特点。以下是关于 std::list 的详细介绍:

特点和优势

双向链表结构:std::list 内部使用双向链表来存储元素。这意味着在插入和删除元素时,不会引起其他元素的内存重新分配和复制,因此在这些操作上具有常数时间复杂度。

插入和删除操作效率高: 由于链表结构,插入和删除元素的操作效率非常高。对于大量的插入和删除操作,std::list 往往比其他容器更具优势。

迭代器的稳定性:std::list 中,插入和删除元素不会使迭代器失效,除非删除的正是迭代器所指向的元素。这使得在遍历过程中进行插入和删除操作更加方便和安全。

空间占用:std::list每个元素都需要存储一个指向前后元素的指针,因此相对于数组型的容器,std::list 的空间占用会更高。

基本操作

push_back() push_front(): 在链表的末尾和开头插入元素。

pop_back() pop_front(): 删除链表的末尾和开头的元素。

insert(): 在指定位置插入元素。

erase(): 删除指定位置的元素。

begin()end(): 返回指向链表起始元素和尾部元素后一个位置的迭代器。

size(): 返回链表中的元素数量。

empty(): 检查链表是否为空。

clear(): 清空链表中的所有元素。

示例用法

#include <iostream>
#include <list>
int main() {
    std::list<int> myList;
    myList.push_back(1);
    myList.push_back(2);
    myList.push_back(3);
    myList.pop_front();
    myList.insert(std::next(myList.begin()), 4);
    for (const auto& num : myList) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

在使用 std::list 时,需要权衡其优势和劣势,根据实际场景来选择合适的容器。当需要频繁插入和删除元素,且不需要随机访问时,std::list 可能是一个很好的选择。但需要注意的是,由于链表的特性,std::list 并不适用于需要快速随机访问元素的场景,因为访问某个位置的元素需要遍历链表。

与其他序列式容器(如 std::vector 和 std::deque)相比,std::list 显著的区别和优势

优势:

插入和删除效率高: 由于 std::list 是双向链表,插入和删除操作在常数时间内完成,不需要涉及内存的重新分配和元素的复制。这使得 std::list 在大量插入和删除操作时非常高效。

迭代器的稳定性:std::list 的插入和删除操作不会使迭代器失效,除非删除的正是迭代器所指向的元素。这使得在遍历过程中进行插入和删除操作更加方便和安全。

空间占用相对稳定:std::list 的空间占用相对稳定,插入和删除操作不会影响其他元素的空间占用。

劣势:

不支持随机访问: 由于链表的结构,std::list 不支持像数组一样的随机访问。访问某个位置的元素需要从链表的开头或结尾开始遍历。

额外的指针开销:std::list 中的每个元素都需要存储指向前后元素的指针,这使得空间占用相对其他容器更高。

缓存效率低: 由于链表中元素在内存中的存储位置不连续,导致在访问链表元素时,缓存命中率较低,可能影响性能。

迭代器的使用限制:std::list 的迭代器不支持与普通指针类似的算术操作(如 + 和 -),因此无法像 std::vector 那样灵活地进行迭代器操作。

成员类型

list构造函数

当使用 std::list 类创建对象时,可以使用不同的构造函数来满足不同的初始化需求。下面详细介绍每个构造函数及其使用示例:

1. default (1)

这个构造函数用于创建一个空的 std::list 容器。它可以接受一个可选的分配器参数,用于指定内存分配策略。

std::list<int> myList; // 创建一个空的 std::list 容器

2. fill (2)

这个构造函数用于创建一个包含 n 个元素的 std::list 容器,并将这些元素初始化为 val。你可以通过传递不同的 val 值来创建一个包含相同值的容器。同样,也可以传递一个可选的分配器参数。

std::list<int> myList(5, 42); // 创建一个包含 5 个元素,每个元素都是 42 的 std::list 容器

3.range (3)

这个构造函数使用迭代器范围 [first, last) 中的元素创建一个 std::list 容器。这使你可以通过一个迭代器范围来初始化容器。同样,它也接受一个可选的分配器参数。

std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> myList(vec.begin(), vec.end()); // 从迭代器范围内的元素创建 std::list 容器

4. copy (4)

这个构造函数用于创建一个与已存在的 std::list 容器 x 相同的副本。它会将 x中的所有元素拷贝到新的容器中。这是一个拷贝构造函数。

std::list<int> originalList = {1, 2, 3, 4, 5};
std::list<int> copiedList(originalList); // 创建一个原容器的副本

这些构造函数提供了不同的初始化方式,以便根据具体需求来创建 std::list 容器。根据你的数据源和其他条件,选择适合的构造函数来创建容器对象。

list迭代器(Iterators)

1. begin()

iterator begin() noexcept;

这个版本的 begin() 返回一个迭代器,可以用于修改容器内的元素。noexcept 表示这个函数不会抛出异常。

std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::iterator it = myList.begin(); // 获取可修改元素的迭代器
*it = 10; // 修改第一个元素的值为 10

const_iterator begin() const noexcept;

这个版本的 begin() 返回一个只读的迭代器,用于在不修改容器的情况下访问元素。const 表示这个函数不会修改容器。

const std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::const_iterator cit = myList.begin(); // 获取只读元素的迭代器
int firstElement = *cit; // 读取第一个元素的值

2. end()

iterator end() noexcept;

这个版本的 end() 返回一个迭代器,可以用于修改容器内的元素。noexcept 表示这个函数不会抛出异常。这个迭代器指向的位置实际上是容器的末尾位置之后一个虚拟的位置,所以它并不指向容器内的任何元素。

std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::iterator it = myList.end(); // 获取可修改元素的迭代器
--it; // 将迭代器前移一个位置,指向最后一个元素
*it = 20; // 修改最后一个元素的值为 20

const_iterator end() const noexcept;

这个版本的 end() 返回一个只读的迭代器,用于在不修改容器的情况下访问元素。const 表示这个函数不会修改容器。同样,这个迭代器也指向虚拟的位置,容器内的最后一个元素之后。

const std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::const_iterator cit = myList.end(); // 获取只读元素的迭代器
--cit; // 将迭代器前移一个位置,指向最后一个元素
int lastElement = *cit; // 读取最后一个元素的值

3. rbegin()

reverse_iterator rbegin() noexcept;

这个版本的 rbegin() 返回一个反向迭代器,可以用于修改容器内的元素。noexcept 表示这个函数不会抛出异常。这个反向迭代器指向容器内的最后一个元素,可以通过递减操作符 -- 往前遍历容器。

std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::reverse_iterator rit = myList.rbegin(); // 获取可修改元素的反向迭代器
*rit = 10; // 修改最后一个元素的值为 10
++rit; // 将反向迭代器往前移动一个位置,指向倒数第二个元素

const_reverse_iterator rbegin() const noexcept;

这个版本的 rbegin() 返回一个只读的反向迭代器,用于在不修改容器的情况下访问元素。const 表示这个函数不会修改容器。这个反向迭代器同样指向最后一个元素,可以通过递减操作符 -- 往前遍历容器。

const std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::const_reverse_iterator crit = myList.rbegin(); // 获取只读元素的反向迭代器
int lastElement = *crit; // 读取最后一个元素的值
++crit; // 将反向迭代器往前移动一个位置,指向倒数第二个元素

4. rend()

reverse_iterator rend() nothrow;

这个版本的 rend() 返回一个反向迭代器,可以用于修改容器内的元素。nothrow 表示这个函数不会抛出异常。这个反向迭代器指向容器内的位置,位于第一个元素之前,可以通过递减操作符 -- 往前遍历容器。

std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::reverse_iterator rit = myList.rend(); // 获取可修改元素的反向迭代器
--rit; // 将反向迭代器往前移动一个位置,指向最后一个元素
*rit = 10; // 修改最后一个元素的值为 10

const_reverse_iterator rend() const nothrow;

这个版本的 rend() 返回一个只读的反向迭代器,用于在不修改容器的情况下访问元素。const 表示这个函数不会修改容器。这个反向迭代器同样指向容器的位置,位于第一个元素之前,可以通过递减操作符 – 往前遍历容器。

const std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::const_reverse_iterator crit = myList.rend(); // 获取只读元素的反向迭代器
--crit; // 将反向迭代器往前移动一个位置,指向最后一个元素
int lastElement = *crit; // 读取最后一个元素的值

5. cbegin()、cend()、crbegin()、crend()

const_iterator cbegin() const noexcept;

这个成员函数返回一个指向容器元素的常量迭代器,指向容器的起始位置。通过常量迭代器,你可以遍历容器的元素,但不能修改它们。

const_iterator cend() const noexcept;

这个成员函数返回一个指向容器元素的常量迭代器,指向容器的结束位置。这个迭代器表示一个超过容器尾部的位置,通常用于迭代器循环的终止条件。

std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::const_iterator cit = myList.cbegin(); // 获取常量迭代器
for (; cit != myList.cend(); ++cit) {
    std::cout << *cit << " "; // 输出容器中的元素,不修改它们
}

const_reverse_iterator crbegin() const noexcept;

这个成员函数返回一个指向容器元素的常量反向迭代器,指向容器的反向起始位置。通过常量反向迭代器,你可以反向遍历容器的元素,但不能修改它们。

const_reverse_iterator crend() const noexcept;

这个成员函数返回一个指向容器元素的常量反向迭代器,指向容器的反向结束位置。这个迭代器表示一个超过容器首部的位置,通常用于反向迭代器循环的终止条件。

std::list<int> myList = {1, 2, 3, 4, 5};
std::list<int>::const_reverse_iterator crit = myList.crbegin(); // 获取常量反向迭代器
for (; crit != myList.crend(); ++crit) {
    std::cout << *crit << " "; // 反向输出容器中的元素,不修改它们
}

list容量函数(Capacity)和元素访问函数(Element access)

1. empty()

empty()std::list 容器的一个成员函数,用于判断容器是否为空。它返回一个布尔值,表示容器是否不包含任何元素。函数声明如下:

bool empty() const noexcept;

返回值:如果容器为空,则返回 true,否则返回 false。

使用示例

#include <iostream>
#include <list>
int main() {
    std::list<int> myList;
    if (myList.empty()) {
        std::cout << "The list is empty." << std::endl;
    } else {
        std::cout << "The list is not empty." << std::endl;
    }
    myList.push_back(42);
    if (myList.empty()) {
        std::cout << "The list is empty." << std::endl;
    } else {
        std::cout << "The list is not empty." << std::endl;
    }
    return 0;
}

在上面的示例中,首先创建一个空的 std::list 容器 myList,然后使用 empty() 函数检查容器是否为空,并输出相应的信息。然后,通过 push_back 向容器中添加一个元素,并再次使用 empty() 函数检查容器是否为空,输出相应的信息。

2. size()

size()std::list 容器的一个成员函数,用于返回容器中元素的数量。它返回一个无符号整数类型,表示容器中的元素数量。函数声明如下:

size_type size() const noexcept;

返回值:返回容器中元素的数量,即大小。

使用示例

#include <iostream>
#include <list>
int main() {
    std::list<int> myList;
    myList.push_back(1);
    myList.push_back(2);
    myList.push_back(3);
    std::cout << "Size of the list: " << myList.size() << std::endl;
    return 0;
}

在上面的示例中,我们首先创建一个 std::list 容器 myList,然后使用 push_back 函数向容器中添加三个元素。然后使用 size() 函数获取容器的大小,并输出到标准输出。


相关文章
|
7天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
22 7
|
16天前
|
存储 编译器 C++
C++ initializer_list&&类型推导
在 C++ 中,`initializer_list` 提供了一种方便的方式来初始化容器和传递参数,而右值引用则是实现高效资源管理和移动语义的关键特性。尽管在实际应用中 `initializer_list&&` 并不常见,但理解其类型推导和使用方式有助于深入掌握现代 C++ 的高级特性。
16 4
|
2月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
62 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
2月前
|
存储 算法 C++
【C++打怪之路Lv10】-- list
【C++打怪之路Lv10】-- list
23 1
|
2月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
68 5
|
2月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
78 2
|
2月前
|
C++
【C++】C++ STL 探索:List使用与背后底层逻辑(三)
【C++】C++ STL 探索:List使用与背后底层逻辑
|
2月前
|
C++
【C++】C++ STL 探索:List使用与背后底层逻辑(二)
【C++】C++ STL 探索:List使用与背后底层逻辑
|
2月前
|
存储 编译器 C++
【C++】C++ STL 探索:List使用与背后底层逻辑(一)
【C++】C++ STL 探索:List使用与背后底层逻辑
|
2月前
|
存储 缓存 C++
C++番外篇——list与vector的比较
C++番外篇——list与vector的比较
26 0