STL Containers用法与示例
Containers
从实现的角度看,容器是一种class template
- Sequence containers(序列容器):array、vector、deque(队列)、list(双向链表)、forward_list(单向链表)。
- Associative containers(关联容器):set/multiset、map/multimap。
- set/map的value/key不能重复,一般底层是红黑树实现(一种自平衡二叉查找树),红黑树演示地址
- Unordered associative containers(无序关联容器):unordered_set/unordered_multiset,unordered_map/unordered_multimap。
详细资料:
https://en.cppreference.com/w/cpp/container
http://www.cplusplus.com/reference/stl/
- container adapter(容器适配器):queue和stack,虽然看似容器,但其实只是一种容器配接器,因为它们的底部完全借助deque,所有操作都由底层的deque供应。
1 Sequence containers 序列式容器
1.1.array 固定数组
是c++语言本身提供的固定大小的数组
- 使用场景:类似vector,比数组更安全(不担心越界),但是内容在栈上,且属于定长容器。
- 支持快速随机访问,不能添加或删除元素。
- 构造函数、at()、[]、front()、back()、data()、max_size()、swap()、fill()用法如下。
查看代码
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
int main()
{
using namespace std;
array<int, 3> a1{1,2,3};
array<int, 3> a2={3,4,5};
array<string, 2> a3{string("hello"),"word"};
// 成员函数at()会检查范围 操作符[]不会
cout<<a1[1]<<endl;
try
{
cout<<a1.at(5)<<endl;
}
catch(const std::out_of_range& e)
{
std::cout << e.what() << '\n';
}
//front() back() 取数组第一个和最后一个元素
cout<<a2.front()<<endl;
cout<<a2.back()<<endl;
//data() 返回数据的指针
cout<<"head addr: "<<a1.begin()<<" --> "<<a1.data()<<endl; //与开头地址相等
cout<<*(a1.data())<<endl;
//max_size() 最大成员数 在固定数组中等于size
cout<<"max_size:"<<a2.max_size()<<endl;
//swap() 交换两个容器
a1.swap(a2);
//基于范围的for 循环
for(const auto& s: a3)
cout << s << ' ';
cout<<endl;
//用给定值填充容器
a3.fill("hihi");
for(const auto& s: a3)
cout << s << ' ';
cout<<endl;
return 0;
}
1.2. vector 动态数组
可变大小数组,每次满了之后会自动扩容
- 使用场景:需要快速查找,不需要频繁插入/删除。
- 用法指南
- 采用线性连续空间。
- 支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢。所以提供的是Random Access Iterators。
- vector是动态空间,一旦旧有空间满了,当新增一个元素,vector会扩大一倍(不同的编译器实现的扩容方式不一致)。
- push_back() 与emplace_back()区别 参考1 参考2
- 如果参数是左值,两个调用的都是 copy constructor。
- 如果参数是右值,两个调用的都是 move constructor(C++ 11后push_back也支持右值)
- emplace_back支持in-place construction(原地构造,也就是传入普通构造函数的参数即可),直接在容器尾部构造这个元素T,省去了拷贝或移动元素的过程。
- 当对vector的任何操作引起空间重新配置,指向原vector的所有迭代器就都失效了。
- 避免使用
vector<bool>
,它不是一个STL容器。其次,它并不存储bool,它存储的是bool的紧凑表示。可用deque<bool>
替代。 - 容器中数据的布局是否与C兼容,如果是的话,选择vector。
- 常用构造函数、assign()、capacity()、reserve(size_type)、shrink_to_fit()、clear()、insert()、emplace_back()、push_back()、pop_back()等示例如下。
查看代码
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
int main()
{
using namespace std;
//常用构造函数
//1,vector( size_type count ); 2,vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );
//3,vector( size_type count,const T& value,const Allocator& alloc = Allocator());
//4,template< class InputIt >vector( InputIt first, InputIt last,const Allocator& alloc = Allocator() );
vector<int> v1(3); //默认值为0
vector<string,allocator<string> > v2({"hello","word","!"}); //类型中可设置空间分配器
vector<int> v3(10,1,allocator<int>()); //10个1值 参数中也可用一个临时对象设置容器空间配置器
vector<int> v4(v1.begin(),v1.end());
vector<vector<int>> v5(10,vector<int>()); //二维数组 10*X
vector<int> v6{1,4,1,2,3,1,3,1};
// assign() 替换容器的内容。 作用同 operator=
v1.assign(v3.begin(),v3.begin()+4); //用v3范围内的内容替换v1
v3.assign({1,2,3,4,4,56}); //初始化列表化赋值
v4.assign(10,0); //10个0值
for(int x:v4)cout<<x<<" ";
cout<<endl;
//at()、[]、front()、back()、data()、max_size()、swap() 方法同其他容器一致
//capacity() 当前分配了多少空间
cout<<"maxsize:"<<v4.max_size()<<" size:"<<v4.size()<<" capacity():"<<v4.capacity()<<endl;
//reserve(size_type)指定大小内存空间 小于size则忽略
v4.reserve(15);
cout<<"capacity():"<<v4.capacity()<<endl;
//shrink_to_fit() 释放没用的内存
v4.shrink_to_fit();
cout<<"capacity():"<<v4.capacity()<<endl;
//clear() 清空元素
v4.clear();
//1,iterator insert( iterator pos, const T& value ); 在pos前插入元素
//2,void insert( iterator pos, size_type count, const T& value ); pos前插入cout个value
//3,void insert( iterator pos, InputIt first, InputIt last );pos前插入[first, last)范围内值
//4,iterator insert( const_iterator pos, std::initializer_list<T> ilist ); pos插入列表值
v4.insert(v4.end(),1); //1
v4.insert(v4.begin(),3,2); // --2 2 2-- 1
v4.insert(v4.begin()+2,v1.begin(),v1.begin()+2); // 2 2 --1 1-- 2 1
v4.insert(v4.begin()+3,{1,2,3,4}); //2 2 1 1 2 3 4 1 2 1
cout<<"v4: ";
for(int x:v4)cout<<x<<" ";
cout<<endl;
//emplace(pos) 是将参数传递给构造函树,在pos位置前,在容器管理的内存空间中直接构造元素。
// 若该位置有其他元素,则先在其他构造,再添加到指定位置前。其它是复制过去的,不是直接构造
v4.emplace(v4.begin(),0);
v4.emplace(v4.end(),9);
v4.emplace_back(10); //末尾添加
cout<<"v4: ";
for(int x:v4)cout<<x<<" ";
cout<<endl;
//push_back(value)
//pop_back()
v1.push_back(22);
v1.pop_back();
//erase() 删除指定位置和范围[first, last)的元素
v1.erase(v1.begin());
v4.erase(v4.begin(),v4.begin()+4);
return 0;
}
1.3. list 双向链表
- 使用场景:需要频繁插入/删除,不需要快速查找。
- 非连续空间存储,插入删除为常数时间。
- 插入和合并操作不会使原来的迭代器失效,这点与vector不同。
- SGI list不仅是一个双向链表,还是环状双向链表。链表尾端有一个空白节点即可实现前开后闭的区间要求。
- 迭代器不能像vector一样以普通指针作为迭代器。迭代器也不能加常数。
- 常用函数示例如下
查看代码
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
int main()
{
using namespace std;
list<string> l1;
list<string> l2(5,string("hello")); //5个hello字符串
list<string> l3(l2.begin(),l2.end());
list<string> l4(l2);
list<string> l5({"hello","world"});
list<string> l6{"a","b","c","d","e","f"};
//resize(n) 只保留容器前n个元素
l2.resize(2);
//a.merger(b) 合并链表a+=b; 链表第一个元素比较默认小的先放前面,剩下的不变
//被合并链表b变空
l6.merge(l5);
l2={"z","d","bb","shuu"};//重新赋值
l3.assign({"a","a","s"});
l2.merge(l3,greater<string>());
//splice 移动元素,拼接在指定位置前
l6.splice(l6.begin(),l2); //l2变为空
l3.assign({"a","a","s"});
list<string>::iterator it=l3.begin();
advance(it,2); //移动迭代器
l3.splice(it,l6);
l6.splice(l6.begin(),l3,--it,l3.end());
//pop_front 删除头节点
//pop_back
//push_back、emplace_back 队尾新增节点
//push_front 插入头节点
//reverse 反转链表
l6.reverse();
//remove(T) 删除等于T的节点 remove_if(UnaryPredicate P) P为真则删除
l6.remove("s");
//unique 删除 连续 位置上元素的重复值
l3.sort(); //排序
l3.unique();
for(string s:l3)
cout<<s<<" ";
cout<<endl;
return 0;
}
1.4. forward_list 单向链表
- 使用场景:需要list的优势,但只要向前迭代。
1.5 deque 双向队列
- 使用场景:头尾增删元素很快,随机访问比vector慢一点,因为内部处理堆跳转。中间插入和删除效率较高。
- 支持快速随机访问,在头尾位置插入或删除速度很慢。
stack
2 associative containers 关联式容器
set/multiset、map/multimap容器底层均基于RB_tree(红黑树)完成。
2.1 RB_tree
- 点击查看RB_tree的实现
- 不仅是一个二叉搜索树,而且必须满足以下规则
- 每个节点不是红色就是黑色。
- 根节点为黑色。
- 如果为红,其子节点必须为黑。
- 任意节点致NULL(树尾端)的任何路径,所含黑色节点必须相同。
- RB_tree出现原因,AVL树条件太苛刻,新增节点是旋转太多了,耗费性能。
- RB_tree也是平衡树,不过是黑色平衡。
2.2 set
所有元素会根据元素的键值自动排序
- 使用场景:需要元素有序,查找/删除/插入性能一样。红黑树效率都是O(logN)。即使是几个亿的内容,最多也查几十次。
- 与map不同,set的键值(key)就是实值(value)。
- set不允许两个元素有相同的键值。
- 与list相同,新增或者删除时,之前的迭代器依然有效。
查看代码
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
using namespace std;
int main()
{
typedef set<int>::iterator itr;
set<int> s1{1, 2, 9, 4, 6, 8};
itr it = s1.begin();
advance(it,2); //移动迭代器
//emplace_hint 在可能的位置插入
s1.emplace_hint(it, 0);
s1.insert(9);
// equal_range 返回一个pair,第一个指向首个不小于 key 的元素,第二个指向首个大于 key 的元素。
pair<itr,itr> range=s1.equal_range(4);
cout<<*(range.first)<<endl;
cout<<*(range.second)<<endl;
//返回第一个指向首个不小于 key 的元素迭代器
itr it2=s1.lower_bound(4);
cout<<*it2<<endl;
// 返回首个大于 key 的元素迭代器
it2=s1.upper_bound(4);
cout<<*it2<<endl;
//返回比较值的函数对象
set<int>::value_compare cmp1= s1.value_comp();
//返回比较key的函数对象
set<int>::key_compare cmp1= s1.key_comp();
for (int x : s1)
{
cout << x << " ";
}
cout<<endl;
return 0;
}
部分参考:
https://blog.csdn.net/fatterrier/article/details/115413194
本文作者:oniisan
本文链接:https://www.cnblogs.com/oniisan/p/STLContainers.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步