C++ list 容器解析

在 C++ 标准模板库(STL)中,list是一种基于带头双向循环链表 实现的容器,它与vectordeque等顺序容器相比,在插入、删除操作上具有独特的优势。本文将从list的核心特性、常用接口、迭代器特性及实战场景等维度,全面解析list容器的使用方式与设计思想。

一、list 容器的核心特性

  1. 底层结构:带头双向循环链表,每个节点包含数据域、前驱指针和后继指针,头节点不存储有效数据,仅用于维护链表结构。
  2. 迭代器类型 :双向迭代器,仅支持++--操作,不支持+-算术运算(区别于vector的随机迭代器)。
  3. 操作效率:任意位置的插入 / 删除操作时间复杂度为 O (1)(仅需修改指针),但随机访问效率低(需遍历链表)。
  4. 不支持 [] 运算符:由于不具备随机访问能力,无法通过下标直接访问元素。

二、list 容器的常用接口与实战

1. 基础遍历与初始化

list的遍历可通过迭代器或范围 for 循环实现,需注意其迭代器不支持算术运算(如it + 1)。

cpp 复制代码
#include <iostream>
#include <list>
using namespace std;

void test_list_basic() {
    list<int> lt;
    // 尾插元素
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);

    // 迭代器遍历
    list<int>::iterator it = lt.begin();
    while (it != lt.end()) {
        cout << *it << " ";
        ++it; // 仅支持++操作
    }
    cout << endl; // 输出:1 2 3 4

    // 范围for遍历
    for (auto e : lt) {
        cout << e << " ";
    }
    cout << endl; // 输出:1 2 3 4
}

2. 高效插入:emplace_back vs push_back

push_backemplace_back均用于尾插元素,但emplace_back更高效 ------ 它直接在list的内存空间中构造对象,避免了临时对象的拷贝 / 移动。

基础类型场景(无明显差异)
cpp 复制代码
list<int> lt;
lt.push_back(1);
lt.emplace_back(2); // 功能等价于push_back,效率略优
lt.emplace_back(3);

自定义类型场景(优势显著)

cpp 复制代码
struct A {
    A(int a1 = 1, int a2 = 1) : _a1(a1), _a2(a2) {}
    int _a1;
    int _a2;
};

void test_emplace_back() {
    list<A> lt;
    A aa1(1, 1);
    lt.push_back(aa1); // 拷贝构造
    lt.push_back(A(2, 2)); // 临时对象构造+拷贝/移动
    // lt.push_back(3, 3); // 错误:push_back仅支持单个参数
    
    lt.emplace_back(aa1); // 拷贝构造
    lt.emplace_back(A(2, 2)); // 临时对象构造+拷贝/移动
    lt.emplace_back(3, 3); // 直接在链表节点中构造A对象,无临时对象
}

3. 插入与删除操作

(1)指定位置插入(insert)

insert可在迭代器指向的位置前插入元素,由于双向迭代器仅支持++/--需通过循环移动迭代器定位。

cpp 复制代码
void test_insert() {
    list<int> lt{1,2,3,4,5,6};
    auto it = lt.begin();
    int k = 3;
    while (k--) { // 移动3次,指向元素3
        it++;
    }
    lt.insert(it, 30); // 在3前插入30
    
    for (auto e : lt) {
        cout << e << " "; // 输出:1 2 3 30 4 5 6
    }
}

(2)查找与删除(erase/remove/remove_if)

  • erase:删除迭代器指向的元素,返回下一个元素的迭代器(避免迭代器失效);
  • remove:直接删除指定值的所有元素;
  • remove_if:按自定义条件删除元素(需配合仿函数)。
cpp 复制代码
#include <algorithm> // find函数头文件

void test_erase() {
    list<int> lt{1,2,3,30,4,5,6};
    // 查找值为2的元素
    auto it = find(lt.begin(), lt.end(), 2);
    if (it != lt.end()) {
        lt.erase(it); // 删除元素2
    }
    
    for (auto e : lt) {
        cout << e << " "; // 输出:1 3 30 4 5 6
    }

    // 直接删除值为30的元素
    lt.remove(30);
    // 删除所有大于5的元素
    lt.remove_if([](int x){ return x > 5; });
}

4. 排序、逆置与合并

(1)排序(sort)

算法库的sort要求随机迭代器,因此list提供了专属的sort成员函数,支持默认升序、自定义降序。

cpp 复制代码
void test_sort() {
    list<int> lt{1,20,3,4,5,6};
    lt.sort(); // 默认升序:1 3 4 5 6 20
    // 降序排序(仿函数)
    greater<int> gt;
    lt.sort(gt); // 20 6 5 4 3 1
}

(2)逆置(reverse)

list重载了reverse成员函数,比算法库的reverse更高效(无需随机迭代器)。

cpp 复制代码
lt.reverse(); // 直接逆置链表,时间复杂度O(n)

(3)合并(merge)

将两个已排序的list合并为一个有序链表,合并后被合并的链表为空。

