STL Containers用法与示例

Containers

从实现的角度看,容器是一种class template

容器的结构与分类

  1. Sequence containers(序列容器):array、vector、deque(队列)、list(双向链表)、forward_list(单向链表)。
  2. Associative containers(关联容器):set/multiset、map/multimap。
    • set/map的value/key不能重复,一般底层是红黑树实现(一种自平衡二叉查找树),红黑树演示地址
  3. 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(容器适配器):queuestack,虽然看似容器,但其实只是一种容器配接器,因为它们的底部完全借助deque,所有操作都由底层的deque供应。

1 Sequence containers 序列式容器

1.1.array 固定数组

是c++语言本身提供的固定大小的数组

  • 使用场景:类似vector,比数组更安全(不担心越界),但是内容在栈上,且属于定长容器。
  • 支持快速随机访问,不能添加或删除元素。
  • 构造函数、at()、[]、front()、back()、data()、max_size()、swap()、fill()用法如下。
查看代码
#include<iostream>
#include <array>
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()等示例如下。
查看代码
#include<iostream>
#include <memory> //Allocator
#include<vector>
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一样以普通指针作为迭代器。迭代器也不能加常数。
  • 常用函数示例如下
查看代码
#include<iostream>
#include<list>
#include<string>
#include<functional> //greater<>()
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相同,新增或者删除时,之前的迭代器依然有效。
查看代码
#include <set>
#include <iostream>
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

posted @ 2022-02-28 15:08  Oniisan_Rui  阅读(129)  评论(0)    收藏  举报