1.什么是STL
STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++ Standard Library)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。
STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但这种分离确实使得STL变得非常通用。例如,由于STL的sort()函数是完全通用的,你可以用它来操作几乎任何数据集合,包括链表,容器和数组;
STL另一个重要特性是它不是面向对象的。为了具有足够通用性,STL主要依赖于模板而不是封装,继承和虚函数(多态性)——OOP的三个要素。你在STL中找不到任何明显的类继承关系。这好像是一种倒退,但这正好是使得STL的组件具有广泛通用性的底层特征。另外,由于STL是基于模板,内联函数的使用使得生成的代码短小高效;
从逻辑层次来看,在STL中体现了泛型化程序设计的思想,引入了诸多新的名词,比如像需求(requirements),概念(concept),模型(model),容器(container),算法(algorithmn),迭代子(iterator)等。与OOP(object-oriented programming)中的多态(polymorphism)一样,泛型也是一种软件的复用技术;从实现层次看,整个STL是以一种类型参数化的方式实现的,这种方式基于一个在早先C++标准中没有出现的语言特性--模板(template)
2 STL内容介绍
STL中六大组件:
1)容器(Container):是一种数据结构,如list,vector,和deques ,以模板类的方法提供。为了访问容器中的数据,可以使用由容器类输出的迭代器;
2)迭代器(Iterator):提供了访问容器中对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器也可以是那些定义了operator*()以及其他类似于指针的操作符地方法的类对象;
3)算法(Algorithm):是用来操作容器中的数据的模板函数。例如,STL用sort()来对一个vector中的数据进行排序,用find()来搜索一个list中的对象,函数本身与他们操作的数据的结构和类型无关,因此他们可以在从简单数组到高度复杂容器的任何数据结构上使用;
4)仿函数:(Function object)
5)迭代适配器:(Adaptor)
6)空间配制器:(allocator
3.顺序容器
简述:顺序容器为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖元素的值,而是与元素加入容器时的位置相对应。所有顺序容器都提供了快速顺序访问元素的能力
顺序容器包括:
- vector(向量)
- list(列表)
- deque(队列)
下面介绍三种顺序容器:
1.vector
- vector是最简单也是最重要的一个容器。其头文件为<vector>.
- vector是数组的一种类表示,它有以下优点:自动管理内存、动态改变长度并随着元素的增减而增大或缩小。
- 在尾部添加元素是固定时间,在头部或中间添加或删除元素是线性时间。
- vector是可反转容器
定义与初始化:
如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行,具体值为何,取决于存储在vector 中元素的数据类型;如果为int型数据,那么标准库将用 0 值创建元素初始化式;如果 vector 保存的是含有构造函数的类类型(如 string)的元素,标准库将用该类型的默认构造函数创建元素初始化式;元素类型可能是没有定义任何构造函数的类类型。这种情况下,标准库仍产生一个带初始值的对象,这个对象的每个成员进行了值初始化。
下图为实例化操作:
vector<int> vec1; //默认初始化,vec1为空
vector<int> vec2(vec1); //使用vec1初始化vec2
vector<int> vec3(vec1.begin(),vec1.end());//使用vec1初始化vec2
vector<int> vec4(10); //10个值为0的元素
vector<int> vec5(10,4); //10个值为4的元素
vector<string> vec6(10,"null"); //10个值为null的元素
vector<string> vec7(10,"hello"); //10个值为hello的元素
常见的操作方法:
vec1.push_back(100); //添加元素
int size = vec1.size(); //元素个数
bool isEmpty = vec1.empty(); //判断是否为空
cout<<vec1[0]<<endl; //取得第一个元素
vec1.insert(vec1.end(),5,3); //从vec1.back位置插入5个值为3的元素
vec1.pop_back(); //删除末尾元素
vec1.erase(vec1.begin(),vec1.end());//删除之间的元素,其他元素前移
cout<<(vec1==vec2)?true:false; //判断是否相等==、!=、>=、<=...
vector<int>::iterator iter = vec1.begin(); //获取迭代器首地址
vector<int>::const_iterator c_iter = vec1.begin(); //获取const类型迭代器
vec1.clear(); //清空元素
2.list
- list表示双向链表。头文件<list>
- list为可反转容器。
- list不支持数组表示法和随机访问。
- 与矢量迭代器不同,从容器中插入或删除元素之后,链表迭代器指向的元素不变。这与链表的特性有关,删除链表中的元素并不改变其它元素位置,只是修改链接信息。(代码证明)
- 不同于vector,list不强调随机访问与快速访问,list强调的是元素的快速插入与删除
- 再次提醒:序列容器都是线性排序,因此list首尾不会相连。
list成员函数:
void merge(list<T,Alloc>& x)//将链表x与调用链表合并,俩个链表必须已排序。合并后的经过排序的链表保存在调用链表中,x为空。线性时间
void remove(const T & val)//从链表中删除val的所有实例。线性时间。
void sort()//使用<运算符对链表进行排序,复杂度NlogN
void splice(iterator pos,list<T,Alloc>x)//将链表x的类容插入到pos前面,x将为空。固定时间。
void unique()//将连续相同的元素压缩为单个元素。线性时间。
代码实现:
#include<iostream>
#include<list>
#include<iterator>
#include<algorithm>
using namespace std;
void outint(int n){cout << n << " ";}
void Show(list<int> dice,int flag){
if(flag) cout<<"dice = ";
else cout<< " two = ";
for_each(dice.begin(),dice.end(),outint);//输出容器的元素。
cout << endl;
}//此函数用来输出容器元素
int main()
{
list<int> dice(5,2);//一种赋初值方法。5个2
Show(dice,1);
int a[] = {1,5,4,3};
dice.insert(dice.begin(),a,a+4);//insert函数用法
Show(dice,1);
list<int> two(dice);//另一种赋初值方法,其值与dice相等
Show(two,0);
dice.splice(dice.begin(),two);//splice函数用法
Show(dice,1);
Show(two,0); //two清空
two = dice;
dice.unique();//unique压缩连续相同的元素
Show(dice,1);
dice.sort();//sort函数用法
two.sort();
Show(dice,1);
Show(two,0);
dice.merge(two);//merge函数用法,将two合并到dice中,two将为空。
Show(dice,1);
Show(two,0);
dice.remove(2);//移除所有2
Show(dice,1);
return 0;
}
insert()与splice()之间的不同主要在与:insert()将原始区间的副本插入到目标地址,而splice()则将原始区间移到目标地址。splice()执行后,迭代器仍有效。也就是说原本指向two中一个元素的迭代器,在使用过splice后仍然指向它。
remove()函数还有更加方便的拓展,将在以后讲到。
常用的操作方法
lst1.assign(lst2.begin(),lst2.end()); //分配值
lst1.push_back(10); //添加值
lst1.pop_back(); //删除末尾值
lst1.begin(); //返回首值的迭代器
lst1.end(); //返回尾值的迭代器
lst1.clear(); //清空值
bool isEmpty1 = lst1.empty(); //判断为空
lst1.erase(lst1.begin(),lst1.end()); //删除元素
lst1.front(); //返回第一个元素的引用
lst1.back(); //返回最后一个元素的引用
lst1.insert(lst1.begin(),3,2); //从指定位置插入3个值为2的元素
lst1.rbegin(); //返回第一个元素的前向指针
lst1.remove(2); //相同的元素全部删除
lst1.reverse(); //反转
lst1.size(); //含有元素个数
lst1.sort(); //排序
lst1.unique(); //删除相邻重复元素
3.deque
- 头文件<deque>
- 在STL中deque类似vector,并且支持随机访问。区别在于:从deque起始位置插入删除元素时间是固定的。
- 为了实现在deque俩段执行插入和删除操作的时间为固定这一目的,deque对象设计比vector设计更为复杂一些。因此,在序列中部执行插入删除操作时,vector更快一些。
如何确定使用那种顺序容器:
通常,使用vector是最好的选择,除非你有很好的理由选择其他容器:
以下是一些选择容器的基本原则:
- 除非你有很好的理由选择其他容器,否则应使用vector
- 如果你的程序有很多很小的元素,且空间的额外开销很重要,则不要使用list或forward_list
- 如果程序要求随机访问元素,应使用vector或deque
- 如果程序要求在容器中间插入或删除元素,应使用list或forward_list
- 如果程序要求在头尾位置插入或删除元素,但不会在中间位置进行插入删除操作,则使用deque
- 如果程序既需要随机访问元素,又需要在容器中间插入元素,答案是取决于在list或forward_list中访问元素与vector或deque中插入/删除元素的相对性能
如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则
- 首先,确定是否真的需要在容器中间位置添加元素,当处理输入数据时,通常可以很容易地向vector追加数据,然后在调用标准库的sort函数来重排容器中的元素,从而避免在中间位置添加元素
- 如果必须在中间位置插入元素,考虑在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中
3.关联容器
简述:关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。元素是有序的集合,默认在插入的时候按升序排列。
关联容器包括:
- map(集合)
- set(映射)
- multimap(多重集合)
- multiset(多重映射)
下面介绍这几种关联容器:
1.map
头文件#include <map>
简述:c++中map中元素是一些关键字—值(Key-value):关键字起到索引的作用,值则表示与索引相关联的数据。
定义与初始化:
map<int,string> map1; //空map
在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。所用的比较函数必须在键类型上定义严格弱排序(strict weak ordering):可理解为键类型数据上的“小于”关系,虽然实际上可以选择将比较函数设计得更复杂。
对于键类型,唯一的约束就是必须支持 < 操作符,至于是否支持其他的关系或相等运算,则不作要求。
下图为初始化实例:
map<k, v>m
//创建一个名为m的空map对象,其键和值的类型分别为k和v
map<k, v>
m(m2)
//创建m2的副本m,m与m2必须有相同的键类型和值类型
map<k, v>
m(b, e)
//创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本。元素的类型必须能转换为pair<const k, v>
操作方法:
添加元素有两种方法:
1、先用下标操作符获取元素,然后给获取的元素赋值
2、使用insert成员函数实现
下标操作添加元素:如果该键已在容器中,则 map 的下标运算与 vector 的下标运算行为相同:返回该键所关联的值。只有在所查找的键不存在时,map 容器才为该键创建一个新的元素,并将它插入到此 map 对象中。此时,所关联的值采用值初始化:类类型的元素用默认构造函数初始化,而内置类型的元素初始化为 0。
下图为操作方法:
map1[3] = "Saniya"; //添加元素
map1.insert(map<int,string>::value_type(2,"Diyabi"));//插入元素
map1.insert(pair<int,string>(1,"Siqinsini"));
map1.insert(make_pair<int,string>(4,"V5"));
string str = map1[3]; //根据key取得value,key不能修改
map<int,string>::iterator iter_map = map1.begin();//取得迭代器首地址
int key = iter_map->first; //取得key
string value = iter_map->second; //取得value
map1.erase(iter_map); //删除迭代器数据
map1.erase(3); //根据key删除value
map1.size(); //元素个数
map1.empty(); //判断空
map1.clear(); //清空所有元素
下面介绍insert成员函数的操作:
m.insert(e)
//e是一个用在m上的value_type 类型的值。如果键(e.first不在m中,则插入一个值为e.second 的新元素;如果该键在m中已存在,则保持m不变。该函数返回一个pair类型对象,包含指向键为e.first的元素的map迭代器,以及一个 bool 类型的对象,表示是否插入了该元素
m.insert
(beg,end)
//beg和end是标记元素范围的迭代器,其中的元素必须为m.value_type 类型的键-值对。对于该范围内的所有元素,如果它的键在 m 中不存在,则将该键及其关联的值插入到 m。返回 void 类型
m.insert
(iter,e)
e是一个用在m上的 value_type 类型的值。如果键(e.first)不在m中,则创建新元素,并以迭代器iter为起点搜索新元素存储的位置。返回一个迭代器,指向m中具有给定键的元素
2.set
头文件:#include<set>
简述:set的含义是集合,它是一个有序的容器,里面的元素都是排序好的,支持插入,删除,查找等操作,就像一个集合一样。所有的操作的都是严格在logn时间之内完成,效率非常高。
1.set容器的定义和使用
set 容器的每个键都只能对应一个元素。以一段范围的元素初始化set对象,或在set对象中插入一组元素时,对于每个键,事实上都只添加了一个元素。
vector<int> ivec;
for (vector<int>::size_type i = 0; i != 10; ++i) {
ivec.push_back(i);
ivec.push_back(i);
}
set<int> iset(ivec.begin(), ivec.end());
cout << ivec.size() << endl; //20个
cout << iset.size() << endl; // 10个
2.在set中添加元素
set<string> set1;
set1.insert("the"); //第一种方法:直接添加
set<int> iset2;
iset2.insert(ivec.begin(), ivec.end());//第二中方法:通过指针迭代器
3.从set中获取元素
set 容器不提供下标操作符。为了通过键从 set 中获取元素,可使用 find运算。
如果只需简单地判断某个元素是否存在,同样可以使用 count 运算,返回 set 中该键对应的元素个数。
当然,对于 set 容器,count 的返回值只能是1(该元素存在)或 0(该元素不存在)。
set<int> iset;
for(int i = 0; i<10; i++)iset.insert(i);
iset.find(1) // 返回指向元素内容为1的指针
iset.find(11) // 返回指针iset.end()
iset.count(1) // 存在,返回1
iset.count(11) // 不存在,返回0
4.迭代器的关联容器操作
*it就是当前迭代器指向的值
set默认是从小到大排列值,定义时set<int,greater<int>> iset;则此时默认是从大到小排列值。
m.lower_bound(k) //返回一个迭代器,指向键不小于 k 的第一个元素
m.upper_bound(k) //返回一个迭代器,指向键大于 k 的第一个元素
m.equal_range(k) //返回一个迭代器的 pair 对象。它的 first 成员等价于 m.lower_bound(k)。而 second 成员则等价于 m.upper_bound(k)
最后总结下:
1.顺序容器和关联容器的区别:
- 顺序容器只有实值val。
- 关联容器的一个元素包含两个部分:键值对(key-value) 即<k值(键值)|实值>。
- 顺序容器不涉及排序,关联容器内部自动排序。
- 本质区别:顺序容器通过元素在容器中的位置顺序存储和访问元素,而关联容器则是通过键(key)存储和读取元素的。
2.set和multiset的区别:
set插入的元素不能相同,但是multiset可以相同。Set默认自动排序。使用方法类似list。
3.关联容器中的 有序容器和无序容器的区别:
①有序容器(底层结构是:红黑树)
(1)map;//key不允许重复
(2)multimap;//key允许重复
(3)set;//Key=val;key不允许重复
(4)multiset//Key=val;key允许重复
是stl里的标准库。
②无序容器(底层结构是:散列表)
(1)unorder_map;
(2)unorder_multimap;
(3)unorder_set;
(4)unorder_multiset
是boost库中的容器,目前boost库是准标准库,使用时需要添加库
4.顺序容器适配器
简介:适配器是使一种不同的行为类似于另一事物的行为的一种机制。容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。适配器是容器的接口,它本身不能直接保存元素,它保存元素的机制是调用另一种顺序容器去实现,即可以把适配器看作“它保存一个容器,这个容器再保存所有元素”。
STL 中包含三种适配器:
- 栈stack
- 队列queue
- 优先级队列priority_queue
下面介绍三种适配器:
1.queue
- 头文件<queue>
- queue不允许随机访问队列元素,不允许遍历队列,可以进行队列基本操作
- 可以将元素添加到队尾,从队首删除元素,查看队尾和队首的值,检查元素数目和测试队列是否为空
queue的操作:
bool empty()const //如果队列为空,则返回true,否则返回false
size_type size()const //返回队列中元素的数目
T& front() //返回指向队首元素的引用
T& back() //返回指向队尾元素的引用
void push(const T& x) //在队尾插入x
void pop() //删除队首元素
pop()//是一个删除数据的方法,不是检索数据的方法。如果要使用队列中的值,首先要使用front()来检索这个值,然后用pop()将他从队列中删除。
2.stack
- 头文件<stack>
- stack是一个适配器,它给底层类(默认vector)提供典型栈接口。
- stack不允许随机访问栈元素,不允许遍历栈,把使用限制在定义栈的基本操作上
- 可以将值压入栈顶,从栈顶弹出元素,查看栈顶的值,检查元素数目,测试栈是否为空
stack的操作:
bool empty()const //如果栈为空,返回true,否则返回false
size_type size()const //返回栈中元素数目
T& top() //返回指向栈顶元素的引用
void push(const T& x) //在栈顶插入x
void pop() //删除栈顶元素
与queue类似,如果要使用栈中的值,必须首先使用top()来检索这个值,然后使用pop()将它从栈顶删除。
注:容器类自动申请和释放内存,因此无需new和delete操作。
3.优先级队列priority_queue 该适配器应用的少就不介绍了