1 基础概念

- 功能: 将数据进行链式存储
链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
链表的组成:链表由一系列结点组成。
结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

STL中的链表是一个双向循环链表

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器。
list的优点:
采用动态存储分配,不会造成内存浪费和溢出,用多少开多少空间,不像vector那样预留很多空间;
链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素。
list的缺点:
链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大;
List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。
2 代码解释
Talk is cheap, show me the code.
#include<iostream>
using namespace std;
#include<list>
void printList(const list<int>& ll)
{
for (list<int>::const_iterator it = ll.begin(); it != ll.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
/*
构造函数原型:
list<T> lst; //list采用采用模板类实现,对象的默认构造形式:
list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem); //构造函数将n个elem拷贝给本身。
list(const list &lst); //拷贝构造函数。
*/
void test01()
{
list<int> l1;
l1.push_back(10);
l1.push_back(20);
l1.push_back(30);
l1.push_back(40);
printList(l1);
list<int> l2(l1.begin(), l1.end());
printList(l2);
list<int> l3(10, 88);
printList(l3);
list<int> l4(l3);
printList(l4);
}
/*
赋值和交换函数原型:
assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem); //将n个elem拷贝赋值给本身。
list& operator=(const list &lst); //重载等号操作符
swap(lst); //将lst与本身的元素互换。
*/
void test02()
{
list<int> l1;
l1.push_back(10);
l1.push_back(20);
l1.push_back(30);
l1.push_back(40);
printList(l1);
list<int> l2;
l2.assign(l1.begin(), l1.end());
printList(l2);
list<int> l3;
l3.assign(10, 88);
printList(l3);
list<int> l4;
l4 = l3;
printList(l4);
//swap 需不需要元素个数相等
l4.swap(l1);
printList(l1);
printList(l4);
}
/*
大小操作函数原型:
size(); //返回容器中元素的个数
empty(); //判断容器是否为空
resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
*/
void test03()
{
list<int> l1;
l1.push_back(10);
l1.push_back(20);
l1.push_back(30);
l1.push_back(40);
printList(l1);
if (l1.empty())
{
cout << "Empty" << endl;
}
else
{
cout << "the size of l1: " << l1.size() << endl;
}
l1.resize(10, 1000);
printList(l1);
l1.resize(4);
printList(l1);
}
/*
插入和删除函数原型:
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。
*/
void test04()
{
list<int> l1;
l1.push_back(100); //100
l1.push_front(200); //200 100
l1.push_front(300); //300 200 100
l1.push_front(400); //400 300 200 100
l1.pop_back(); //400 300 200
l1.pop_front(); //300 200
printList(l1);
list<int>::iterator it = l1.begin();
cout << "*it=" << *it << endl;
l1.insert(++it, 1000); //300 1000 200
printList(l1);
cout << "*it=" << *it << endl; //特别注意这个时候迭代器的位置!!!
l1.insert(++it, 3, 33); //300 1000 200 33 33 33
printList(l1);
list<int> l2(l1);
l2.insert(l2.begin(), l1.begin(), l1.end());
printList(l2); //300 1000 200 33 33 33 300 1000 200 33 33 33
it = l1.begin();
cout << "*it=" << *it << endl;
printList(l1);
//l1.erase(it, ++it); //注意并没有这个操作!!!!!正确的应该只有起始迭代器到结束迭代器的操作
printList(l1);
l1.erase(++it);
printList(l1); //33 33 200
}
/*
数据存取函数原型:
//移除 L.push_back(10000); L.push_back(10000); L.push_back(10000); printList(L); L.remove(10000); printList(L); //清空 L.clear(); printList(L); }int main() { test01(); system("pause"); return 0; } 43444546474849505152535455565758596061626364
front(); //返回第一个元素。
back(); //返回最后一个元素。
*/
void test05()
{
list<int> l1;
l1.push_back(100); //100
l1.push_front(200); //200 100
l1.push_front(300); //300 200 100
l1.push_front(400); //400 300 200 100
cout << l1.front() << endl;
cout << l1.back() << endl;
}
/*
反转和排序函数原型:
reverse(); //反转链表
sort(); //链表排序
*/
bool myCompare(int a, int b)
{
return a > b;
}
void test06()
{
list<int> l1;
l1.push_back(123210); //100
l1.push_front(2422410); //200 100
l1.push_front(343430); //300 200 100
l1.push_front(403242130); //400 300 200 100
printList(l1);
l1.reverse();
printList(l1);
l1.sort();
printList(l1);
l1.sort(myCompare);
printList(l1);
}
/*
排序案例
案例描述:将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高
排序规则:按照年龄进行升序,如果年龄相同按照身高进行降序
*/
class Person
{
public:
Person(string name, int age, int height)
{
this->name = name;
this->age = age;
this->height = height;
}
string name;
int age;
int height;
};
bool comparePerson(const Person&p1,const Person&p2)
{
if (p1.age == p2.age)
{
return p1.height > p2.height;
}
else
{
return p1.age < p2.age;
}
}
void printPersonInfo(const list<Person>& ll)
{
for (list<Person>::const_iterator it = ll.begin(); it != ll.end(); it++)
{
cout << (*it).name << " " << (*it).age << " " << (*it).height << endl;
}
}
void test07()
{
Person p1("zhao",99,170);
Person p2("qian", 89, 190);
Person p3("sun", 79, 200);
Person p4("li", 89, 180);
Person p5("zhou", 109, 210);
list<Person> p;
p.push_back(p1);
p.push_back(p2);
p.push_back(p3);
p.push_back(p4);
p.push_back(p5);
printPersonInfo(p);
p.sort(comparePerson);
printPersonInfo(p);
}
int main()
{
test07();
system("pause");
return 0;
}
3 应用场景
C++的STL(Standard Template Library)中的list容器是一个双向链表,它提供了在两端进行高效插入和删除操作的能力。在实际项目中,list容器常常用于以下一些应用场景:
-
高效的插入和删除操作: 由于
list是一个双向链表,插入和删除元素的时间复杂度是O(1),这使得它在需要频繁插入或删除元素的场景中非常高效。例如,某些算法需要在中间插入或删除元素时,list相比于数组或向量更具优势。#include <list> #include <iostream> int main() { std::list<int> myList; myList.push_back(1); myList.push_back(2); myList.push_back(3); // 在第二个位置插入元素 auto it = std::next(myList.begin()); myList.insert(it, 4); // 删除第一个元素 myList.pop_front(); // 输出:2 4 3 for (const auto &element : myList) { std::cout << element << " "; } return 0; } -
不需要随机访问的情况:
list不支持通过索引直接访问元素,但在某些情况下,我们并不需要随机访问,而是更关注在序列中进行插入和删除操作的性能。#include <list> #include <algorithm> #include <iostream> int main() { std::list<std::string> wordList = {"apple", "banana", "orange", "grape"}; // 删除所有长度小于 6 的单词 wordList.remove_if([](const std::string &word) { return word.length() < 6; }); // 输出:banana orange for (const auto &word : wordList) { std::cout << word << " "; } return 0; } -
避免动态数组重新分配的开销: 与
vector不同,list的元素在内存中不是连续存储的,因此在插入和删除操作时,不需要进行动态数组的重新分配,从而避免了重新分配的开销。#include <list> #include <iostream> int main() { std::list<int> myList; for (int i = 0; i < 10000; ++i) { // 在列表末尾插入元素,不会导致重新分配 myList.push_back(i); } // 执行大量的插入和删除操作而不触发数组重新分配 // ... return 0; }
这些场景中,list容器的特性使得它成为一个合适的选择。然而,需要注意的是,由于其非连续存储的特性,list在访问元素时性能较差,因此在需要频繁随机访问元素的情况下,可能会考虑其他容器,如vector。
4 代码示例
假设你正在开发一个任务调度器,需要存储和管理一系列待执行的任务,并且需要支持高效的插入和删除操作。在这种情况下,使用std::list容器可以提供一些优势。以下是一个简单的示例,演示了如何使用list来管理任务列表:
#include <iostream>
#include <list>
#include <string>
// 任务结构体
struct Task {
std::string name;
int priority;
Task(const std::string& taskName, int taskPriority)
: name(taskName), priority(taskPriority) {}
};
// 任务调度器类
class TaskScheduler {
private:
std::list<Task> taskList;
public:
// 添加任务
void addTask(const Task& task) {
// 在任务列表末尾添加任务
taskList.push_back(task);
}
// 删除指定优先级的所有任务
void removeTasksByPriority(int priority) {
// 使用 erase-remove 惯用法删除指定优先级的任务
taskList.remove_if([priority](const Task& task) {
return task.priority == priority;
});
}
// 打印所有任务
void printTasks() const {
std::cout << "Tasks in the scheduler:\\n";
for (const auto& task : taskList) {
std::cout << "Name: " << task.name << ", Priority: " << task.priority << "\\n";
}
}
};
int main() {
TaskScheduler scheduler;
// 添加一些任务
scheduler.addTask(Task("TaskA", 2));
scheduler.addTask(Task("TaskB", 1));
scheduler.addTask(Task("TaskC", 3));
scheduler.addTask(Task("TaskD", 2));
// 打印所有任务
scheduler.printTasks();
// 删除优先级为2的任务
scheduler.removeTasksByPriority(2);
// 打印删除后的任务列表
scheduler.printTasks();
return 0;
}
在这个示例中,std::list容器使得在任务调度器中添加和删除任务变得简单高效。使用remove_if算法可以方便地删除符合特定条件的任务,而不需要手动管理元素的移动。这样,list容器提供了在实际项目中处理类似场景的便利性和性能优势。
本文详细介绍了C++STL库中的双向链表list,包括其基本概念、优点与缺点,以及在实际编程中的应用场景,如高效插入删除、避免动态数组重新分配和任务调度。
1435

被折叠的 条评论
为什么被折叠?



