登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 132|回复: 1

C++基础知识09.STL容器

[复制链接]

89

主题

2

回帖

36

积分

网站编辑

积分
36
发表于 2025-3-4 22:20:30 | 显示全部楼层 |阅读模式

STL容器

标准模板库(Standard Tem plate Library,STL)是所有 C++ 编译器和操作系统平台都支持的一种模板库。STL 提供了大量的复用软件组织,能让 C++ 程序设计者快速而高效地进行开发。

STL 是惠普实验室开发的一系列标准化组件的统称。1994 年,STL 被纳入 C++ 标准,成为 C++ 库的重要组成部分。

介绍

STL主要由六个部分组成:空间配置器(Allocator)、适配器(Adapters)、容器(Containers)、迭代器(Iterator)、仿函数(Functors)和算法(Algorithm)。STL 的一个基本理念就是将数据和操作分离,数据由容器加以管理,操作则由可定制的算法完成,迭代器在两者之间充当“黏合剂”。

空间配置器

C++ 标准库采用了空间配置器实现对象内存空间的分配和归还,空间配置器是特殊的内存模型。例如,使用 vector 容器,存储数据的空间由空间配置器完成内存的分配和资源回收。空间配置器本质上是对 new 和 delete 运算符再次封装而成的类模板,对外提供可用的接口,实现内存资源的自动化管理。

适配器

适配器主要指容器适配器。容器适配器也是一类容器,它除了能存储普通数据,还可以存储 list、vector、deque 等容器。容器适配器采用特定的数据管理策略,能够使容器在操作数据时表现出另一种行为。例如,使用容器适配器 stack 封装一个 vector<int> 容器,使 vector<int> 容器在处理数据时,表现出栈这种数据结构的特点(先进后出)。STL 提供了三个容器适配器:stack(栈)、queue(队列)和 priority_queue(优先队列)。适配器体现了 STL 设计的通用性,极大提高了编程效率。

迭代器

迭代器是 STL 提供的用于操作容器中元素的类模板,STL 算法利用迭代器遍历容器中的元素,迭代器本身也提供了操作容器元素的方法,使容器元素访问更便捷。迭代器将容器与算法联系起来,起到了“黏合剂”的作用,STL 提供的算法几乎都通过迭代器实现元素访问。STL 提供了输入迭代器、输出迭代器、正向迭代器、双向迭代器和随机访问迭代器五种类型的迭代器,使用迭代器访问容器元素更简单、易用,且代码更加紧凑、简洁。

仿函数

仿函数通过重载()运算符实现,使类具有函数一样的行为。仿函数也称为函数对象,是 STL 很重要的组成部分,它使 STL 的应用更加灵活方便,增强了算法的通用性。大多数 STL 算法可以使用一个仿函数作为参数,以达到某种数据操作的目的。例如,在排序算法中,可以使用仿函数 less 或 greater 作为参数,以实现数据从大到小或从小到大的排序。

算法

算法是 STL 定义的一系列函数模板,是 STL 非常重要的一部分内容。算法可以对容器中的元素施加特定操作。STL 算法不依赖于容器的实现细节,只要容器的迭代器符合算法要求,算法就可以通过迭代器处理容器中的元素。(典型的迭代器设计模式)

STL 容器分类

序列容器 关联容器 无序容器
vector set unordered_set (C++11)
list multiset unordered_multiset (C++11)
deque map multiset_map (C++11)
array(C++11) multimap unordered_multimap (C++11)
forward_list(C++11)

序列容器

  • vector,与 Java 的 ArrayList 类似。也有扩容机制,不过这个扩容一般是变为原先的 2 倍。不同版本的 STL 的具体实现也不一样。

vector

创建容器

vector 模板中定义了多个重载构造函数,因此 vector 容器的创建与初始化也有多种方式。vector 容器的创建方式主要有四种。

#include <iostream>
#include <vector>
using namespace std;

int main(){
    // 创建初始容量为 5 的 vector
    vector<int> v1(5);
    // 容量为 5,并且有五个初始值为 -1 的元素。
    vector<int> v2(5,-1);

    // 列表初始化
    vector<string> v3 = {"a","aa","aaa"};

    // 空初始化,不指定容量
    vector<int> v4;

    return 0;
}

获取容量和已有元素个数

capacity,vector 可以容纳多少元素

size,目前已有的元素个数

#include <iostream>
#include <vector>
using namespace std;

void get(){
    vector<int> v1(2,0);
    cout<<v1.size()<<endl;
    cout<<v1.capacity()<<endl;

    for (size_t i = 0; i < 10; i++){
        v1.push_back(i+10);
        cout<<"当前元素的数量:"<<v1.size()<<"总共可容纳的数量"<<v1.capacity()<<endl;
        /* code */
    }

}