cpp 复制代码
void test_merge() {
    list<double> first{3.1,2.2,2.9};
    list<double> second{3.7,7.1,1.4};
    
    first.sort(); // 1.4 2.2 2.9(错误,实际排序后:2.2 2.9 3.1)
    second.sort(); // 1.4 3.7 7.1
    first.merge(second); // 合并后first:1.4 2.2 2.9 3.1 3.7 7.1,second为空
    
    for (auto e : first) {
        cout << e << " ";
    }
}

5. 去重(unique)

unique用于删除连续的重复元素,使用前必须先排序(否则仅能删除连续重复项)。

cpp 复制代码
void test_unique() {
    list<int> lt{1,20,3,3,3,4,5,5,6,5};
    lt.sort(); // 先排序:1 3 3 3 4 5 5 5 6 20
    lt.unique(); // 去重后:1 3 4 5 6 20
    
    for (auto e : lt) {
        cout << e << " ";
    }
}

6. 节点转移(splice)

splice可将一个list的节点转移到另一个list的指定位置前,操作仅修改指针,效率极高。

cpp 复制代码
void test_splice() {
    list<int> mylist1{1,2,3,4};
    list<int> mylist2{10,20,30};
    auto it = mylist1.begin();
    ++it; // 指向2
    
    // 将mylist2所有节点转移到mylist1的it位置前
    mylist1.splice(it, mylist2); // mylist1:1 10 20 30 2 3 4,mylist2为空
    
    // 实战:将指定元素移到链表头部
    list<int> lt{1,2,3,4,5,6};
    int x = 5;
    it = find(lt.begin(), lt.end(), x);
    if (it != lt.end()) {
        // 把it到末尾的所有节点移到头部
        lt.splice(lt.begin(), lt, it, lt.end()); // lt:5 6 1 2 3 4
    }
}

7. 内容替换(assign)

assign用于替换list的全部内容,先清空原有元素,再构造新元素,比直接拷贝更高效。

cpp 复制代码
void test_assign() {
    list<int> lt1, lt2{10,20,30,20,10};
    // 优化排序:先拷贝到vector(支持随机迭代器)
    vector<int> v(lt2.begin(), lt2.end());
    sort(v.begin(), v.end()); // 算法库sort效率更高
    lt2.assign(v.begin(), v.end()); // 替换lt2内容为排序后的结果
}

三、list 迭代器的深度解析

1. 迭代器类型划分

STL 迭代器按功能可分为三类:

  • 单向迭代器(如forward_list):仅支持++
  • 双向迭代器(如list/map/set):支持++/--
  • 随机迭代器(如vector/string/deque):支持++/--/+/-

2. 迭代器的实现(自定义 list 迭代器)

list的迭代器本质是对链表节点指针的封装,核心重载*->++--等运算符:

cpp 复制代码
template<class T, class Ref, class Ptr>
struct list_iterator {
    typedef list_node<T> Node;
    typedef list_iterator<T, Ref, Ptr> Self;
    Node* _node;

    list_iterator(Node* node) : _node(node) {}

    Ref operator*() { return _node->_data; } // 支持const/非const引用
    Ptr operator->() { return &_node->_data; } // 访问自定义类型成员

    Self& operator++() { _node = _node->_next; return *this; }
    Self& operator--() { _node = _node->_prev; return *this; }

    bool operator!=(const Self& s) const { return _node != s._node; }
};

3. 按需实例化特性

模板的实例化遵循 "按需实例化" 原则:未调用的成员函数不会被编译,因此即使代码中存在语法问题,只要未调用就不会报错。

cpp 复制代码
list<int>::const_iterator it = lt.begin();
while (it != lt.end()) {
    // *it += 10; // 编译不报错(未实例化时),运行报错(const迭代器不可修改)
    cout << *it << " ";
    ++it;
}

四、list 容器的适用场景

适用场景:

  • 频繁在任意位置插入 / 删除元素(如链表结构的业务场景);
  • 无需随机访问,仅需顺序遍历;
  • 需要高效的节点转移(splice)操作。

不适用场景:

  • 需要随机访问元素(优先选vector/deque);
  • 频繁排序(优先选vector,配合算法库sort)。
相关推荐
雨大王51215 小时前
数字孪生如何助力汽车零部件企业实现柔性生产?
开发语言·人工智能·python
hqwest15 小时前
码上通QT实战05--绘制导航按钮
开发语言·css·qt·自定义控件·qframe·布局ui
AI爱好者202015 小时前
智能优化算法2025年新书推荐——《智能优化算法及其MATLAB实例(第4版)》
开发语言·算法·matlab
初子无爱15 小时前
Java接入支付宝沙箱支付教程
java·开发语言
duanyuehuan15 小时前
js 解构赋值
开发语言·前端·javascript
wearegogog12315 小时前
基于试射法和龙格库塔法的层状介质射线追踪MATLAB实现
开发语言·matlab
木木木一15 小时前
Rust学习记录--C4 Rust所有权
开发语言·学习·rust
悟能不能悟16 小时前
前端调用a服务,a服务将请求用controller+openfeign调用b服务,接口参数中有header参数和body,a服务应该怎么设置,才简单
java·开发语言·前端
2501_9418859616 小时前
从接口演化到系统自治的互联网工程语法重构与多语言实践思路拆解分享文
java·开发语言
yong999016 小时前
MATLAB自回归预测模型实现方案
开发语言·matlab·回归