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); //100l1.push_front(200); //200 100l1.push_front(300); //300 200 100l1.push_front(400); //400 300 200 100l1.pop_back(); //400 300 200l1.pop_front(); //300 200printList(l1);list<int>::iterator it = l1.begin();cout << "*it=" << *it << endl;l1.insert(++it, 1000); //300 1000 200printList(l1);cout << "*it=" << *it << endl; //特别注意这个时候迭代器的位置!!!l1.insert(++it, 3, 33); //300 1000 200 33 33 33printList(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 33it = 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); //100l1.push_front(200); //200 100l1.push_front(300); //300 200 100l1.push_front(400); //400 300 200 100cout << 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); //100l1.push_front(2422410); //200 100l1.push_front(343430); //300 200 100l1.push_front(403242130); //400 300 200 100printList(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 3for (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 orangefor (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
容器提供了在实际项目中处理类似场景的便利性和性能优势。