C++ STL之容器(使用方法)

1. 容器简介

       容器就是数据结构,用来将数据元素按照一定的规则进行排列,不同的容器拥有不同的排列规则,不同的排列规则可以达到不同的数据操作特点。容器只需要提供迭代器 算法只需要拿到迭代器就可以完成容器和算法之间的关联和操作

  • 顺序容器:数组(array),动态数组(vector),双向队列(deque),双向链表(list),单向链表(forward_list),字符串(string)
  • 关联容器:map、set、multimap、multiset、unordered_set、unordered_map、unordered_multiset、unordered_multimap
  • 容器适配器:单向队列(queue),栈(stack)

Alt
Alt
alt

2. String

       string 封装了 char*,管理这个字符串,是一个 char*型的容器。string 管理 char*所分配的内存。每一次 string 的复制,取值都由 string 类负责维护,不用担心复制越界和取值越界等。

2.1 构造操作

string();//创建一个空的字符串 例如: string str;
string(const string& str);//使用一个 string 对象初始化另一个 string 对象
string(const char* s);//使用字符串 s 初始化
string(int n, char c);//使用 n 个字符 c 初始化
//例子:
//默认构造函数
string s1;
//拷贝构造函数
string s2(s1);
string s2 = s1;
//带参数构造函数
char* str = "itcast";
string s3(str);
string s4(10, 'a');

2.2 赋值操作

string& operator=(const char* s);//char*类型字符串 赋值给当前的字符串
string& operator=(const string &s);//把字符串 s 赋给当前的字符串
string& operator=(char c);//字符赋值给当前的字符串
string& assign(const char *s);//把字符串 s 赋给当前的字符串
string& assign(const char *s, int n);//把字符串 s 的前 n 个字符赋给当前的字符串
string& assign(const string &s);//把字符串 s 赋给当前字符串
string& assign(int n, char c);//用 n 个字符 c 赋给当前字符串
string& assign(const string &s, int start, int n);//将 s 从 start 开始 n 个字符赋值给字符串

2.3 常用操作(存取字符串、拼接、 查找、替换、比较、插入、删除)

//存取字符串
char& operator[](int n);//通过[]方式取字符,越界程序挂掉
char& at(int n);//通过 at 方法获取字符,越界会抛出异常

string s = "itcast";
char c = s[1];
c = s.at(1);

//拼接
string& operator+=(const string& str);//重载+=操作符
string& operator+=(const char* str);//重载+=操作符
string& operator+=(const char c);//重载+=操作符
string& append(const char *s);//把字符串 s 连接到当前字符串结尾
string& append(const char *s, int n);//把字符串 s 的前 n 个字符连接到当前字符串结尾
string& append(const string &s);//同 operator+=()
string& append(const string &s, int pos, int n);//把字符串 s 中从 pos 开始的 n 个字符连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加 n 个字符 c

//查找和替换
int find(const string& str, int pos = 0) const; //查找 str 第一次出现位置,从 pos 开始查找
int find(const char* s, int pos = 0) const; //查找 s 第一次出现位置,从 pos 开始查找
int find(const char* s, int pos, int n) const; //从 pos 位置查找 s 的前 n 个字符第一次位置
int find(const char c, int pos = 0) const; //查找字符 c 第一次出现位置
int rfind(const string& str, int pos = npos) const;//查找 str 最后一次位置,从 pos 开始查找
int rfind(const char* s, int pos = npos) const;//查找 s 最后一次出现位置,从 pos 开始查找
int rfind(const char* s, int pos, int n) const;//从 pos 查找 s 的前 n 个字符最后一次位置
int rfind(const char c, int pos = 0) const; //查找字符 c 最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从 pos 开始 n 个字符为字符串 str
string& replace(int pos, int n, const char* s); //替换从 pos 开始的 n 个字符为字符串 s

//比较
int compare(const string &s) const;//与字符串 s 比较,等于时返回0
int compare(const char *s) const;//与字符串 s 比较,等于时返回0

//切割子串
string substr(int pos = 0, int n = npos) const;//返回由 pos 开始的 n 个字符组成的字符串

//插入删除
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入 n 个字符 c
string& erase(int pos, int n = npos);//删除从 Pos 开始的 n 个字符

