一、介绍
C++的list容器又被称为链表,它是一种物理存储单元上非连续的存储结构,它的逻辑顺序是通过链表的指针来实现的。可以通过链表将数据转换为“链式存储”。
list的迭代器iterator是一个双向迭代器,不支持通过下标随机访问的方式。
二、数据结构
链表的数据结构是有多个结点连接在一起的。每个节点中一定有一个节点指针类型的指针变量,它指向下一个结点。下图所示的就是一个简单的结点:
我们可以将他们连接起来,组成一个链表,链表的最后一个节点的指针是NULL。一般链表分为带头节点和不带头结点的,头结点的Data部分是不存储数据的:
这里带头结点的原因是:在遍历元素时,更加具有规律性。除了利用头结点进行分类,链表还分为单向链表和双向链表,C++STL的list容器所采用的就是双向链表。
我们观察上图可以看到,其实最后一个节点的指针是NULL,如果让最后一个指针指向头部,那么链表就形成了循环链表:
从这里我们也可以观察出,由头结点的链表更利于元素遍历。
C++STL中的list链表容器是一个双向循环链表。
三、list与vector比较
C++的STL中,vector和list是最常用的两种容器。
- list优点:
- 采用动态分配,减少空间浪费
- 链表执行插入和删除,只需要修改指针即可,不需要(像vector一样)移动大量元素
- 链表的插入和删除不会造成迭代器失效,但vector的迭代器会失效。
- list缺点:
- 链表的空间(多了指针域)和时间(元素遍历)消耗大。
四、list的使用
为了便于观察链表元素的值,我们编写printList()函数遍历打印元素信息:
//用于输出链表各个节点的Data值的函数
void printList(const list<int>& lst) {
if (lst.empty()) {
cout << "The list is EMPTY now." << endl;
return;
}
cout << "[";
for (list<int>::const_iterator it = lst.begin(); it != lst.end(); it++) {
if (it != lst.begin()) {
cout << ", ";
}
cout << *it;
}
cout << "]" << endl;
}
4.1 list初始化
声明 | 解释 |
list<T> lst | 初始化一个空链表 lst |
list<T> lst(begin, end) | 将区间[begin, end)的所有元素初始化 lst |
list<T> lst(int n, T elem) | 用n个elem初始化链表 |
list<T> lst(list<T> lst1) | 拷贝函数 |
list<int> lst0;//空链表
cout << "lst0 : "; printList(lst0);
list<int> lst1(6, 6);//用6个6初始化链表
cout << "lst1 : "; printList(lst1);
list<int> lst2(lst1.begin(), lst1.end());//区间初始化
cout << "lst2 : "; printList(lst2);
list<int> lst3(lst2);//拷贝函数
cout << "lst3 : "; printList(lst3);
程序执行结果如下所示:
4.2 list赋值
list采用运算符=和assign函数来实现赋值操作
声明 | 解释 |
list& operator=(const list &list) | 重载=运算符 (有关重载运算符,可以看之前写的《C++重载运算符》) |
assign(begin, end) | 将[begin(), end())区间赋值给自身 |
assign(int n, T elem) | 将n个elem赋值给自身 |
list<int> lst0;
cout << "lst0 : "; printList(lst0);
lst0.assign(6, 6);//将6个6赋值给链表
cout << "lst0 : "; printList(lst0);
list<int> lst1 = lst0;//等号运算符重载
cout << "lst1 : "; printList(lst1);
list<int> lst2;
lst0.push_front(1);
lst2.assign(lst0.begin(), lst0.end());//区间赋值
cout << "lst2 : "; printList(lst2);
程序的执行结果如下:
4.3 list元素交换
声明 | 解释 |
swap(const list &list) | 交换两个链表的全部元素 |
list<int> lst0(6, 6);
list<int> lst1(7, 7);
cout << "Before SWAP : " << endl;
cout << "lst0 : "; printList(lst0);
cout << "lst1 : "; printList(lst1);
lst0.swap(lst1);//SWAP
cout << "After SWAP : " << endl;
cout << "lst0 : "; printList(lst0);
cout << "lst1 : "; printList(lst1);
程序的执行结果如下:
4.4 list容量与大小
声明 | 解释 |
empty() | 判断deque是否为空(是否存在元素) |
size() | 返回deque中结点的个数 |
resize(int num) | 重新将deque的容量大小设置为num 1. 若容量长度变大,则用默认值填充新位置 2. 若容量长度变小,则超出容量长度的元素被删除 |
resize(int num, T elem) | 重新将deque的容量大小设置为num 1. 若容量长度变大,则用elem填充新位置 2. 若容量长度变小,则超出容量长度的元素被删除 |
list<int> lst0(6, 6);
//判空
cout << "lst0.empty() : " << boolalpha << lst0.empty() << endl;
//获取结点个数
cout << "lst0.size() : " << lst0.size() << endl;
lst0.resize(8, 9);//容量变大,用9填充
cout << "lst0 : "; printList(lst0);
lst0.resize(4);//容量变小
cout << "lst0 : "; printList(lst0);
程序的执行结果如下所示:
4.5 list插入元素
声明 | 解释 |
push_back(Node node) | 在链表尾部插入一个结点node |
push_front(Node node) | 在链表头部插入一个结点node |
Node* insert(Node *pos, Node node) | 在pos位置插入一个结点node (返回新插入元素的位置) |
insert(Node *pos, int n, Node node) | 在pos位置插入n个结点node (无返回值) |
insert(Node *pos, begin, end) | 在pos位置插入[begin, end)区间的数据 (无返回值) |
list<int> lst;
//为lst尾部插入0 1 2 3 4 5 6 7 8 9
for (int i = 0; i < 10; i++) {
lst.push_back(i);
}
cout << "lst : "; printList(lst);
//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//在lst头部插入996
lst.push_front(996);
cout << "lst : "; printList(lst);
//[996, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//在第三个位置插入333
list<int>::iterator it = lst.begin();
it++; it++;
list<int>::iterator it_new = lst.insert(it, 333);
cout << "lst : "; printList(lst);
//[996, 0, 333, 1, 2, 3, 4, 5, 6, 7, 8, 9]
cout << "新插入元素为:" << *it_new << endl;//333
//在it_new位置插入3个888
lst.insert(it_new, 3, 888);
cout << "lst : "; printList(lst);
//[996, 0, 888, 888, 888, 333, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//在it_new位置插入lst1的区间结点
list<int> lst1(2, 777);
lst.insert(it_new, lst1.begin(), lst1.end());
cout << "lst : "; printList(lst);
//[996, 0, 888, 888, 888,777, 777, 333, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行结果如下所示:
4.6 list删除元素
声明 | 解释 |
pop_back() | 删除链表中最后一个结点 |
pop_front() | 删除链表中第一个结点 |
clear() | 删除链表的全部结点 |
erase(begin, end) | 删除[begin, end)区间的结点 (返回下一个结点的位置) |
erase(pos) | 删除pos位置的结点 (返回下一个结点的位置) |
remove(Data data) | 删除链表中所有与data值相匹配的结点 |
list<int> lst;
//为lst尾部插入0 1 2 3 4 5 6 7 8 9
for (int i = 0; i < 10; i++) {
lst.push_back(i);
}
cout << "lst : "; printList(lst);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//删除最后一个结点
lst.pop_back();
cout << "lst : "; printList(lst);//[0, 1, 2, 3, 4, 5, 6, 7, 8]
//删除第一个结点
lst.pop_front();
cout << "lst : "; printList(lst);//[1, 2, 3, 4, 5, 6, 7, 8]
//删除[begin+2,end-2)区间的结点
list<int>::iterator itBegin = lst.begin();
itBegin++; itBegin++;
list<int>::iterator itEnd = lst.end();
itEnd--; itEnd--;
lst.erase(itBegin, itEnd);
cout << "lst : "; printList(lst);//[1, 2, 7, 8]
//删除begin+1位置的结点
itBegin = lst.begin();
itBegin++;
lst.erase(itBegin);
cout << "lst : "; printList(lst);//[1, 7, 8]
//在链表尾部添加值为7的结点
lst.push_back(7);
cout << "lst : "; printList(lst);//[1, 7, 8, 7]
//删除链表中值为7的结点
lst.remove(7);
cout << "lst : "; printList(lst);//[1, 8]
程序执行结果如下所示:
4.7 list数据获取
声明 | 解释 |
front() | 返回链表中最后一个结点 |
back() | 返回链表中第一个结点 |
list<int> lst;
//为lst尾部插入0 1 2 3 4 5 6 7 8 9
for (int i = 0; i < 10; i++) {
lst.push_back(i);
}
cout << "lst : "; printList(lst);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
cout << lst.front() << endl;//0
cout << lst.back() << endl;//9
4.8 list反转
声明 | 解释 |
reverse() | 将链表中的全部元素反转 |
list<int> lst;
//为lst尾部插入0 1 2 3 4 5 6 7 8 9
for (int i = 0; i < 10; i++) {
lst.push_back(i);
}
cout << "Before reverse : " << endl;
cout << "lst : "; printList(lst);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
lst.reverse();
cout << "After reverse : " << endl;
cout << "lst : "; printList(lst);//[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
程序直接结果如下所示:
4.9 list排序
在包含头文件#include<algorithm>可以使用sort函数对链表元素进行升序排序。
4.9.1 标准类型的list
声明 | 解释 |
sort() | 将链表中的全部元素按升序排序 |
list<int> lst;
//为lst尾部插入0 1 2 3 4 5 6 7 8 9
for (int i = 9; i >= 0; i--) {
lst.push_back(i);
}
cout << "Before sort : " << endl;
cout << "lst : "; printList(lst);//[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
lst.sort();
cout << "After sort : " << endl;
cout << "lst : "; printList(lst);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序运行结果如下:
4.9.2 自定义类型的list
如果是自定义的类型,比如现在有一个自定义类Person,里边有五花八门的元素,现在
list<Person> p;
p.sort();
执行以上代码,程序就会报错,因为编译器根本不知道两个Person对象比较大小的规则是什么。
因此我们需要告知sort函数,我们的比较规则,此时需要我们自己写一个compare函数:
bool compare(Person &p1, Person &p2);
然后将这个函数名称,也就是compare作为参数传给sort函数:
p.sort(compare);
可以结合我之前写的小例子更直观的理解:《C++ 自定义类型list的排序案例》