- C++
C++ list链表教程:从入门到实战
- 2025-6-9 21:18:00 @
C++ list链表教程:从入门到实战
一、什么是list链表?
在C++标准库中,list
是一种双向链表数据结构。与数组不同,链表中的元素在内存中并不连续存储,而是通过指针(或引用)连接起来的。每个节点包含数据和指向前一个/后一个节点的指针,这使得list
在插入和删除操作上具有高效性。
二、list的基本使用
首先,我们需要包含必要的头文件并了解基本的声明方式:
#include <iostream>
#include <list> // 使用list必须包含的头文件
#include <algorithm> // 用于使用算法函数
using namespace std;
int main() {
// 1. 声明list的多种方式
list<int> myList1; // 空list
list<int> myList2(5, 10); // 包含5个10的list: [10,10,10,10,10]
list<int> myList3 = {1, 2, 3, 4, 5}; // 初始化列表
list<int> myList4(myList3); // 拷贝构造
list<int> myList5(myList3.begin(), myList3.end()); // 区间构造
// 2. 常用操作函数演示
cout << "=== 基本操作演示 ===" << endl;
// 2.1 添加元素
myList1.push_back(10); // 在尾部添加元素
myList1.push_front(20); // 在头部添加元素
cout << "添加元素后myList1: ";
for (int num : myList1) {
cout << num << " ";
}
cout << endl;
// 2.2 访问元素
// list不支持随机访问(如myList[0]),需通过迭代器访问
list<int>::iterator it = myList3.begin();
cout << "myList3第一个元素: " << *it << endl;
// 2.3 删除元素
myList3.pop_back(); // 删除尾部元素
myList3.pop_front(); // 删除头部元素
cout << "删除首尾元素后myList3: ";
for (int num : myList3) {
cout << num << " ";
}
cout << endl;
// 2.4 查找元素
it = find(myList3.begin(), myList3.end(), 3);
if (it != myList3.end()) {
cout << "找到元素3,位置为: " << distance(myList3.begin(), it) << endl;
}
// 2.5 其他常用函数
cout << "myList3大小: " << myList3.size() << endl;
cout << "myList3是否为空: " << (myList3.empty() ? "是" : "否") << endl;
// 3. 迭代器的使用
cout << "\n=== 迭代器使用演示 ===" << endl;
cout << "正向遍历myList3: ";
for (it = myList3.begin(); it != myList3.end(); ++it) {
cout << *it << " ";
}
cout << endl;
cout << "反向遍历myList3: ";
list<int>::reverse_iterator rIt;
for (rIt = myList3.rbegin(); rIt != myList3.rend(); ++rIt) {
cout << *rIt << " ";
}
cout << endl;
// 4. 自定义类型的list
cout << "\n=== 自定义类型list演示 ===" << endl;
struct Person {
string name;
int age;
Person(string n, int a) : name(n), age(a) {}
};
list<Person> personList;
personList.push_back(Person("张三", 25));
personList.push_back(Person("李四", 30));
personList.push_front(Person("王五", 22));
cout << "人员列表: " << endl;
for (const Person& p : personList) {
cout << "姓名: " << p.name << ", 年龄: " << p.age << endl;
}
// 5. list的高级操作
cout << "\n=== 高级操作演示 ===" << endl;
list<int> listA = {1, 2, 3};
list<int> listB = {4, 5, 6};
// 5.1 合并两个list
listA.merge(listB); // listB会被清空
cout << "合并后listA: ";
for (int num : listA) {
cout << num << " ";
}
cout << endl;
// 5.2 排序与反转
listA.sort(); // 升序排序
cout << "排序后listA: ";
for (int num : listA) {
cout << num << " ";
}
cout << endl;
listA.reverse(); // 反转
cout << "反转后listA: ";
for (int num : listA) {
cout << num << " ";
}
cout << endl;
return 0;
}
三、list与vector的区别
理解list和vector的差异有助于我们在合适的场景选择合适的数据结构:
特性 | list | vector |
---|---|---|
内存分配 | 非连续内存,每个节点独立分配 | 连续内存块 |
随机访问 | 不支持,时间复杂度O(n) | 支持,时间复杂度O(1) |
插入/删除 | 在任意位置插入/删除高效,O(1)(已知迭代器位置) | 插入/删除可能需要移动元素,O(n) |
内存效率 | 每个节点需要额外存储指针,内存开销大 | 内存紧凑,效率高 |
适用场景 | 频繁插入/删除操作,不需要随机访问 | 频繁随机访问,较少插入/删除 |
四、list的迭代器
list使用双向迭代器,这意味着:
- 可以向前(++it)和向后(--it)移动
- 不支持随机访问操作(如it + n或it - n)
- 迭代器类型包括:
iterator
:正向迭代器const_iterator
:常量正向迭代器reverse_iterator
:反向迭代器const_reverse_iterator
:常量反向迭代器
五、list的常用函数详解
下面是list中最常用的一些函数及其功能说明:
-
元素操作
push_back(value)
:在列表末尾添加元素push_front(value)
:在列表开头添加元素pop_back()
:删除末尾元素pop_front()
:删除开头元素insert(iterator, value)
:在指定位置插入元素erase(iterator)
:删除指定位置的元素
-
访问与遍历
begin()
:返回指向第一个元素的迭代器end()
:返回指向最后一个元素之后的迭代器rbegin()
:返回指向最后一个元素的反向迭代器rend()
:返回指向第一个元素之前的反向迭代器front()
:返回第一个元素的引用back()
:返回最后一个元素的引用
-
容量操作
size()
:返回列表中元素的数量empty()
:判断列表是否为空max_size()
:返回列表可容纳的最大元素数量
-
修改操作
clear()
:删除列表中的所有元素swap(list)
:交换两个列表的内容sort()
:对列表进行排序reverse()
:反转列表中的元素顺序merge(list)
:合并两个已排序的列表
六、实战案例:使用list实现简单通讯录
下面通过一个实际案例来展示list的综合应用:
#include <iostream>
#include <list>
#include <string>
#include <algorithm>
using namespace std;
// 联系人结构体
struct Contact {
string name;
string phone;
string email;
Contact(string n, string p, string e) : name(n), phone(p), email(e) {}
// 用于显示联系人信息
void display() const {
cout << "姓名: " << name << ", 电话: " << phone << ", 邮箱: " << email << endl;
}
// 用于比较联系人姓名(用于排序)
bool operator<(const Contact& other) const {
return name < other.name;
}
};
int main() {
list<Contact> contactList;
// 添加一些联系人
contactList.push_back(Contact("张三", "13800138001", "zhangsan@example.com"));
contactList.push_back(Contact("李四", "13900139001", "lisi@example.com"));
contactList.push_back(Contact("王五", "13700137001", "wangwu@example.com"));
int choice;
string name, phone, email;
list<Contact>::iterator it;
while (true) {
// 显示菜单
cout << "\n=== 简单通讯录 ===" << endl;
cout << "1. 显示所有联系人" << endl;
cout << "2. 添加新联系人" << endl;
cout << "3. 删除联系人" << endl;
cout << "4. 查找联系人" << endl;
cout << "5. 排序联系人" << endl;
cout << "0. 退出" << endl;
cout << "请选择操作: ";
cin >> choice;
if (choice == 0) {
cout << "感谢使用,再见!" << endl;
break;
}
switch (choice) {
case 1:
// 显示所有联系人
if (contactList.empty()) {
cout << "通讯录为空!" << endl;
} else {
cout << "联系人列表:" << endl;
for (const Contact& c : contactList) {
c.display();
}
}
break;
case 2:
// 添加新联系人
cout << "请输入姓名: ";
cin >> name;
cout << "请输入电话: ";
cin >> phone;
cout << "请输入邮箱: ";
cin >> email;
contactList.push_back(Contact(name, phone, email));
cout << "联系人添加成功!" << endl;
break;
case 3:
// 删除联系人
cout << "请输入要删除的联系人姓名: ";
cin >> name;
it = find_if(contactList.begin(), contactList.end(),
[name](const Contact& c) { return c.name == name; });
if (it != contactList.end()) {
contactList.erase(it);
cout << "联系人删除成功!" << endl;
} else {
cout << "未找到该联系人!" << endl;
}
break;
case 4:
// 查找联系人
cout << "请输入要查找的联系人姓名: ";
cin >> name;
it = find_if(contactList.begin(), contactList.end(),
[name](const Contact& c) { return c.name == name; });
if (it != contactList.end()) {
cout << "找到联系人:" << endl;
it->display();
} else {
cout << "未找到该联系人!" << endl;
}
break;
case 5:
// 按姓名排序联系人
contactList.sort();
cout << "联系人已按姓名排序!" << endl;
break;
default:
cout << "无效选择,请重新输入!" << endl;
}
}
return 0;
}
七、list的性能特点与适用场景
-
优点
- 在任意位置插入和删除元素的时间复杂度为O(1)(已知迭代器位置)
- 不需要预先分配内存,动态增长更灵活
- 双向迭代器支持正向和反向遍历
-
缺点
- 不支持随机访问,访问中间元素需要从头遍历
- 每个节点需要额外的指针空间,内存开销较大
- 缓存利用率较低,性能可能不如vector
-
适用场景
- 需要频繁在序列中间进行插入和删除操作
- 不需要随机访问元素
- 数据量动态变化较大,难以预先估计
八、总结
C++的list
容器是一种非常有用的双向链表实现,它在插入和删除操作上表现出色。通过本教程,你应该已经掌握了:
- list的基本概念和特点
- list的声明和初始化方法
- list的常用操作和函数
- list迭代器的使用
- list与vector的区别
- list在实际场景中的应用
在实际编程中,根据具体需求选择合适的数据结构是非常重要的。当你需要频繁进行插入和删除操作,而不需要随机访问时,list会是一个很好的选择。
2 条评论
-
admin SU @ 2025-6-9 22:28:08已修改
在C++标准库的STL中,
std::list
是双向链表(每个节点包含前驱和后继指针),但不是循环链表。标准库中并没有直接提供循环双向链表的容器,但可以通过以下方式实现循环特性:1. 用
std::list
手动实现循环特性虽然
std::list
本身不是循环的,但可以通过代码逻辑实现循环遍历:#include <iostream> #include <list> int main() { std::list<int> lst = {1, 2, 3, 4, 5}; // 手动实现循环遍历(回到头节点) if (!lst.empty()) { auto it = lst.begin(); do { std::cout << *it << " "; ++it; if (it == lst.end()) it = lst.begin(); // 循环到头部 } while (it != lst.begin()); // 回到起点时停止 } return 0; }
2. 使用boost::circular_buffer
(环形缓冲区) Boost库提供了circular_buffer
,它支持循环访问,但本质是动态数组而非链表:#include <iostream> #include <boost/circular_buffer.hpp> int main() { boost::circular_buffer<int> cb(5); // 容量为5的循环缓冲区 cb.push_back(1); cb.push_back(2); cb.push_back(3); // 循环访问 for (int i = 0; i < 10; ++i) { std::cout << cb[i % cb.size()] << " "; // 自动循环 } return 0; }
3. 自定义循环双向链表
如果需要真正的循环双向链表结构,可以自己实现:
#include <iostream> template<typename T> struct Node { T data; Node* prev; Node* next; Node(const T& val) : data(val), prev(nullptr), next(nullptr) {} }; template<typename T> class CircularDoublyLinkedList { private: Node<T>* head; size_t size; public: CircularDoublyLinkedList() : head(nullptr), size(0) {} void insert(const T& val) { Node<T>* newNode = new Node<T>(val); if (!head) { head = newNode; head->next = head; head->prev = head; } else { newNode->next = head; newNode->prev = head->prev; head->prev->next = newNode; head->prev = newNode; } ++size; } // 其他操作... };
4. 使用场景与注意事项
- 循环遍历:适合需要循环访问的场景(如游戏中的回合制系统)
- 标准库替代方案:多数场景下
std::list
+手动循环逻辑已足够 - 性能考量:链表插入删除O(1),但随机访问O(n)
如果需要循环特性且不介意动态数组实现,推荐使用boost::circular_buffer
;如果必须是链表结构,可自定义实现或扩展std::list
。 -
2025-6-9 21:18:19@
C++
std::list
链表教程🌟 一、什么是
std::list
?在 C++ 中,
std::list
是 STL(标准模板库)中的一种双向链表容器。它允许快速的插入和删除操作,但不支持随机访问(不能像数组那样通过下标访问元素)。✅ 特点:
- 每个节点都包含一个值和两个指针:指向前面一个节点和后面一个节点。
- 插入/删除效率高(O(1)),查找效率低(O(n))。
- 不会因为插入/删除导致迭代器失效(除了被删除的节点)。
🧱 二、如何使用
std::list
?1. 引入头文件
#include <iostream> #include <list> // 必须包含这个头文件 using namespace std;
🚀 三、基本操作示例
1. 定义一个 list
int main() { list<int> my_list; // 创建一个存储 int 的空链表 return 0; }
2. 添加元素
常用方法:
my_list.push_back(10); // 在尾部添加元素 my_list.push_front(5); // 在头部添加元素
完整例子:
#include <iostream> #include <list> using namespace std; int main() { list<int> my_list; // 在尾部添加元素 my_list.push_back(10); // 链表: 10 my_list.push_back(20); // 链表: 10 -> 20 // 在头部添加元素 my_list.push_front(5); // 链表: 5 -> 10 -> 20 // 打印链表 for (int val : my_list) { cout << val << " "; } // 输出: 5 10 20 return 0; }
3. 删除元素
a. 删除首元素 / 尾元素
my_list.pop_front(); // 删除第一个元素 my_list.pop_back(); // 删除最后一个元素
b. 删除特定值的所有匹配项
my_list.remove(10); // 删除所有等于 10 的元素
完整示例:
#include <iostream> #include <list> using namespace std; int main() { list<int> my_list = {1, 2, 3, 2, 4}; // 删除所有值为 2 的元素 my_list.remove(2); // 输出结果 for (int val : my_list) { cout << val << " "; } // 输出: 1 3 4 return 0; }
4. 清空链表
my_list.clear();
🔍 四、遍历链表
使用迭代器进行遍历:
for (auto it = my_list.begin(); it != my_list.end(); ++it) { cout << *it << " "; }
也可以用范围 for 循环:
for (int val : my_list) { cout << val << " "; }
🔧 五、插入元素到指定位置
使用
insert()
方法:// 插入单个元素 auto it = my_list.begin(); ++it; // 移动到第二个位置 my_list.insert(it, 99); // 在第2个位置前插入 99
完整示例:
#include <iostream> #include <list> using namespace std; int main() { list<int> my_list = {10, 20, 30}; auto it = my_list.begin(); ++it; // 指向 20 的位置 my_list.insert(it, 15); // 插入后变成: 10 -> 15 -> 20 -> 30 for (int val : my_list) cout << val << " "; // 输出: 10 15 20 30 return 0; }
🔄 六、删除指定位置的元素
使用
erase()
方法:auto it = my_list.begin(); ++it; // 第二个元素 my_list.erase(it); // 删除第二个元素
📐 七、获取链表大小和是否为空
cout << "Size: " << my_list.size() << endl; cout << "Empty? " << (my_list.empty() ? "Yes" : "No") << endl;
📁 八、其他常用操作
排序(升序)
my_list.sort(); // 默认升序排序
反转链表
my_list.reverse(); // 原地反转链表
示例:
#include <iostream> #include <list> using namespace std; int main() { list<int> my_list = {3, 1, 4, 2}; my_list.sort(); // 排序后: 1, 2, 3, 4 my_list.reverse(); // 反转后: 4, 3, 2, 1 for (int val : my_list) cout << val << " "; // 输出: 4 3 2 1 return 0; }
🧠 九、总结
操作 方法 说明 添加元素 push_back()
尾插 push_front()
头插 删除元素 pop_back()
尾删 pop_front()
头删 remove(value)
删除所有等于 value 的元素 erase(iterator)
删除指定位置的元素 插入元素 insert(pos, val)
在 pos 前插入 val 遍历 迭代器或范围 for 清空 clear()
清空所有元素 排序 sort()
升序排列 反转 reverse()
反转链表顺序 获取大小 size()
返回元素个数 是否为空 empty()
判断是否为空
📝 十、实战小练习
题目:创建一个链表,存入数字 1~5,然后在第3个位置插入 99,并删除值为 3 的元素,最后输出结果。
#include <iostream> #include <list> using namespace std; int main() { list<int> my_list = {1, 2, 3, 4, 5}; // 初始化链表 // 插入 99 在第3个位置 auto it = my_list.begin(); advance(it, 2); // 移动到第三个位置 my_list.insert(it, 99); // 插入后变成: 1, 2, 99, 3, 4, 5 // 删除值为 3 的元素 my_list.remove(3); // 结果: 1, 2, 99, 4, 5 // 输出结果 for (int val : my_list) cout << val << " "; // 输出: 1 2 99 4 5 return 0; }
需要的话告诉我哦!
- 1