int main(){
    get();
    return 0;
}
/*
2
2
当前元素的数量:3总共可容纳的数量4
当前元素的数量:4总共可容纳的数量4
当前元素的数量:5总共可容纳的数量8
当前元素的数量:6总共可容纳的数量8
当前元素的数量:7总共可容纳的数量8
当前元素的数量:8总共可容纳的数量8
当前元素的数量:9总共可容纳的数量16
当前元素的数量:10总共可容纳的数量16
当前元素的数量:11总共可容纳的数量16
当前元素的数量:12总共可容纳的数量16
*/

访问元素

可以像数组一样访问,也可以用 at 函数访问。

void visited(){
    vector<int> v1;
    int count = 10;
    for (int i = 0; i < count; i++){
        v1.push_back(i+4);
    }
    for (int i = 0; i < count; i++){
        cout<<v1[i]<<"=="<<v1.at(i)<<endl;
    }
}

元素重新赋值

  • 单个元素的赋值,v[i] = 10
  • 批量的元素赋值,
void assignTest(){
    vector<int> v1;
    int count = 10;
    for (size_t i = 0; i < count; i++){
        v1.push_back(i);
    }

    v1[2] = 100;
    cout<<v1[2]<<endl; // 100

    // ssign(n,element) 将 n 个 element 赋值给 vector
    v1.assign(5,-1);
    cout<<v1[0]<<":"<<v1[6]<<endl; // -1,6
}

获取头尾元素,从尾部插入删除元素

方法 说明
void push_back 尾部插入元素
void push_back 尾部删除元素
reference front() 获取头部元素
reference back() 获取尾部元素
void op(){
    vector<int> v1;
    int count = 5;
    for (size_t i = 1; i <= count; i++){
        v1.push_back(i); // 向尾部添加元素。
    }
    // 删除尾部元素
    v1.pop_back();

    // 头部元素1-尾部元素4
    cout<<"头部元素"<<v1.front()<<"-尾部元素"<<v1.back()<<endl;
}

容器迭代

vector 容器提供了迭代器,通过迭代器可以访问、修改容器中的元素。

迭代器 说明
iterator 正向遍历容器元素
reverse_iterator 反向遍历容器元素
const_iterator 正向遍历容器元素,但通过 const_iterator 只能访问容器元素,不能修改元素的值
const_reverse_iterator 反向遍历容器元素,但通过只能访问容器元素,不能修改元素的值。
函数 说明
begin() 返回容器的<b>起始位置</b>的迭代器 iterator
end() 返回容器的<b>结束位置</b>的迭代器 iterator
rbegin() 返回以容器<b>结束位置作为起始位置</b>的反向迭代器 reverse_iterator
rend() 返回以容器<b>起始位置作为结束位置</b>的反向迭代器 reverse_iterator
cbegin() 返回容器的<b>起始位置</b>的常量迭代器 const_iterator,不能修改迭代器指向的内容(C++11)
cend() 返回容器的<b>结束位置</b>的常量迭代器 const_iterator,不能修改迭代器指向的内容(C++11)
crbegin() 返回以容器<b>结束位置作为起始位置</b>的反向迭代器 const_reverse_iterator
crend() 返回以容器<b>起始位置作为结束位置</b>的反向迭代器 const_reverse_iterator
void iter(){
    vector<int> c = {1,2,3};
    vector<int>::iterator iter;
    // 正向迭代
    for (iter = c.begin(); iter != c.end(); iter++){
        cout<<*iter<<"\t";
    }
    cout<<""<<endl;
    vector<int>::reverse_iterator riter;
    // 反向迭代
    for (riter = c.rbegin(); riter!=c.rend(); riter++){
        cout<<*riter<<"\t";
    }
}
/*
1       2       3
3       2       1 
*/

<span style="color:red">迭代器失效:vector 容器是一个顺序容器,在内存中是一块连续的内存,当插入或删除一个元素后,内存中的数据会移动,从而保证数据的连续。当插入或删除数据后,其他数据的地址可能会发生变化,迭代器获取容器位置信息的数据不正确,即迭代器失效,会导致访问出错</span>

任意位置插入数据 - 只能基于迭代器,不能用纯粹的数字

