C++ STL 概述_严丝合缝的合作者们
1. 初识 STL
什么是STL
?
STL(Standard Template Library)
是C++
以模板形式提供的一套标准库,提供了很多开发过程需要的通用功能模块。使用 STL
,可以让开发者将主要精力用于解决程序的高级业务逻辑,而无须关心底层的基础逻辑调用。
STL
由 6
大部分组成:
容器
:存储和组织数据的类模板
,是STL
的核心。迭代器
:独立于容器,提供访问容器中数据的通用操作组件。算法
:提供通用基础算法功能,算法通过迭代器对容器中的数据进行查找、计算……。函数对象
:重载了括号运算符()
的模板类,为算法提供灵活的策略。适配器
:通过对已有的容器、迭代器、函数对象进行适配,创造出新的编程组件。配置器
:为容器服务,负责其内存空间的配置与管理。
6
大部件遵循单一职责设计思想,组件与组件之间彼此独立,每一个组件在各自内部高度自治性地实现分配到的功能。
各组件在合作关系上,互为依赖,相互之间形成服务与被服务关系。从而构建出一个精密、灵活、具有高度自适应的编程环境。
如下图为组件之间的分工合作关系:
学习STL
包括:
- 了解、熟悉各组件的使用。
- 掌握各组件之间的服务关系。
因STL
知识体系庞大而复杂,非一言二语能讲透。本文仅俯瞰一下STL
,对STL
有一个大概了解,对每一个组件的细节讨论将留到系列文章中讲解。
下面通过一个简单的案例初步了解容器、迭代器、算法、函数对象
之间的合作关系。
案例需求:求解一个已知数列中的所有质数(质数:只能被 1
和自身整除的数字)。
设计流程:
- 首先在源代码文件的头部包含程序中需要用到的所有头文件。
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
- 认识
STL
的vector
容器,使用它存储已知数列。
STL
中的容器种类繁多,容器之间有共性操作、也存在个体差异性,可适配于不同的应用场景。在常规操作时,可选择
vector
容器,需要包含<vector>
头文件。
vector<int> nums= {2,9,10,13,21,5};
- 认识
迭代器
,遍历容器。迭代器类似于指针,用于访问容器。
//获取到指向容器第一个数据的迭代器
vector<int>::iterator begin=nums.begin();
//获取到指向器结束位置的迭代器,注意,并不是最后一个数据,而是最后一个数据的下一个存储位置
vector<int>::iterator end=nums.end();
//使用迭代器输出容器中数据
while(begin!=end){
cout<<*begin<<"\t";
begin++;
}
cout<<endl;
- 认识函数对象,使用函数对象编写求解质数的算法。函数对象可以为
STL
的算法组件提供特定的算法策略,算法组件充当了平台功能,利用平台耦合容器、函数对象。类似于拼搭游戏,可以有各种可能。
下面代码用到了
sqrt
函数,需要包含<cmath>
头文件。
//使用结构体作为函数对象
template <typename T>
struct Zs {
// 函数对象的特点:重载 () 运算符
void operator()(T & x) const {
//求解质数的算法
bool isZs=true;
for(T i=2; i<=sqrt(x); i++) {
if (x % i==0) {
isZs=false;
break;
}
}
if(isZs)
cout<<x<<"是质数"<<endl;
}
};
- 使用
for_each
算法 。STL
提供了大量算法,使用时需要包含<algorithm>
头文件。
//重新指向容器的开始位置(因为前面的操作移动过迭代器)
begin=nums.begin();
//使用 for_each 算法组件
for_each(begin,end,Zs<int>());
- 输出结果。
STL
使用了高内聚、低耦合的设计理念,各组件的专业能力非常强,合作时又能做到严丝合缝、润物细无声。
容器
专注于数据的存储。迭代器
专注于容器的访问。函数对象
提供具体的算法策略。算法
相当于发动机,提供聚合动力。
容器是STL
的核心(无数据无程序)组件,且类型繁多,下文将简要介绍容器的共性操作。
2. 容器
STL
中的容器和数组相似,能够存储数据集,但有其自身的特点:
- 支持容量的自动增长。当添加数据时,如果容量不够时,容器会自动分配新的内存。
- 容器可以迭代。
- 支持数据类型参数(泛型编程)。
2.1 分类
STL
中的容器众多,有点乱入花丛渐迷眼的既视感。一般会按照存储方式对其进行分类:
- 序列式容器:数据以添加时的顺序进行存储,当然可以对数据排序。
- 关联式容器:数据由
键
和值
两部分组成。
2.1.1 序列式容器
序列式容器的存储方案有 2
种:
- 连续(线性)存储:基于数组的存储方式,数据与数据在内存中是相邻的。
- 链式(非线性)存储:以节点的方式非线性存储。数据与数据在内存中并不一定相邻,结点之间通过存储彼此的地址知道对方的位置。
STL
中常用到的序列式容器对象:
vector
:向量,线性存储,类似于数组。需要包含<vector>
头文件。list
:双向链表,非线性存储。需要包含<list>
头文件。slist
:单向链表,非线性存储。需要包含<slit>
头文件。deque
:双向队列。需要包含<deque>
头文件。- stack:栈,先进后出。需要包含
<stack>
头文件。 queue
:队列,数据先进先出。需要包含<queue>
头文件。priority_queue
:优先级队列。需要包含<queue>
头文件。
2.1.2 关联式容器
关联式容器也有 2
种存储方案:
- 使用搜索二叉树:容器中的元素依照键值进行排序。
STL
是用红黑树实现关联容器,红黑树是一种查找效率很高的平衡搜索二叉树。
- 使用哈希表:对键值进行哈希算法,然后根据哈希值把数据存储在不同的单元中。
STL
中常用的关联容器:
set
:集合。包含头文件<set>
。map
:映射。包含头文件<map>
。multiset
:可重复集合。包含头文件<set>
。multimap
:可重复映射。包含头文件<map>
。
2.2 容器的通用操作
2.2.1 初始化
使用容器时包含:
创建容器。
初始化容器。初始化时可以指定容器的容量、为容器指定一系列初始值、为容器中的数据指定比较方法……
初始化序列化容器:
#include <iostream>
#include <vector>
#include <algorithm>
#include <list>
#include <set>
using namespace std;
//使用结构体作为函数对象
int main(int argc, char** argv) {
//初始容量为 12 向量容器
vector<int> vec(12);
cout<<"容器大小:"<<vec.size()<<endl;
//初始化长度为 2,且值为 12 、30的向量容器
vector<int> vec1 {12,30};
cout<<"容器大小:"<<vec1.size()<<endl;
//构造整型链表,初始容量 34
list<int> lst(34);
cout<<"容器大小:"<<lst.size()<<endl;
//整型数组
int ary1[5]= {1,2,3,4,5};
//用数组初始化
vector<int> vec2(ary1,ary1+5);
cout<<"容器大小:"<<vec2.size()<<endl;
//用向量初始化链表
list<int> intList(vec.begin(),vec.end());
cout<<"容器大小:"<<intList.size()<<endl;
//用链表初始化集合
set<int> intSet(lst.begin(),lst.end());
cout<<"容器大小:"<<intSet.size()<<endl;
return 0;
}
输出结果:
初始化map、set
容器时。
//创建并初始化集合
set<int> mySet {1,5,3};
//构造 map 容器
map<std::string, int> myMap;
//构造并初始化
std::map<std::string, int>myMap{ {"rose",1},{"jone",2} };
//输出
for (auto iter = myMap.begin(); iter != myMap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
输出结果:
jone 2
rose 1
2.2.2 添加数据
一般要求容器组件提供对数据进行常规维护的方法(增、删、改、查……)。
STL
为 2
类容器提供了insert
方法,可以在指定的位置为容器加入新的数据。
这里需要注意:
STL
位置一般用迭代器描述,而不是索引位置。
// 初始化向量
vector<int> vec {1, 2, 3, 4, 5};
//开始迭代器
vector<int>::iterator begin=vec.begin();
//结束迭代器
vector<int>::iterator end=vec.end();
cout<<"原向量容器数据"<<endl;
for(; begin!=end; begin++) {
cout<<*begin<<"\t";
}
//重置开始位置
begin=vec.begin();
// 指向容器vec的第三个元素
begin =begin + 2;
//在位置 3 插入数据
vec.insert( begin, 6 );
//重置开始和结束位置
begin=vec.begin();
end=vec.end();
cout<<"\n插入数据后:"<<endl;
for(; begin!=end; begin++) {
cout<<*begin<<"\t";
}
输出结果:
关联式容器的插入数据效果和序列式容插入效果会有不同。
- 序列式容器中插入数据后,期望位置和最终结果位置是一样的。如期望插入在第
3
个数据之后,实际也是插入在第3
个数据之后。 - 关联式容器会自动按
键
进行位置重排,会出现期望位置和最终位置不一样的情况(特别在以红黑树存储数据时,为了保持平衡性,会对数据进行平衡处理)。
STL
还为序列式容器提供了push、push_back、push_front
方法,此方法只能在容器头或容器尾进行数据添加。
// 声明一个向量
vector<int> vec(10);
// 压入数据
vec.push_back(1);
vec.push_back( 1 );
vec.push_back( 2 );
// 声明一个链表
list<int> ls(10);
// 压入数据
ls.push_back( 1 );
ls.push_front( 2 );
// 声明一个栈,栈只有 push 方法
stack<string> st;
// 压入数据
st.push("A");
2.2.3 删除数据
STL
的容器都有 erase
方法,用来删除指定位置或区间的数据。也提供有clear
方法,用来清除整个容器。
位置和区间都需使用迭代器指定。
// 初始化向量
vector<int> vec {1, 2, 3, 4, 5, 6};
//指向容器vec的第三个元素
vector<int>::iterator iter = vec.begin() + 2;
// 删除第三个元素
vec.erase(iter);
//指向容器vec的第三个元素
iter = vec.begin() + 2;
// 删除第二个元素之后的所有元素
vec.erase(iter, vec.end() );
// 构造一个集合
set<int> intSet( ary1, ary1+5 );
// 删除键值为4的元素(集合的键值与实值是一致的)
intSet.erase( 4 );
2.2.4 查找数据
序列式容器没有提供查找方法,但是,如果某容器类重载了[]
运算符,则可以通过给定数据的索引号找到相应数据,也可以通过 at
方式进行查找。
// 初始化向量
vector<int> vec {1, 2, 3, 4, 5, 6};
int tmp= vec[2];
cout<<tmp<<endl;
//效果上面一样
tmp= vec.at(2);
cout<<tmp<<endl;
序列式容器一般都会提供front
和back
方法,用来返回第一个和最后一个数据。因为栈的特殊性,只有top
方法用来返回栈顶数据。
vector<int> vec {1, 2, 3, 4, 5, 6};
list<int> intList( vec.begin(), vec.end() );
//返回第一个数据
x = intList.front();
//返回最后一个数据
x = intList.back();
stack<int,vector<int> > st;
//返回栈顶数据
x = st.top();
关联式容器提供有专门的find
方法,可通过指定键值进行查找,注意,返回的是用迭代器所描述的位置。
// 整数型数组
int ary[5] = { 3, 1, 5, 2, 4};
// 构造集合
set<int> intSet( ary, ary+5 );
// 查找集合中键值为4的元素
set<int>::iterator iter = intSet.find( 4 );
//输出
cout<<*iter<<endl;
基于组件的分工合作设计思想,容器自身的查找只会提供一些基本功能。当有更复杂的查找需求时,可以使用STL
算法中相应的函数模板进行查询,例如find
,find_if
,find_end
和find_first_of
。
2.2.5 修改数据
可以先查找到要修改的数据,然后直接修改,如果查找数据时返回的是迭代器,则可以通过迭代器进行修改。
// 构造向量
vector<int> vec { 3, 1, 5, 2, 4};
//直接修改
vec[3] =9;
//[] 反回的是向量数据的引用
int &refTmp=vec[3];
//和前面的直接修改一样
refTmp=9;
map<int,int> myMap();
//按键值查找,返回迭代器
map<int,int>::iterator iter=myMap.find(10);
//通过迭代器修改
iter->second=8;
//和上面的效果一样
myMap[10]=8;
2.2.6 其它方法
begin
: 返回容器开始位置的迭代器。end
:返回容器尾部数据后一个存储位置的迭代器。rbegin
:求指向容器反向开始元素的迭代器。rend
:求容器反向结尾元素后一个存储单元的迭代器。swap
:交换两个容器的内容。swap
方法用来交换两个容器的内容。要求两个容器的类型、大小相同。
//构造两个向量
vector<int> v1 {1, 2, 3};
vector<int> v2 {4, 5, 6};
//交换两个向量
v1.swap(v2);
vector<int>::iterator iter = v2.begin();
//输出向量v2的内容
for(; iter != v2.end(); iter++) {
cout<<*iter<<endl;
}
==、!=、<、<=、>、>=
:比较运算符,判断两个容器之间的关系。比较返回结果是第一对不相等数据间的比较结果。如果两个容器的数据数目不相等,则容器不相等。
// 定义两个向量
vector <int> v1, v2;
// 在v1中加入数据
v1.push_back( 1 );
v1.push_back( 2 );
v1.push_back( 3 );
// 在v2中加入数据
v2.push_back( 1 );
v2.push_back( 3 );
//返回结果是 V1 第一个数据与 V2 中第一个数据的比较结果
bool res=v1 < v2;
// 输出1,true 如果 v1 的第一个数据是 4 则,输出 0
cout<< "v1 < v2:" <<res <<endl;
3. 总结
STL
是一个庞大且功能非常完善的组件库,本文仅对其做了一个大概的描述,但是,一叶也能知秋,旨在理顺其脉络,先画出STL 旅行地图,然后再一一击破。
C++ STL 概述_严丝合缝的合作者们的更多相关文章
- C++框架_之Qt的开始部分_概述_安装_创建项目_快捷键等一系列注意细节
C++框架_之Qt的开始部分_概述_安装_创建项目_快捷键等一系列注意细节 1.Qt概述 1.1 什么是Qt Qt是一个跨平台的C++图形用户界面应用程序框架.它为应用程序开发者提供建立艺术级图形界面 ...
- STL模板_容器概念
一.STL(Standard Template Library,标准模板库)概述1.容器:基于泛型的数据结构.2.算法:基于泛型的常用算法.3.迭代器:以泛型的方式访问容器中的元素,是泛型的算法可以应 ...
- 1.1 STL 概述
综述 STL = Standard Template Library,标准模板库,惠普实验室开发的一系列软件的统称.它是由Alexander Stepanov.Meng Lee和David R M ...
- STL概述
一.关于STL STL(Standard Template Library,标准模板库)是C++语言标准中的重要组成部分.STL 以模板类和模板函数的形式为程序员提供了各种数据结构和算法的精巧实现,程 ...
- (1/18)重学Standford_iOS7开发_iOS概述_课程笔记
写在前面:上次学习课程对iOS还是一知半解,由于缺乏实践,看公开课的视频有时不能很好地领会知识.带着问题去学习永远是最好的方法,接触一段时间iOS开发以后再来看斯坦福iOS公开课,又会有许多新的发现, ...
- STL模板_概念
模板和STL一.模板的背景知识1.针对不同的类型定义不同函数版本.2.借助参数宏摆脱类型的限制,同时也因为失去的类型检查而引 入风险.3.借助于编译预处理器根据函数宏框架,扩展为针对不同类型的 具体函 ...
- Spring笔记01_下载_概述_监听器
目录 Spring笔记01 1.Spring介绍 1.1 Spring概述 1.2 Spring好处 1.3 Spring结构体系 1.4 在项目中的架构 1.5 程序的耦合和解耦 2. Spring ...
- c++ STL概述
2018-09-26 c++ STL(标准模板库)是一套通过c++模板类实现的标准类库,提供了通用的模板和函数,功能强大,大大的提高了c++的开发效率. c++ STL的核心主要包括三个组件:(1)容 ...
- SSL/TLS协议运行机制的概述_转
转自:SSL/TLS协议运行机制的概述 作者: 阮一峰 日期: 2014年2月 5日 互联网的通信安全,建立在SSL/TLS协议之上. 本文简要介绍SSL/TLS协议的运行机制.文章的重点是设计思想和 ...
随机推荐
- nginx虚拟主机测试
一.基于域名的nginx虚拟主机 基于域名的nginx虚拟主机的操作步骤: 1 .为虚拟主机提供域名和IP的映射(也可以使用DNS正向解析) echo "172.16.10.101 www. ...
- SPFA算法(SLF优化)2022.7.8更新
SPFA可能会被卡掉,能用dijkstra就别用SPFA,代码较长,但我已尽力做到解释,请耐心看下去,存储为邻接表存储. #include<bits/stdc++.h> #define i ...
- Spring源码学习笔记9——构造器注入及其循环依赖
Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...
- V8中的快慢数组(附源码、图文更易理解😃)
接上一篇掘金 V8 中的快慢属性,本篇分析V8 中的快慢数组,了解数组全填充还是带孔.快慢数组.快慢转化.动态扩缩容等等.其实很多语言底层都采用类似的处理方式,比如:Golang中切片的append操 ...
- 张高兴的 .NET IoT 入门指南:(八)基于 GPS 的 NTP 时间同步服务器
时间究竟是什么?这既可以是一个哲学问题,也可以是一个物理问题.古人对太阳进行观测,利用太阳的投影发明了日晷,定义了最初的时间.随着科技的发展,天文观测的精度也越来越准确,人们发现地球的自转并不是完全一 ...
- Java面试题(五)--Rabbits
1.什么是MyBatis? 1.Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动.创建连接.创建statement等 ...
- Docker 容器虚拟化
Docker 容器虚拟化 1.虚拟化网络 Network Namespace 是 Linux 内核提供的功能,是实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,它们有独自网络栈信息.不管是虚拟机 ...
- Go语言 WaitGroup 详解
你必须非常努力,才能看起来毫不费力! 微信搜索公众号[ 漫漫Coding路 ],一起From Zero To Hero ! 前言 在前面的文章中,我们使用过 WaitGroup 进行任务编排,Go语言 ...
- 万物皆可集成系列:低代码释放用友U8+深度价值(2)—数据拓展应用
在上一篇内容我们介绍了如何利用低代码开发套件实现低代码应用与U8+系统的对接集成,本次给大家带来的是如何将用友U8+系统中的数据进行价值扩展和实际应用. 我们以生产物料齐套分析为例来说明如何利用低代码 ...
- 硬件IIC驱动原理
1.IIC物理层 IIC通信属于同步半双工通信,IIC总线由两根信号线组成.一根是数据线SDA,一根是时钟线SCL,时钟线只能由主机发送给从机,数据线可以双向进行通信,总线上可挂载多个设备,挂载数量受 ...