C++入门——08list

发布于:2024-08-18 ⋅ 阅读:(192) ⋅ 点赞:(0)

1.std::list

学习文档:cplusplus.com/reference/list/list/?kw=list

  • 底层数据结构:双向链表,每个节点包含指向前一个和后一个节点的指针。
  • 迭代:支持双向迭代(前向和后向)。
  • 操作效率:在任意位置的插入和删除操作效率较高,因为这些操作不涉及数据的移动。
  • 随机访问:不支持随机访问,访问特定位置的元素需要线性时间。
  • 额外开销:每个节点需要额外的指针存储开销,这可能在存储大量小数据类型时带来额外的空间开销。

2.list常用接口

  • list()无参构造函数

// 无参构造函数
std::list<int> l1;
  • list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素

// 填充构造函数
std::list<int> l2(5, 10); // l2: [10, 10, 10, 10, 10]
  • list (const list& x) 拷贝构造函数

// 拷贝构造函数
std::list<int> l3(l2); // l3 是 l2 的副本
  • list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造list

// 范围构造函数
    std::vector<int> vec{1, 2, 3, 4, 5};
    std::list<int> l4(vec.begin(), vec.end()); // l4: [1, 2, 3, 4, 5]
  • beginend,begin返回指向 list 中第一个元素的迭代器,end返回指向 list 中最后一个元素的下一个位置的迭代器。iterator(非 const 迭代器)或 const_iteratorconst 迭代器,取决于 list 对象是否为 const)。

begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

std::list<int> l{1, 2, 3, 4, 5};

// 使用 begin 和 end 遍历 list
std::cout << "Forward iteration:" << std::endl;
for (auto it = l.begin(); it != l.end(); ++it) {
    std::cout << *it << " ";
}
std::cout << std::endl;


//Forward iteration: 1 2 3 4 5 
  • rbeginrend,rbegin返回指向 list 中最后一个元素的反向迭代器(即逆序开始位置)。rend返回指向 list 中第一个元素之前的位置的反向迭代器(即逆序结束位置)。

rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

std::list<int> l{1, 2, 3, 4, 5};

// 使用 rbegin 和 rend 遍历 list
std::cout << "Reverse iteration:" << std::endl;
for (auto rit = l.rbegin(); rit != l.rend(); ++rit) {
    std::cout << *rit << " ";
}
std::cout << std::endl;
//Reverse iteration: 5 4 3 2 1

  • empty 检测list是否为空,是返回true,否则返回false

std::list<int> l{1, 2, 3, 4, 5};

// 检查 list 是否为空
if (l.empty()) {
   std::cout << "The list is empty." << std::endl;
} else {
   std::cout << "The list is not empty." << std::endl;
}
  • size 返回list中有效节点的个数

std::list<int> l{1, 2, 3, 4, 5};
// 输出 list 中的元素数量
std::cout << "The size of the list is: " << l.size() << std::endl;  
//The size of the list is: 5

  • front 返回list的第一个节点中值的引用

std::list<int> l{1, 2, 3, 4, 5};

// 获取并打印第一个元素
if (!l.empty()) {
   std::cout << "The first element is: " << l.front() << std::endl;
}
//The first element is: 1
  • back 返回list的最后一个节点中值的引用

std::list<int> l{1, 2, 3, 4, 5};
// 获取并打印最后一个元素
if (!l.empty()) {
   std::cout << "The last element is: " << l.back() << std::endl;
}
//The last element is: 5
  • push_front 在list首元素前插入值为val的元素

std::list<int> l;
// 使用 push_front 和 push_back
l.push_front(1);
l.push_front(0);
// 0 1
  • pop_front 删除list中第一个元素

l.pop_front();  // 删除 0
// 1
  • push_back 在list尾部插入值为val的元素

l.push_back(2);
l.push_back(3);
// 1 2 3



void push_back(const value_type& val);
void push_back(value_type&& val);
  • pop_back 删除list中最后一个元素

l.pop_back();   // 删除 3
// 1 2 
  • insert 在list position 位置前插入值为val的元素

// 使用 insert
auto it = l.begin();
++it;
l.insert(it, -1); // 在第二个位置前插入 -1
// 1 -1 2



//在 list 中指定位置 position 前插入一个或多个元素。
iterator insert(const_iterator position, const value_type& val);
iterator insert(const_iterator position, value_type&& val);
iterator insert(const_iterator position, size_type count, const value_type& val);
template <class InputIterator>
iterator insert(const_iterator position, InputIterator first, InputIterator last);
  • erase 删除list position位置的元素

// 使用 erase
it = l.begin();
l.erase(it);    // 删除 1
//-1 2


//删除 list 中指定位置 position 的元素或指定范围 [first, last) 的元素。
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);
  • swap 交换两个list中的元素

std::list<int> l2{10, 20, 30};
l.swap(l2);     // 交换 l 和 l2 的内容
//10 20 30   
  • clear 清空list中的有效元素

 l.clear();      // 清空 l
// 

3.list的迭代器失效

将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

  1. 删除操作后的迭代器

    • 使用 erase 删除的元素的迭代器会失效,但其他迭代器不会受到影响。在删除操作后,确保更新或重新获得需要的迭代器。
  2. 插入操作后的迭代器

    • insert 操作不会使现有迭代器失效,因此可以在插入操作之后继续使用原来的迭代器进行遍历。
void TestListIterator1()
{
    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    list<int> l(array, array+sizeof(array)/sizeof(array[0]));
    auto it = l.begin();
    while (it != l.end())
    {
        // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
        l.erase(it);
        ++it;
     }
}


// 改正
void TestListIterator()
{
    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    list<int> l(array, array+sizeof(array)/sizeof(array[0]));
    auto it = l.begin();
    while (it != l.end())
    {
        l.erase(it++); // it = l.erase(it);
    }
}

4.list与vector的对比,跟顺序表和链表类似

特性 std::vector std::list
底层结构 动态顺序表,底层为一段连续的内存空间 带头节点的双向链表
随机访问 支持随机访问,访问某个元素的效率为 O(1) 不支持随机访问,访问某个元素的效率为 O(N)
插入和删除 任意位置的插入和删除效率低,需要搬移元素,时间复杂度为 O(N)
插入时可能需要增容,导致效率降低
任意位置的插入和删除效率高,不需要搬移元素,时间复杂度为 O(1)
空间利用率 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器 原生态指针 对原生态指针(节点指针)进行封装
迭代器失效 插入元素时可能导致迭代器失效(因扩容而失效),删除时当前迭代器可能需要重新赋值 插入元素不会导致迭代器失效,删除元素时只会导致当前迭代器失效,其他迭代器不受影响
使用场景 需要高效存储,支持随机访问,不关心插入删除效率 大量插入和删除操作,不关心随机访问


网站公告

今日签到

点亮在社区的每一天
去签到