函数 说明
insert(pos, elem) 在 pos 位置上插入元素 elem
insert(pos, n, elem) 在 pos 位置插入 n 个 elem
insert(pos, begin, end) 在 pos 位置插入 [begin, end] 区间的数据★
earse(pos) 删除 pos 位置的元素
earse(begin, end) 删除 [begin, end] 区间的数据
void randomInsert(){
    vector<char> v1;
    v1.insert(v1.begin(),'d');
    v1.insert(v1.begin(),'c');
    v1.insert(v1.begin(),'b');
    v1.insert(v1.begin(),'a');

    for (size_t i = 0; i < v1.size(); i++){
        cout<<v1[i]<<"\t";
    }
    cout<<""<<endl;
    // 在 1 位置插入 5 个 ‘0’
    v1.insert(v1.begin()+1,5,'0');

    for (size_t i = 0; i < v1.size(); i++){
        cout<<v1[i]<<"\t";
    }

    cout<<""<<endl;

    // 擦除起始到结束的所有数据
    v1.erase(v1.begin(),v1.end());

    for (size_t i = 0; i < v1.size(); i++){
        cout<<v1[i]<<"\t";
    }
    cout<<""<<endl;
}
/*
a       b       c       d
a       0       0       0       0       0       b       c       d
*/

deque

双端队列,用法与 vector 一样,区别在于,deque 容器不支持 vector 容器的 reserver 函数、capacity 函数和 data 函数,但是新增了 pop_front 和 push_front 函数。

array(c++11)

array 大小固定,存储结构和数组的存储结构一样,但是比数组有更多的方法。

#include<iostream>
#include<array>
using namespace std;

int main(){
    // 使用时需要声明空间大小
    array<int,10> arr;
    cout<<arr.size()<<endl;

    // 用 10 填充容器
    arr.fill(10);
    arr[0] = 20;

    cout<<arr[0]<<endl;

    // 用迭代器进行迭代
    array<int,10>::iterator pos;
    for (pos = arr.begin(); pos != arr.end(); pos++){
        cout<<*pos<<endl;
    }
}

list

双链表实现。用法与 vector 类似。

函数 说明
list\<T> lt; 创建一个空 list
list\<T> lt(n); 创建一个初始容量为 n 的 list
list\<T> lt(n, elem); 创建一个包含 n 个 elem 的 list
list\<T> lt(begin, end); 创建一个 list,用 [begin, end] 区间的值为元素赋值
list\<T> lt(lt1); 用 lt1 初始化一个 list

赋值操作也 vector 一致,插入删除与 vector 一致

forward_list

C++11 提供,单链表实现。不支持 insert() 函数和 erase() 函数,但它提供了 insert_after() 函数和 erase_after() 函数用于插入和删除元素

关联容器

关联容器中的元素是按关键字来保存和访问的,典型的关联容器有 set 集合和 map 集合。map 与 set 的功能与 Java 中 map、set 的功能基本一致。

C++ 标准库提供了 8 个关联容器。名字包含 multi 的允许重复的关键字,没有包含的则不允许包含重复的关键字。不保持关键字按顺序存储的容器的名字都以单词 unordered 开头。例如:unordered_multi_set 是一个允许重复关键字,元素无序保存的集合。

类型 map 和 multimap 定义在头文件 map 中;set 和 multiset 定义在头文件 set 中;无序容器则定义在头文件 unordered_map 和 unordered_set 中。

<b>按关键字有序保存元素</b> -
map 关联数组:保存 key-value 对
set 只保存关键字
multimap key 可重复的 map
multiset 关键字(key)可重复的 set
<b>无序集合</b> -
unordered_map 哈希函数组织的 map
unordered_set 哈希函数组织的 set
unordered_multimap 关键字可重复出现的 set
unordered_multiset 关键字可重复出现的 map

简单示例

<b>set 和 multiset</b>

set 与 multiset 都是集合,用于存储一组相同数据类型的元素。两者的区别是 set 用来存储一组无重复的元素,而 multiset 允许存储有重复的元素。集合支持插入、删除、查找等操作,但集合中的元素值不可以直接修改,因为这些元素都是自动排序的,如果想修改其中某一个元素的值,必须先删除原有的元素,再插入新的元素。

<b>map 和 multimap</b>

map 是单重映射,multimap 是多重映射。map 的一个 key 只能对应一个 value,而 multimap 一个 key 可以对应多个 value.

#include<iostream>
#include<map>
using namespace std;

int main(){
    map<char,int> m;
    m['a'] = 1;
    m['a'] = 2;
    // 2, 原先的值 1 被覆盖了。
    cout<<m['a']<<endl;
}

map 和 multimap 插入元素的方法与其他容器不太一样。每次插入的是一对元素,参数中的 elem 指的是一对元素。在插入元素时,使用 pair<> 语句或 make_pair() 函数创建一个 pair 对象,将 pair 对象作为 insert() 函数的参数,插入容器中。

