list 是 C++ 标准库中的一种容器,它的底层是由双向链表(doubly linked list)实现,该链表也可称为双向循环链表。
在双向链表中,每个节点都包含两个指针,一个指向前一个节点,一个指向后一个节点,因此称为双向遍历链表。而在双向循环链表中,链表的头节点的前指针指向尾节点,链表的尾节点的后指针指向头节点,从而形成一个环形结构。如下图所示:
list 容器的存储结构图:
注意:list和vector是两个最常被使用的容器!
根据链表这种数据结构的特性,能得出该数据结果也同样具体相似的特性,相较于vector的连续线性空间,有以下优缺点:
- 采用动态存储分配,不会造成内存浪费和溢出。
- 可以对任意位置进行快速的插入删除操作,时间复杂度为O(1)。
- 容器遍历速度没有数组快,需要访问指针来遍历各个元素。
- 空间消耗也比数组大,因为元素大小包含了指针域,数据域,增加了对空间的消耗。
- begin() 返回指向容器中第一个元素的双向迭代器。
- end() 返回指向容器中最后一个元素所在位置的下一个位置的双向迭代器。
- rbegin() 返回指向最后一个元素的反向双向迭代器。
- rend() 返回指向第一个元素所在位置前一个位置的反向双向迭代器。
- cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
- cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
- crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
- crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
- empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
- size() 返回当前容器实际包含的元素个数。
- max_size() 返回容器所能包含元素个数的最大值。这通常是一个很大的值,一般很少会用到这个函数。
- front() 返回第一个元素的引用。
- back() 返回最后一个元素的引用。
- assign() 用新元素替换容器中原有内容。
- emplace_front() 在容器头部生成一个元素。该函数和 push_front() 的功能相同,但效率更高。
- push_front() 在容器头部插入一个元素。 pop_front() 删除容器头部的一个元素。
- emplace_back() 在容器尾部直接生成一个元素。该函数和 push_back() 的功能相同,但效率更高。
- push_back() 在容器尾部插入一个元素。
- pop_back() 删除容器尾部的一个元素。
- emplace() 在容器中的指定位置插入元素。该函数和 insert() 功能相同,但效率更高。
- insert() 在容器中的指定位置插入元素。
- erase() 删除容器中一个或某区域内的元素。
- swap() 交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的。
- resize() 调整容器的大小。
- clear() 删除容器存储的所有元素。
- splice() 将一个 list 容器中的元素插入到另一个容器的指定位置。
- remove(val) 删除容器中所有等于 val 的元素。
- remove_if() 删除容器中满足条件的元素。
- unique() 删除容器中相邻的重复元素,只保留一个。
- merge() 合并两个事先已排好序的 list 容器,并且合并之后的 list容器依然是有序的。
- sort() 通过更改容器中元素的位置,将它们进行排序。
- reverse() 反转容器中元素的顺序。
void printList(const list<int>& L){
for (list<int>::const_iterator it = L.begin(); it != L.end(); it++){
cout << *it << " ";
}
cout << endl;
}
代码示例:
void test(){
list<int>L1; //默认构造
for (int i = 0; i < 10; i++)
{
L1.push_back(i);
}
printList(L1);
list<int>L2(2, 10); // n个elem构造
printList(L2);
list<int>L4(L2.begin(), L2.end()); // 区间拷贝赋值
cout << "L4 区间赋值:";
printList(L4);
list<int>L3(L2); //拷贝构造的方式
cout << "L3 拷贝构造函数赋值:";
printList(L3);
}
代码示例:
void test(){
cout << "operator = 赋值:";
list<int>L5 = L1;
printList(L5);
L2.swap(L1);
cout << "L1和L2交换后: ";
printList(L2);
L1.assign(10, 10);
cout << "L1 assign函数赋值:";
printList(L1);
}
函数原型:
size(); // 输出容器元素大小
empty(); // 判断容器是否为空
resize(num); // 改变大小为 num
resize(num,elem); // 改变大小为 num,每个数据为 elem
代码示例:
void test(){
cout << "L2 元素大小:" << L2.size() << endl;
cout << "判断是否为空:";
if (L2.empty())cout << "L2为空" << endl;
else cout << "L2不为空" << endl;
cout << "L2 resize 11" << endl;
L2.resize(11);
printList(L2);
cout << "L2 resize 12,2:" << endl;
L2.resize(12, 2);
printList(L2);
}
函数原型:
- erase() // 删除元素操作
- erase(where) ; // 删除指定迭代器位置的元素
- erase(First,Last); // 删除区间[first, last]之间的元素
- insert() // 插入操作
- insert(where, initializer_list); // 在指定位置插入初始化列表
- insert(where,val); // 在指定位置插入val值
- insert(where,const val); // 在指定位置插入常量值
- insert(where, first, last); // 在指定位置插入该区间内的元素
- insert(where, count, val); // 在指定位置插入count数量的val值
- pop_back();
- pop_front();
- push_back();
- push_front();
- clear();
- remove();
- emplace();
- emplace_front();
- emplace_back();
emplace函数
代码示例:
void test03()
{
list<int>L1;
for (int i = 0; i < 10; i++)
{
L1.push_back(i);
}
printList(L1);
L1.push_back(11); //尾部插入
printList(L1);
L1.push_front(-1); //头部插入
printList(L1);
L1.pop_back(); // 尾部删除
printList(L1);
L1.pop_front(); // 头部删除
printList(L1);
list<int>::iterator it = L1.begin();
for (int i = 0; i < 10; i++)
{
L1.emplace(it++, i + 2); // emplace 插入
}
printList(L1);
L1.emplace_back(); //尾插
L1.emplace_front(); //头插
printList(L1);
// erase():两个重载 erase(where) erase(First,Last)
L1.erase(L1.begin());
printList(L1);
L1.erase(L1.begin()++, L1.end());
printList(L1);
cout << "清空" << endl;
L1.clear();
list<int>L2;
L2.assign(5, 9);
// insert 五个重载
cout << "begin() insert {1,5,4}" << endl;
L1.insert(L1.begin(), { 1,5,4 });
printList(L1);
L1.insert(L1.begin(), 10);
printList(L1);
L1.insert(L1.begin(), L2.begin(), L2.end());
printList(L1);
L1.insert(L1.begin(), 5, 3);
printList(L1);
L1.remove(3); //删除所匹配的值
printList(L1);
}
函数原型:
front() // 返回头部元素
back() // 返回尾部元素
代码示例:
void test(){
list<int>L1;
for (int i = 0; i < 10; i++){
L1.push_back(i);
}
printList(L1);
cout<<"第一个元素:"<<L1.front()<<endl;
cout<<"最后一个元素:"<<L1.back()<<endl;
}
函数原型:
reverse(const _BidIt _First, const _BidIt _Last) // reverse elements in [_First, _Last)
sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred) // order [_First, _Last)
代码演示:
//降序排列
bool compare(int a, int b)
{
return a > b;
}
//反转,排序
void test02()
{
list<int>L1;
for (int i = 0; i < 10; i++)
{
L1.push_back(i);
}
printList(L1);
cout << "L1反转:";
L1.reverse();
printList(L1);
cout << "L1 sort排序:";
L1.sort();
printList(L1);
list<int> myList = { 5, 2, 8, 1, 3 };
cout << "使用自定义的比较函数进行降序排序: ";
myList.sort(compare); //std::list 的 sort() 成员函数不需要传入比较函数作为参数,它默认按照 < 运算符进行升序排序。
printList(myList);
}
list::iterator it = L2.begin(); it += 2;
这个写法是错误的;
原因:
list是一个双向链表,它的迭代器是双向迭代器(Bidirectional Iterator),不支持随机访问,因此不能像数组或向量那样使用 += 操作符来直接跳跃指定的步数。简单来说就是存储空间不是连续的,不像数组那样可以随机访问,所以迭代器也不能随机访问。
同理:也不支持 [],at 的方式来随机访问元素。
本文对list容器的存储结构和成员函数的使用进行的全面的展示以及相关代码的操作,更为深入的内容暂时不需要理解,也难以理解,因为涉及到汇编语言和泛化编程的思想,后面涉及到再详细说明。只需要对成员函数的使用和什么时候使用有一个深刻的认知。当需要频繁进行插入和删除操作时,使用该容器就显得十分合适。
希望本文能够让大家能掌握好 list 容器的使用,记住多动手编程,多编程才是王道!
欢迎关注点赞收藏⭐️留言