3. Vector

       vector 是个动态数组,连续内存空间,随机存取效率高。当空间不足的时候插入新元素,vector 会重新申请一块更大的内存空间(每次增长一倍),将旧空间数据拷贝到新空间,然后释放旧空间。vector 是单口容器,所以在尾端插入和删除元素效率较高,在指定位置插入,势必会引起数据元素移动,效率较低
在这里插入图片描述

3.1 构造操作

vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将 v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将 n 个 elem 拷贝给本身。
vector(const vector &vec);//拷贝构造函数。
//例子 使用第二个构造函数 我们可以...
int arr[] = {2,3,4,1,9};
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));

3.2 赋值操作

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将 vec 与本身的元素互换,实际交换彼此的指针
//第一个赋值函数,可以这么写:
int arr[] = { 0, 1, 2, 3, 4 };
assign(arr, arr + 5);//使用数组初始化 vector

3.3 常用操作(大小、 查找、替换、比较、插入、删除)

//大小
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量,因为当容空间器不够时会⾃动扩充空间 ,但不⼀定都会填满,所以可能与size()不等
reserve(int len);//容器预留 len 个元素长度,预留位置不初始化,元素不可访问。
//reserve 是容器预留空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素
//resize 是改变容器的大小,且在创建对象,因此,调用这个函数之后,就可以引用容器内的对象了.

//存取
at(int idx); //返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range 异常。
operator[];//返回索引 idx 所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素

//插入和删除
insert(const_iterator pos, int count,ele);//迭代器指向位置 pos 插入 count 个元素 ele.
push_back(ele); //尾部插入元素 ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从 start 到 end 之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素

4. Deque

       deque 则是一种双向开口的连续性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,vector 当然也可以在头尾两端进行插入和删除操作,但是头部插入和删除操作效率奇差,无法被接受。deque是动态的以分段的连续空间组合而成,随时可以增加一段新的空间并链接起来,由中控器来维持连续的假象
在这里插入图片描述
在这里插入图片描述

4.1 构造操作

deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将 n 个 elem 拷贝给本身。
deque(const deque &deq);//拷贝构造函数。

4.2 赋值操作

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将 deq 与本身的元素互换

4.3 常用操作(大小、插入、删除、存取)

//大小
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。

//插入和删除
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据
insert(pos,elem);//在 pos 位置插入一个 elem 元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
insert(pos,beg,end);//在 pos 位置插入[beg,end)区间的数据,无返回值。
at(idx);//返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range。
operator[];//返回索引 idx 所指的数据,如果 idx 越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据

//存取
at(idx);//返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range。
operator[];//返回索引 idx 所指的数据,如果 idx 越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据

5. Stack

       stack是一种先进后出(first in last out,FILO)的数据结构,它只有一个出口,stack 只允许在栈顶新增元素,移除元素,获得顶端元素,但是除了顶端之外,其他地方不允许存取元素,只有栈顶元素可以被外界使用,也就是说stack不具有遍历行为,没有迭代器
在这里插入图片描述

5.1 构造操作

stack<T> stkT;//stack 采用模板类实现, stack 对象的默认构造形式:
stack(const stack &stk);//拷贝构造函数

5.2 赋值操作

stack& operator=(const stack &stk);//重载等号操作符

5.3 存取操作

push(elem);//向栈顶添加元素
pop();//从栈顶移除第一个元素
top();//返回栈顶元素

5.4 大小操作

empty();//判断堆栈是否为空
size();//返回堆栈的大小

6. Queue

       queue 是一种先进先出(first in first out, FIFO)的数据类型,他有两个口,数据元素只能从一个口进,从另一个口出。队列只允许从队尾加入元素,队头删除元素,必须符合先进先出的原则,queue 和 stack 一样不具有遍历行为。
在这里插入图片描述

6.1 构造操作

queue<T> queT;//queue 采用模板类实现,queue 对象的默认构造形式:
queue(const queue &que);//拷贝构造函数

6.2 赋值操作

queue& operator=(const queue &que);//重载等号操作符

6.3 存取操作

push(elem);//往队尾添加元素
pop();//从队头移除第一个元素
back();//返回最后一个元素
front();//返回第一个元素

6.4 大小操作

empty();//判断队列是否为空
size();//返回队列的大小