#include<iostream>
#include<map>

using namespace std;

int main(){
    map<char,int> m;
    m['a'] = 1;
    m['a'] = 2;
    // 2, 原先的值 1 被覆盖了。
    cout<<m['a']<<endl;

    multimap<char, int>mm;
    mm.insert(make_pair('a',1));
    mm.insert(make_pair('a',11));
    mm.insert(make_pair('a',1));
    auto iter = mm.find('a');
    // 同一个 key 的 value 遍历到了末尾值会变成 mm.end()
    for (auto itr = mm.find('a'); itr != mm.end(); itr++){
        cout << itr->first << '\t' << itr->second << '\n'; 
    }
}
/*
2
a       1
a       11
a       1
*/

使用关联容器

使用 map 对字符出现的次数进行计数

当从 map 中提取一个元素时,会得到一个 pair 类型的对象。pair 是一个模板类型,保存两个名为 first 和 second 的(公有)数据成员。map 所使用的 pair 用 first 成员保存关键字,用 second 成员保存对应的值。因此,输出语句的效果是打印每个字符及其关联的计数器。

void count(){
    // map 统计字符计数。
    string content = "Hello Hello ollo";
    map<char, int> word_count;
    for (size_t i = 0; i < content.length(); i++){
        ++word_count[content[i]];
    }
    for(const auto &w: word_count){
        cout<<w.first<<":"<<w.second<<endl;
    }
}
/*
 :2
H:2
e:2
l:6
o:4
*/

使用 set 统计未在 set 中出现的字符

void count(){
    string content = "Hello oollH";
    map<char, size_t> word_count;
    set<char> exclude = {' ', 'H'};
    for (size_t i = 0; i < content.size(); i++){
        if(exclude.find(content[i]) == exclude.end()){
            // 不存在则统计
            ++word_count[content[i]];
        }
    }

    for (const auto &w : word_count){
        cout<<w.first<<":"<<w.second<<endl;
    }  
}
/*
e:1
l:4
o:3
*/

关联容器概述

定义关联容器

定义一个 map/set 时,必须既指明数据类型。每个关联容器都定义了一个默认构造函数,它创建一个指定类型的空容器。我们也可以将关联容器初始化为另一个同类型容器的拷贝,或是从一个值范围来初始化关联容器,只要这些值可以转化为容器所需类型就可以。在新标准下,我们也可以对关联容器进行值初始化:

map<char, int> word_count; // 空容器
set<string> exclude = {'H', ' '};
map<string, string> authors = {
    {"name", "Jack"},
    {"age", "18"}
};

当初始化一个 map 时,必须提供关键字类型和值类型。将每个 key-value 对包围在花括号中 {key, value}

初始化 multimap/multiset

map/set 的关键字唯一,而 multimap/multiset 的关键字可以有多个。比如 multimap 可以同时保存。

#include<iostream>
#include<map>
#include<string>
using namespace std;

int main(){
    multimap<string,string> map;
    map.insert(make_pair("name","kkx"));
    map.insert(make_pair("name","kkx2"));
    map.insert({"name","kkx3"});

    for(auto const &w : map){
        cout<<w.first<<":"<<w.second<<endl;
    }
}
/*
name:kkx
name:kkx2
name:kkx3
*/

也可以使用其他容器的内容来进行初始化,此处就用 set 作为示例。multiset 也是一样的。

#include<iostream>
#include<vector>
#include<set>
using namespace std;

void test(){
    vector<int> vec;
    for (size_t i = 0; i < 10; i++){
        vec.push_back(i);
    }

    set<int> unique(vec.begin(),vec.end());
    multiset<int> no_unique(vec.begin(),vec.end());
    for(auto const &w : no_unique){
        cout<<w<<endl;
    }
}

习题

  • 定义一个 map,关键字是家庭的姓,值是一个 vector,保存家中孩子的名。编写代码,实现添加新的家庭以及向已有家庭中添加新的孩子。
  • 编写一个程序,在一个 vector 而不是一个 set 中保存不重复的单词。使用 set 的优点是什么?

关联容器操作

获取关联容器的 keyType 和 valueType

void getType(){
    map<string,int>::key_type mpaKeyType;
    map<string,int>::value_type mpaKeyType;
    map<string,int>::mapped_type mapType;
}

0

主题

203

回帖

170

积分

注册会员

积分
170
发表于 2025-3-6 09:42:19 | 显示全部楼层
好好学习!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|断点社区 |网站地图

GMT+8, 2025-4-11 12:17 , Processed in 0.100060 second(s), 24 queries , Yac On.

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

快速回复 返回顶部 返回列表