C++——list的了解和使用
目录
引言
forward_list与list
标准库中的list
一、list的常用接口
1.list的迭代器
2.list的初始化
3.list的容量操作
4.list的访问操作
5.list的修改操作
6.list的其他操作
二、list与vector的对比
结束语
引言
本篇博客要介绍的是STL中的list。
求点赞收藏评论关注!!!十分感谢!!!
forward_list与list
首先我们先简单了解一下forward_list与list:
forward_list与list都是C++标准模板库(STL)中的容器,它们提供了不同的链表实现,适用于不同的场景。
forward_list
定义与结构:
1.forward_list是C++11引入的一种容器,它提供了一种单向链表的数据结构。
2.它只维护一个指向下一个节点的指针,因此内存使用相对高效。
特点:
1.只能在单向遍历,即只能从前往后遍历,不能反向遍历。
2.在已知位置的情况下,插入和删除操作非常高效,时间复杂度为O(1)。
3.不支持通过索引访问元素,只能使用迭代器进行访问。
适用场景:
1.适用于需要频繁进行前向遍历和插入、删除操作的场景。
2.当内存使用要求较高,且不需要双向遍历时,forward_list是更好的选择。
list
定义与结构:
1.list是C++标准库中基于带头双向循环链表实现的容器。
2.它支持双向迭代器,可以从前往后和从后往前遍历。
特点:
1.在任何位置进行插入和删除操作的时间复杂度都是近似O(1)。
2.支持双向遍历,迭代器使用更灵活。
3.与forward_list相比,内存占用稍多,因为每个节点需要维护两个指针(一个指向前一个节点,一个指向下一个节点)。
适用场景:
1.适用于需要双向遍历和更灵活操作的场景。
2.当需要在列表中间频繁插入或删除元素时,list是更好的选择。
具体内容可以看看这两篇文章:
数据结构——单链表
数据结构——双向链表
本文的重点是list。
标准库中的list
一、list的常用接口
list接口
1.list的迭代器
list的迭代器访问元素与我们之前学习的其他容器的迭代器访问一样。
int main()
{
list<int> li = { 1, 2, 3, 4, 5 };
list<int>::iterator it = li.begin();
cout << "顺序遍历:";
while (it != li.end())
{
cout << *it << " ";
++it;
}
cout << endl;
cout << "逆序遍历:";
list<int>::reverse_iterator rit = li.rbegin();
while (rit != li.rend())
{
cout << *rit << " ";
++rit;
}
return 0;
}
由于list的迭代器是双向迭代器,支持++和--操作,因此可以在list中向前和向后遍历。
2.list的初始化
list的初始化与我们之前学习的其他容器的初始化一样,我们直接看个简单的使用示例:
int main()
{
// 默认构造函数
list<int> numbers1;
cout << "默认构造: ";
for (const auto& num : numbers1)
{
cout << num << " ";
}
cout << endl;
// n个val构造
list<int> numbers2(5, 5);
cout << "n个val构造: ";
for (const auto& num : numbers2)
{
cout << num << " ";
}
cout << endl;
int arr[] = { 1, 2, 3, 4, 5 };
// 通过vector的迭代器初始化
list<int> numbers3(arr, arr + sizeof(arr) / sizeof(arr[0]));
cout << "迭代器区间构造: ";
for (const auto& num : numbers3)
{
cout << num << " ";
}
cout << endl;
list<int> numbers4 = { 6, 7, 8, 9, 10 };
// 拷贝构造
list<int> numbers5(numbers4);
cout << "拷贝构造: ";
for (const auto& num : numbers5)
{
cout << num << " ";
}
cout << endl;
numbers1 = numbers2;
// 赋值重载
cout << "赋值重载: ";
for (const auto& num : numbers1)
{
cout << num << " ";
}
cout << endl;
return 0;
}
输出结果为:
3.list的容量操作
函数名称 | 功能 |
size | 返回列表中元素的数量 |
max_size | 返回列表可容纳的最大元素数量 |
empty | 检查列表是否为空,是则返回 true,否则返回 false |
resize | 重新设置列表中元素的数量,超过原来数量则用默认值填充 |
clear | 清空列表中的所有元素 |
这些函数也是很容易理解的,我们还是直接看代码示例:
int main()
{
list<int> li = { 1,2,3,4,5 };
cout << "Size of list: " << li.size() << endl;
cout << "Max size of list: " << li.max_size() << endl;
if (li.empty())
{
cout << "empty" << endl;
}
else
{
cout << "not empty" << endl;
}
li.clear();
if (li.empty())
{
cout << "empty" << endl;
}
else
{
cout << "not empty" << endl;
}
return 0;
}
输出结果为
与deque这一容器一样,list也没有reserve这一接口。
int main()
{
list<int> li = { 1,2,3 };
li.resize(5);
for (auto& num : li)
{
cout << num << " ";
}
cout << endl;
li.resize(2);
for (auto& num : li)
{
cout << num << " ";
}
return 0;
}
输出结果为;
4.list的访问操作
函数名称 | 功能 |
back | 返回列表最后一个元素 |
front | 返回列表第一个元素 |
由于list 是一个双向链表,不支持高效的随机访问。在链表中,访问某个元素需要从头节点开始顺序遍历,直到找到目标元素。因此,为 list 提供下标运算符或 at 方法并不合适。
访问操作的使用示例如下:
int main()
{
list<int> li = { 1,2,3 };
cout << li.front() << endl;
cout << li.back() << endl;
return 0;
}
输出结果为:
5.list的修改操作
常用的修改操作有如下几个:
函数名称 | 功能 |
---|---|
push_back | 在列表尾部添加元素 |
push_front | 在列表头部添加元素 |
pop_back | 删除列表最后一个元素 |
pop_front | 删除列表第一个元素 |
insert | 在指定位置插入元素 |
erase | 删除指定位置或区间的元素 |
swap | 交换两个列表 |
assign | 使用指定列表替换原列表 |
这些接口我们也是十分熟悉了,我们直接看代码示例:
尾删和尾插:
int main() { list<int> li; li.push_back(1); li.push_back(2); li.push_back(3); li.push_back(4); for (auto& num : li) { cout << num << " "; } cout << endl; li.pop_back(); for (auto& num : li) { cout << num << " "; } cout << endl; return 0; }
输出结果为:
头删和头插:
int main() { list<int> li; li.push_front(1); li.push_front(2); li.push_front(3); li.push_front(4); for (auto& num : li) { cout << num << " "; } cout << endl; li.pop_front(); for (auto& num : li) { cout << num << " "; } cout << endl; return 0; }
输出结果为:
assign和swap:
int main() { list<int> li1 = { 1,1,1,1,1 }; li1.assign(3, 3); for (auto& num : li1) { cout << num << " "; } cout << endl; list<int> li2 = { 2,2,2,2,2 }; li1.swap(li2); for (auto& num : li1) { cout << num << " "; } cout << endl; for (auto& num : li2) { cout << num << " "; } return 0; }
输出结果为:
insert:
int main() { list<int> li = { 1,2,3,4,5 }; list<int>::iterator it = li.begin(); it = li.insert(it, 6); it = li.insert(it, 7); for (auto& num : li) { cout << num << " "; } return 0; }
输出结果为:
erase:
int main() { list<int> li = { 1,2,3,4,5 }; list<int>::iterator it = li.begin(); it = li.erase(it); for (auto& num : li) { cout << num << " "; } return 0; }
输出结果为:
6.list的其他操作
接下来我们来学习list的其他操作:
函数名称 | 功能描述 |
---|---|
splice | 将元素从一个列表转移到另一个列表 |
remove | 移除具有特定值的元素 |
remove_if | 移除满足条件的元素 |
unique | 移除重复的值 |
sort | 对容器中的元素进行排序 |
merge | 合并已排序的列表 |
reverse | 反转元素的顺序 |
splice:
splice 是 list 提供的一个成员函数,用于将一个列表(list)中的元素移动到另一个列表中,而不需要进行元素复制或移动操作。
使用示例:
int main() { list<int> li1 = { 1,2,3 }; list<int> li2 = { 4,5,6 }; list<int> li3 = { 7,8,9 }; list<int> li4 = { 0 }; // 将 li2 的所有元素拼接到 li1 的末尾 // li1 现在包含 {1, 2, 3, 4, 5, 6} // li2 现在为空 {} li1.splice(li1.end(), li2); for (auto num : li1) { cout << num << " "; } cout << endl; // 获取 li3 的迭代器,指向第一个元素(7) auto begin1 = li3.begin(); // 将迭代器向前移动一位,指向第二个元素(8) ++begin1; // 将 li3 的第一个元素(7)移动到 li4 的末尾 // li4 现在包含 {0, 7} // li3 现在包含 {8, 9} li4.splice(li4.end(), li3, li3.begin(), begin1); for (auto num : li4) { cout << num << " "; } return 0; }
输出结果为:
remove:
用于从容器中移除所有等于指定值的元素。
与成员函数 erase 不同,成员函数 erase 按元素的位置擦除元素(使用迭代器),此函数 按元素的值删除元素。
int main() { list<int> li = { 1,2,3,3,4,5 }; li.remove(3); for (auto num : li) { cout << num << " "; } cout << endl; return 0; }
输出结果为:
remove_if:
用于从容器中移除满足特定条件的元素。
bool fun(int num) { return num == 3; } int main() { list<int> li = { 1,2,3,3,4,5 }; li.remove_if(fun); for (auto num : li) { cout << num << " "; } cout << endl; return 0; }
输出结果为:
unique:
用于移除容器中连续重复的元素。
int main() { list<int> li = { 1,2,3,3,4,5 }; li.unique(); for (auto num : li) { cout << num << " "; } return 0; }
输出结果为:
sort:
用于对容器中的元素进行排序。
int main() { list<int> li = { 9,1,5,3,2,4,8,0,7,6 }; li.sort(); for (auto num : li) { cout << num << " "; } return 0; }
输出结果为:
merge:
用于将两个已排序的范围合并成一个有序范围。
要求输入的两个范围必须是有序的(通常是升序)。它会将两个范围中的元素按顺序合并到目标范围中。目标范围必须有足够的空间来存储合并后的结果。
int main() { list<int> li1 = { 1,3,2,5,7 }; list<int> li2 = { 2,3,4,6,8 }; li1.sort(); li2.sort(); li1.merge(li2); for (auto num : li1) { cout << num << " "; } return 0; }
输出结果为:
reverse:
用于反转容器中元素的顺序。
int main()
{
list<int> li = { 9,1,5,3,2,4,8,0,7,6 };
li.reverse();
for (auto num : li)
{
cout << num << " ";
}
return 0;
}
输出结果为:
二、list与vector的对比
vector | list | |
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为0(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致选代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
结束语
求点赞收藏评论关注!!!
感谢各位大佬的支持!!!