7. List

       链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。STL中的list为双向链表,索引时迭代器可前进或后退。

  • 采用动态存储分配,不会造成内存浪费和溢出
  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
  • 链表灵活,但是空间和时间额外耗费较大
    在这里插入图片描述

7.1 构造操作

list<T> lstT;//list 采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将 n 个 elem 拷贝给本身。
list(const list &lst);//拷贝构造函数。

7.2 赋值操作

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将 lst 与本身的元素互换。

7.3 存取操作

front();//返回第一个元素。
back();//返回最后一个元素。

7.4 常用操作(插入、删除、大小、排序)

//插入和删除
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在 pos 位置插 elem 元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
insert(pos,beg,end);//在 pos 位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除 pos 位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与 elem 值匹配的元素。

//大小
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。

//反转排列排序
reverse();//反转链表,比如 lst 包含 1,3,5 元素,运行此方法后,lst 就包含 5,3,1 元素。
sort(); //list 排序

8. Set/Multiset

       set/multiset 的特性是所有元素会根据元素的值自动进行排序(默认从小到大排序)。set 是以 RB-tree(红黑树,平衡二叉树的一种)为底层机制,其查找效率非常好。set 容器中不允许重复元素,multiset 允许重复元素。set 集合是根据元素值进行排序,关系到 set 的排序规则,如果任意改变 set 的元素值,会严重破坏 set 组织。
在这里插入图片描述

8.1 构造操作

set<T> st;//set 默认构造函数:
set<T,func> st;//当T为对象时,需要指定针对于该对象的排序规则,即传入仿函数func
mulitset<T> mst; //multiset 默认构造函数:
set(const set &st);//拷贝构造函数

8.2 赋值操作

set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器

8.3 常用操作(大小、插入、删除、查找)

//大小
size();//返回容器中元素的数目
empty();//判断容器是否为空

//插入和删除
insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为 elem 的元素。

//查找
find(key);//查找键 key 是否存在,若存在,返回该键的元素的迭代器;若不存在,返回 map.end();
lower_bound(keyElem);//返回第一个 key>=keyElem 元素的迭代器。
upper_bound(keyElem);//返回第一个 key>keyElem 元素的迭代器。
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器。
pair<set<int>::iterator,set<int>::iterator> ret = equal_range(2);
ret.first//第一个迭代器
ret.second//第二个迭代器

9. Map/Multimap

       map 相对于 set 区别,map 具有键值和实值,所有元素根据键值自动排序。pair 的第一元素被称为键值,第二元素被称为实值。map 也是以红黑树为底层实现机制。

9.1 构造操作

map<T1, T2> mapTT;//map 默认构造函数,key:value
map(const map &mp);//拷贝构造函数

9.2 赋值操作

map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器

9.3 常用操作(大小、插入、删除、查找)

//大小
size();//返回容器中元素的数目
empty();//判断容器是否为空

//插入和删除
map.insert(...); //往容器插入元素,返回 pair<iterator,bool>
map<int, string> mapStu;
// 第一种 通过 pair 的方式插入对象
mapStu.insert(pair<int, string>(3, "小张"));
// 第二种 通过 pair 的方式插入对象
mapStu.inset(make_pair(-1, "校长"));
// 第三种 通过 value_type 的方式插入对象
mapStu.insert(map<int, string>::value_type(1, "小李"));
// 第四种 通过数组的方式插入值
mapStu[3] = "小刘";
mapStu[5] = "小王";
clear();//删除所有元素
erase(pos);//删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中 key 为 keyElem 的对组。

//查找
find(key);//查找键 key 是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回 map.end();
count(keyElem);//返回容器中 key 为 keyElem 的对组个数。对 map 来说,要么是 0,要么是 1。对
multimap 来说,值可能大于 1lower_bound(keyElem);//返回第一个 key<=keyElem 元素的迭代器。
upper_bound(keyElem);//返回第一个 key>keyElem 元素的迭代器。
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器。

10. unordered_set/multiset、unordered_map/multimap

       unordered_set/multiset、unordered_map/multimap是无序的,底层实现结构为哈希表(hash table),使用方法与8,9相似。唯独需要注意的就是unordered_set/multiset的元素是非C++内建类型时、unordered_map/multimap的key是非C++内建类型时,需要传入一个计算hash值的仿函数(hash function)
