目录
一、概念
STL的四种基本组件
容器vector
迭代器iterator
函数对象function object
算法algorithm
二、使用
容器vector的使用
泛型程序设计:
所谓泛型程序设计就是编写不依赖于具体数据类型的程序。C++中,模板就是泛型程序设计的主要工具。泛型程序设计的主要思想是将算法从特定的数据结构中抽象出来,使算法成为通用的、可以作用于各种不同的数据结构。这样就不必为每种容器都编写一套同样的算法,当容器类模板修改、扩充时也不必重写大量算法函数。这种以模板函数形式实现的通用算法和各种通用容器结合,提高软件复用性。
一、概念
- 标准模板库STL是一款用于支持C++泛型程序的模板库。由于C++标准库有多种不同的实现,因此STL也有不同的版本,但是他们为用户提供的接口都遵守相同的标准。
- STL提供了一些常用的数据结构和算法。他更远大的意义是,它定义了一套概念体系,为泛型程序设计提供了逻辑基础。
STL的四种基本组件
容器vector
容器是容纳、包含一组元素的对象。容器类库中包含7种基本容器:
- 向量vector
- 双端队列deque
- 列表list
- 集合set
- 多重集合multiset
- 映射map
- 多重映射multimap
STL中各容器头文件和所属概念
这7种容器可以分为两种基本类型:
- 顺序容器aequence container:将一组具有相同类型的元素以严格的线性形式组织起来,向量、双端队列和列表容器就属于这一种。
- 关联容器associative container:具有根据一组索引来快速提取元素的能力,集合和映射容器就属于这一种。
迭代器iterator
- 迭代器提供了顺序访问容器中每个元素的方法。
- 对于迭代器可以使用“++”运算符来获得指向下一个元素的迭代器,可以使用“*”运算符来访问一个迭代器所指向的元素
- 如果元素类型是类或者结构体,还可以使用“->”运算符直接访问该元素的一个成员,有些迭代器还支持通过“--”运算符获得指向上一个元素的迭代器。
- 指针也具有同样的特性,因此指针本身也是一种迭代器,迭代器是泛化的指针,但是迭代器不仅仅是指针。指针可以指向内存中的一个地址,通过这个地址就可以访问相应的内存单元;而迭代器更为抽象,它可以指向容器中的一个位置,我们不关心这个位置对应的真正物理地址,只需要通过迭代器访问这个位置的元素。
函数对象function object
- 函数对象是一个行为类似函数的对象,对它可以像调用函数一样调用。任何普通的函数和任何重载了“()”运算符的类的对象都可以作为函数对象使用,函数对象是泛化的函数。
算法algorithm
- STL包含70多个算法,这些算法覆盖了相当大的应用领域,其中包含查找算法、排序算法、消除算法、计数算法、比较算法、变换算法、置换算法和容器管理等。
- 这些算法的一个最重要的特性就是它们的统一性,并且可以广泛用于不同的对象和内置的数据类型。
- 标准模板库 = 容器(类模板)+算法(函数模板)
- 标准模板库的特点是不依赖于类型存在,是个模板。
- 容器:vector queue stack list set map multiset multimap... 容器是用来存储数据
- 算法:copy sort find find_if count count_if swap 算法用来操作容器内的元素
- 迭代器是容器和算法的粘合剂,相当于一个指针,将容器内的元素访问出来,通过算法去操作。容器是封装起来的类模板,其内部结构无人知晓,只能通过容器接口去使用容器。但是STL中的算法是通用的函数模板,而不是专门针对某个容器类型。算法要适用于多种容器,而每一种容器中存放的元素又可以是任何类型,这就必须使用更为抽象的指针——迭代器
- 仿函数:称为函数对象,将其放在算法内,可以将算法的功能得以扩展
STL组件之间的关系
二、使用
容器vector的使用
用vector中定义对象及初始化
#include<iostream>
#include<vector>
#include<iterator>
#include<algorithm>
int main()
{
//定义对象及初始化的使用:
vector<int> vv1;//定义容器vv1
vector<int> vv2(5);//定义大小为5的容器vv2,初始化为5个0,只写一个值默认为个数,值为0
vector<int> vv3(4, 9);//定义大小为5的容器vv3,初始化为4个9
int num[5] = { 1,2,3,4,5 };
vector<int> vv4(num, num + 5);//前闭后开区间将vv4初始化为1 2 3 4 5,将区间元素插入到vv4内
vector<int> vv5(vv4.begin(), vv4.end());//vv4的begin到vv4的end初始化vv5
vector<int> vv6(vv3);//通过vv3拷贝构造得到vv6,vv6复制的结果为4个9
vector<int> vv7;
vv7 = vv2;//vv7被初始化为vv2,也就是0
vv4[1] = 10;//将vv4[1]赋值为10,对象+中括号可以使用,说明重载了中括号★
//vv1[1] = 10;但这样错误,因为vv1[1]大小没赋值,但是可以用push_back压数据
vv1.push_back(1);//vv1=1;
//从标准输入读入向量容器的内容
for (int i = 0; i < 5; i++)
cin >> vv2[i];
}
其中,vv4是个成员函数,如下,使用“.”就自动出来其成员函数
对象+中括号可以使用,说明重载了中括号
容器基本功能函数
设S表示一种容器类型,s1和s2是S类型的实例,容器支持的基本功能如下:
- S s1 容器都有一个默认构造函数,用于构造一个没有任何元素的的空容器
- s1 op s2 这里的op可以是==,!=,<,<=,>,>=之一,他会对两个容器之间的元素按字典顺序比较
- s1.beigin():返回指向s1的第一个元素的地址
- cbegin:const类型
- crbegin:逆序
- s1.end():返回指向s1最后一个元素的下一个的地址
vector<int>::iterator iter;
for(iter=vv1.begin();iter!=vv1.end();iter++)
{
cout<<*iter<<" ";
}
- s1.clear():将容器s1的内容清空
- s1.empty():返回一个布尔值,表示s1容器是否为空
- s1.size():返回s1的元素个数
- s1.swap(s2):将s1容器和s2容器内容交换
(1)构造函数
顺序容器除了具有默认构造函数外,还可以使用给定的元素构造,也可以使用已有的迭代器的区间所表示的序列来构造:
S s(n,t);//构造一个由n个t元素构成的容器实例s
S s(n); //构造一个有n个元素的容器实例s,每个元素都是T()
S s(q1,q2); //使用将[q1,q2)区间内的数据作为s的元素构造s
(2)赋值函数
可以使用下面成员函数assign将指定的元素赋给顺序容器,顺序容器中原先的元素会被清除,赋值函数的3种形式是与构造函数一一对应的。
s.assign(n,t);//赋值后的容器由n个t元素构成
s.assign(n); //赋值后的容器有n个元素的容器实例s,每个元素都是T()
s.assign(q1,q2); //赋值后的容器的元素为[q1,q2)区间内的数据
assign举例:
//区间赋值 vv1.assign(5,6);//将vv1重新赋值为5个6 int num={1,2,3,4,5}; vv1.assign(num,num+5);//将vv1重新赋值为1 2 3 4 5 vv1.assign(vv4.begin(),vv4.begin()+3);//将vv1重新赋值为vv2的第一个和第4个之间的数值
copy也是复制:
copy(vv4.begin(),vv4.end(),ostream_iterator<int>(cout," ”); //vv4.begin到ee4.end区间内的值用输出迭代器输出 //第二个参数可以不写," "用空格隔开 //ostream_ierator<int>是个类模板
不仅可以操作容器,也可以对数组操作:
int num[]={1,2,3,4,5}; int numdes[5]; copy(num,num+5,numdes);//把num到num+5拷贝到numdes内
(3)元素的插入
向顺序容器中可以一次插入一个或多个指定元素,也可以将一个迭代器区间所表示的序列插入。插入时需要通过一个指向当前容器元素的迭代器来指示插入位置,如下:
s.insert(p1, t);//在s容器中p1所指向的位置插入一个新的元素t
//插入后的元素夹在原pl和p1-1所指向的元素之间,该函数会返回一个迭代器指向新插人的元素。
s.insert(pl, n, t);//在s容器中pl所指向的位置插人n个新的元素t,
//插入后的元素夹在原pl和p1-1所指向的元素之间,没有返回值。
s.insert(pl, q1, q2);//将[q1, q2)区间内的元素顺序插入到s容器中p1位置处,
//新元素夹在原p1和p1-1所指向的元素之间。
insert
vv.insert(vv4.begin(),5,10);//vv4.begin()位置插入5个10,原有数据后移 vv.insert(vv4.begin()+4,num,num+5); //vv4.begin()+4位置插入num-num+5这个区间的值,原有数据后移
(4)元素的删除
使用下面的函数可以从容器中删除指定元素或清空容器。删除指定元素时需要通过指向当前容器元素的迭代器来指示被删除元素的位置或区间。
sl. erase(pl);
//删除s1容器中p1所指向的元素,返回被删除的下一个元素的迭代器。
s1. erase(pl, p2);
//删除sl容器中[pl, p2)区间内的元素,
//返回最后一个被删除元素的下一个元素的迭代器(即在删除前p2所指向元素的迭代器)。
erase
vv.erase(vv.begin()+2,vv.begin()+4);//区间内删掉
(5)改变容器的大小
可以通过下面的函数改变容器的大小。
s1.resize(n);
//将容器的大小变为n,如果原有的元素个数大于n,则容器末尾多余的元素会被删除;
//如果原有的元素个数小于n,则在容器末尾会用T()填充。
(6)首位元素的直接范围
可以通过顺序容器的成员函数快捷地访问容器的首尾元素,如下:
- front:第一个元素
- back:最后一个元素
s. front();
//获得容器首元素的引用。
s. back();
//获得容器尾元素的引用。
(7)在容器尾部插入、删除元素
虽然说使用insert成员函数可以在任意位置插入元素,使用erase成员函数可以删除任意位置的元素,但由于在顺序容器尾部插入、删除元素的操作更为常用,因此STL提供了更加便捷的成员函数,如下:
s.push_back(t);//向容器尾部插入元素t。
s.pop_back();//将容器尾部的元素删除。
push_back:最后添加一个元素(参数为要插入的值)
vv.push_back(vv4.begin(),5,10);//vv4.begin()位置插入5个10,原有数据后移
(8)在容器头部插人、删除元素
列表list和双端队列deque两个容器支持高效地在容器头部插人新的元素或删除容器头部的元索,但是向量容器vector不支持。支持这一操作的概念构成了“前插顺序容器”(FrontInsertionSequence)这一概念,它是“顺序容器”的子概念。这些操作包括以下两种。
s.push_front(t);//向容 器头部插人元素t
s.pop_front();//删除容器头部的元素t.
(9)at:
用法:取下标,相当于'[]'
vv1.at(1) ; //输出vv1[1]的值,可作为左值返回就是引用
vv1.at(2)=50; //将vv1[1]修改为50
(10)emplace
把当前位置所指向的值改成value
vv4.emplace(vv4.begin(),100);
vv4.emplace(200);//最后插入一个200
迭代器的使用
//对容器进行访问
vector<int>::iterator iter; //定义迭代器iter
//iter++就是指向下一个元素
//其中每个容器封装了自己的迭代器,因此声明迭代器类型和指向类型一致
//每个容器封装自己的迭代器,vector的是vector<>,list是list<>
/*拿指针来理解迭代器
*int *p=num; 此时int与vector<int>类似;*与iterator类似
*iterator就说明iter是迭代器类型,vector<int> 是迭代器指向的基类型
*/
输入流迭代器
template<class T>istream_iterator<T>;
其中,T是使用该迭代器从输入流到输入数据的类型。类型T要满足两个条件:有默认构造函数;对该类型的数据可以使用“>>”从输入流输入。一个输入流迭代器的实例需要由下面的构造函数来构造:
istream_iterator(istream& in);
在该构造函数中,需要提供用来输入数据的输入流(例如cin)作为参数。
- 一个输入流迭代器实例支持“*”,“->”,“++”等几种运算符。
- 用“*”可以访问刚刚读取的元素
- 用“++”可以从输入流中读取下一个元素
- 若类型T是类类型或者结构类型,用“->”可以直接访问刚刚读取元素的成员。
输出流迭代器
输出流迭代器用来向一个输出流中连续输出某种类型的数据,它也是一个类模板:
template<class T>ostream_iterator<T>;
其中T表示向输出流中输出数据的类型,类型T需要具有一个功能:对该类型的数据可以使用"<<"向输出流输出。
一个输出流迭代器可以用下面两个构造函数来构造:
ostream_iterator(ostream& out); ostream_iterator(ostream& out,const char* delimiter);
构造函数的参数out表示将数据输出到的输出流。参数delimiter是可选的,表示两个输出数据之间的间隔符。输出流得带起也支持"*"运算符,但对于一个输出迭代器iter,*iter只能作为赋值运算符的左值。例如*iter=x,这相当于执行了out<<x或out<<x<<delimiter
输出流迭代器也支持“++”运算符,但该运算符实际上并不会使该迭代器的状态发生任何改变,支持“++”运算仅仅是为了让它和其他迭代器有统一的接口。
在两种迭代器的帮助下,输入流和输出流可以直接参与STL算法,这就是引入两种迭代器的意义。输入流迭代器和输出流迭代器被看做一种适配器。适配器是指用于已有对象提供新的接口的对象,适配器本身一般并不提供新的功能,只为了改变对象的接口而存在。
对输出方式总结:
1.迭代器iter遍历访问输出
vector<int>::iterator iter; for(iter=vv1.begin();iter!=vv1.end();iter++) cout<<*iter<<" "; cout<<endl;
2.输出迭代器ostream_iterator<>()
3.for_each()
void print(int n) { cout<<n<<endl; } for_each(vv4.begin(),vv4.end(),print); //print是自己写的,想求和也可以自定义sum函数等 class Print { public: void operator()(int n)//重载operator() { cout<<n<<" "; } }; for_each(vv4.begin(),vv4.end(),Print());//函数对象 template<class T> class Print { public: void operator()(T n)//重载operator() { cout<<n<<" "; } }; for_each(vv4.begin(),vv4.end(),Print<int>());//模板
用实例对容器和迭代器进行简单的使用
class Student
{
public:
Student(int num=0,string name=""):m_num(num),m_name(name){}
int GetNum()const//常成员函数后面加const表示不能修改本类数据成员的值
return m_num;
String GetName()const
return m_name;
private:
int m_num;
string m_name;
}
bool cmp(Student& a,Student& b)//vs.begin()是指针,需要"&"
{
return a.GetNum()<b.GetNum();
}
void main()
{
vector<Student>vs;
vs.push_back(Student(1001,"aaa"));
vs.push_back(Student(1002,"bbb"));
vector<Student>::iterator iter;
//访问输出
for(iter=vs.begin();iter!=vs.end();iter++)
{
//(*iter).m_num不对,因为是私有数据成员,因此添加接口GET
cout<<iter->GetNum()<<" "<<iter->GetName()<<endl;//此时可以(*iter).
//"."是对象使用,"->"是指针使用
}
//排序
sort(vs.begin(),vs.end(),cmp);
}