alt

//unordered_map模板定义
template<//key:value
    class Key,//键值,key
    class T,//实值,value,
    class Hash = std::hash<Key>,//计算hash值,当key为C++内建类型时,有默认计算hash值得函数对象使用;否则需要自己传入计算hash值的新规则函数对象(仿函数)
    class KeyEqual = std::equal_to<Key>,//判断索引值相等的函数对象,与hash值类似,可能需要自定义
    class Allocator = std::allocator<std::pair<const Key, T>>
>
class unordered_map;
#include <iostream>
#include <string>
#include <unordered_map>
 
int main()
{
    // 创建包含三个字符串的(映射到字符串的) unordered_map
    std::unordered_map<std::string, std::string> u =
    {
        {"红色", "#FF0000"},
        {"绿色", "#00FF00"},
        {"蓝色", "#0000FF"}
    };
 
    // 用于打印键值对的辅助 lambda 函数
    auto print_key_value = [](const auto& key, const auto& value)
    {
        std::cout << "键:[" << key << "] 值:[" << value << "]\n";
    };
 
    std::cout << "遍历并打印 unordered_map 的键值对,显式指定类型:\n";
    for (const std::pair<const std::string, std::string>& n : u)
        print_key_value(n.first, n.second);
 
    std::cout << "\n通过 C++17 的结构化绑定来遍历并打印 unordered_map 的键值对:\n";
    for (const auto& [key, value] : u)
        print_key_value(key, value);
 
    // 向 unordered_map 中添加两项
    u["黑色"] = "#000000";
    u["白色"] = "#FFFFFF";
 
    std::cout << "\n通过键输出值:\n"
                 "红色的十六进制表示:[" << u["红色"] << "]\n"
                 "黑色的十六进制表示:[" << u["黑色"] << "]\n\n";
 
    std::cout << "通过以先前不存在的键使用 operator[] 来插入新的键值对:\n";
    print_key_value("新键", u["新键"]);
 
    std::cout << "\n通过使用 auto 来遍历并打印键值对;\n"
                 "现在`新键`是映射中的一个键:\n";
    for (const auto& n : u)
        print_key_value(n.first, n.second);
}
//unordered_set模板定义
template<
    class Key,//元素的类型
    class Hash = std::hash<Key>,//计算hash值,当key为C++内建类型时,有默认计算hash值得函数对象使用;否则需要自己传入计算hash值的新规则函数对象(仿函数)
    class KeyEqual = std::equal_to<Key>,//判断索引值相等的函数对象,与hash值类似,可能需要自定义
    class Allocator = std::allocator<Key>//空间分配器,一般情况下无需修改
>
class unordered_set;
//unordered_set示例
#include <iostream>
#include <unordered_set>
 
void print(const auto& set)
{
    for (const auto& elem : set)
        std::cout << elem << ' ';
    std::cout << '\n';
}
 
int main()
{
    std::unordered_set<int> mySet{2, 7, 1, 8, 2, 8}; // 创建 int 的集合
    print(mySet);
 
    mySet.insert(5); // 将元素 5 加入集合
    print(mySet);
 
    if (auto iter = mySet.find(5); iter != mySet.end())
        mySet.erase(iter); // 移除 iter 指向的元素
    print(mySet);
 
    mySet.erase(7); // 移除元素 7
    print(mySet);
}

11. 容器使用时机

  • vector 的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
  • deque 的使用场景:比如排队购票系统,对排队者的存储可以采用 deque,支持头端的快速移除,尾端的快速添加。如果采用 vector,则头端移除时,会移动大量的数据,速度慢。
  • vector 与 deque 的比较
    (1)vector.at()比 deque.at()效率高,比如 vector.at(0)是固定的,deque 的开始位置
    却是不固定的。
    (2)如果有大量释放操作的话,vector 花的时间更少,这跟二者的内部实现有关。
    (3)deque 支持头部的快速插入与快速移除,这是 deque 的优点。
  • list 的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
  • set 的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
  • map 的使用场景:比如按 ID 号存储十万个用户,想要快速要通过 ID 查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是 vector 容器,最坏的情况下可能要遍历完整个容器才能找到该用户。

在这里插入图片描述




  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值