目录
1、STL简介
STL提供了六大组件,分别是:容器、算法、迭代器、仿函数、适配器、空间配置器。
容器:储存数据的各种结构,如string、vector、deque、stack、list、set、map,从实现角度来看,STL容器是一种class template。可以分为:序列式容器(由元素进入的时间和地点决定的)和关联式容器(元素在容器中的位置有定义的规则决定)
算法:以有限的步骤,解决逻辑或数学上的问题。如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte。可以分为质变算法(更改区间内的元素内容)和非质变算法(不更改区间内的元素内容)。
迭代器:容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将指针相关操作予以重载的class template。所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template
适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.
STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。优势:高可重用性,高性能、高移植性。
2、常用容器
2.1、string容器
2.1.1、string容器基本概念
1、C++标准库定义了一种string类,定义在头文件<string>。
2、Char*是一个指针,String是一个类;string封装了char*,管理这个字符串,是一个char*型的容器。
3、string封装了很多实用的成员方法:查找find,拷贝copy,删除delete 替换replace,插入insert。
4、不用考虑内存释放和越界:string管理char*所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。
2.1.2、string常用函数
见(char*、string、CString的应用及相互转换,string章节)
2.2、vector容器
2.2.1、vector容器基本概念
1、vector是动态空间(部机制会自动扩充空间以容纳新元素),随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。
2、vector提供的是随机访问迭代器。
3、vector所采用的数据结构非常简单,线性连续空间,它以两个迭代器_Myfirst和_Mylast分别指向配置得来的连续空间中目前已被使用的范围,并以跌代器_Myend指向整块连续内存空间的尾端。
4、对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器(Random Access Iterators)就都失效了;
5、vector容器是单向开口的连续内存空间,尾删尾插。
2.2.2、vector常用构造函数
//T指容器装的数据类型
vector<T> v(4,T);//将4个T类型的数据类型对象拷贝给v;
vector<T> v(iterator.begin(),iterator.end());//将迭代器begin()至end()区间的数据拷贝给v;
vector<T> v1;
vector<T> v(v1);//将容器v1中的数据拷贝给v;
注:deque、list容器构造函数类似,只是关键字变为deque
2.2.3、vector常用赋值函数
vector<T> v
vector<T> v1
vector<test_class>::iterator it_start = ++v1.begin();
vector<test_class>::iterator it_endl = v1.begin()+3;
v.assign(it_start,it_start);//此迭代器不能是指向v的数据段。
//v.assign(++v.begin(),v.begin()+3);//因为迭代器失效,报错
v.assign(4,T);//将4个T类型的对象放入容器中。
v=v1;//容器直接进行赋值操作,重载了‘=’
v.swap(v1);//将v中的数据与v1进行交换
注:deque、list容器构造函数类似,只是关键字变为deque
2.2.4、vector大小操作
vector<T> v;
T object;
int num=3;
v.size();//返回容器中元素中的个数。
v.empty();//判断容器中的元素是否为空。
v.capacity();//容器的容量,及vector开辟的内存空间大小,如果容量变了,注意迭代器可能失效
v.resize(num);//改变容器中的元素个数为前num个,若容器变长(改变的元素个数大于原来的元素个数),默认值填充新位置。可能会修改容器的容量,看情况分析。
v.resize(num,object);//改变容器中的元素个数为前num个,若容器变长(改变的元素个数大于原来的元素个数),object填充在新位置。可能会修改容器的容量,看情况分析。
v.reserve(num);//容器预留len个元素长度,预留位置不初始化,元素不可访问。重新设置容器的容量,如果容器原始容量大于num则不会被改变。如果小于num,容器容量大小变为num。注:提前预留可以防止迭代器失效
注:deque、list容器构造函数类似,只是关键字变为deque,同时没有容量(capacity)、空间预留概念(reserve)
2.2.5、vector存储操作
vector<T> v;
T object;
int idx=3;
v.at(idx);//返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。可以用try捕捉到
v[idx];//返回索引idx所指的数据,越界时,运行直接报错。无法被try捕捉
v.front();//返回容器中第一个数据元素
v.back();//返回容器中最后一个数据元素
2.2.6、vector插入与删除
vector<T> v;
vector<T> v1;
vector<T>::iterator it_start=v.begin();
vector<T>::iterator it_end=v.begin();
int num=4;
T object;
v.insert(it_start+3,num,object);//在迭代器it_start+3指向的位置,插入num个object对象。
v.insert(it_start,object);//在迭代器it_start指向的位置,插入object对象。
v.insert(it_start,v1.begin()+2,v1.begin()+4);//在迭代器it_start指向的位置,添加v1.begin()+2到v1.begin()+4俩个迭代器指向位置的区间的数据。
v.pop_back();//删除最后一个元素。
v.push_back(object);//在尾部插入一个元素。
v.erase(it_start+2,it_end-2);//删除迭代器it_start+2到it_end-2之间的元素。
v.erase(it_start);//删除迭代器it_start指向的元素
v.clear();//清空容器中所有的元素
注:删除元素,不会改变容器的容量。可以通过定义新容器,运用swap函数对容器进行交换,改变容器大小。
2.3、deque容器
2.3.1、deque容器基本概念
1、一种双向开口的连续线性空间,可以在头尾两端分别做元素的插入和删除操作
2、允许使用常数项对头端进行元素的插入和删除操作。
3、没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。
4、维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器(Random Access Iterator)架构。
5、每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。
2.3.2、deque构造函数
与2.2.2、vector构造函数一样,关键字变为deque。
2.3.3、deque赋值操作
与2.2.3、vector赋值操作一样,关键字变为deque。
2.3.4、deque大小操作
与2.2.4、vector赋值操作相比,关键字变为deque。另外:
1、没有容量概念(capacity函数),原因见概念4和5
2、没有空间预留函数(reserve),原因见概念4和5
2.3.5、deque存储操作
与2.2.5、vector存储操作一样,关键字变为deque。
2.3.6、deque插入删除操作
与2.2.6、vector存储操作相比,关键字变为deque。另外增加函数功能:头部插入与头部删除。
deque<T> d;
T object;
d.push_front(object);//头部插入一个元素
d.pop_front();//删除头部元素
2.4、list容器
2.4.1、list容器基本概念
1、一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。
2、每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
3、采用动态存储分配,不会造成内存浪费和溢出。每次插入或者删除一个元素,就是配置或者释放一个元素的空间。
4、list容器是一个循环的双向链表,执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素。
5、链表灵活,但是空间和时间额外耗费较大。
6、list迭代器(Bidirectional Iterators)必须有能力指向list的节点,递增时指向下一个节点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取的是节点的成员(获取的迭代器指针没有+2等操作,只能自加(++),自减(--)访问数据成员)。
7、插入操作和删除操作都不会造成原有list迭代器的失效。
2.4.2、list构造函数
与2.2.2、vector构造函数一样,关键字变为list。
2.4.3、list赋值操作
与2.2.3、vector赋值操作一样,关键字变为list。可以通过自身指针对自己重新赋值,原因见概念7
vector<T> v
vector<test_class>::iterator it_start = ++v.begin();
vector<test_class>::iterator it_endl = --v.begin();
v.assign(++v.begin(),v.begin()+3);
2.4.4、list大小操作
与2.2.4、vector赋值操作相比,关键字变为list。另外:
1、没有容量概念(capacity函数),原因见概念4和6
2、没有空间预留函数(reserve),原因见概念4和6
2.4.5、list存储操作
与2.3.6、deque存储操作相比,没有at与[]重载。
2.4.6、list插入删除操作
与2.3.6、deque存储操作相比,关键字变为list。另外增加函数功能:删除容器中某个相同的元素(如果是类,需要对‘=='进行重载)。
list<T> l;
T object;//对T中‘==’进行重载。bool T::operator==(const T tc);
l.remove(object)
2.5、stack容器
2.5.1、stack容器基本概念
1、一种先进后出的数据结构。
2、stack容器允许新增元素(在栈顶),移除元素(在栈顶),取得栈顶元素。
3、stack不允许有遍历行为。
4、不提供迭代器,所有元素的进出都必须符合”先进后出”的条件,只有stack顶端的元素,才有机会被外界取用
2.5.2、stack构造函数
stack<T> s;//默认构造函数
stack<T> s1(s);//拷贝构造
2.5.3、stack赋值操作
stack<T> s;
stack<T> s1;
s1=s;//重载了‘=’;
s1.swap(s);//s1与s容器交换
2.5.4、stack大小操作
stack<T> s;
s.empty();//判断堆栈是否为空
s.size();//返回堆栈的大小
2.5.5、stack插入和删除
stack<T> s;
T object;
T object1;
s.push(object);
object1=s.top();//返回栈顶元素
s.pop();//删除栈顶元素
2.6、queue容器
2.6.1、queue容器基本概念
1、queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器只允许从一端新增元素,从另一端移除元素。
2、所有元素的进出都必须符合”先进先出”的条件,只有顶端元素,才有机会被外界取用。
3、不提供遍历功能,也不提供迭代器。
2.6.2、queue构造函数
与2.4.2、stack构造函数一样,使用queue关键字
2.6.3、queue赋值操作
与2.4.3、stack赋值操作一样
2.6.4、queue大小操作
与2.4.4、stack大小操作一样
2.6.5、queue存取、插入和删除操作
queue<T> q;
T object;
q.push(object);//往队尾添加元素
q.pop();//从队头移除第一个元素,最先压入的数据
q.back();//返回最后一个元素,队尾数据,最后压入
q.front();//返回第一个元素,队头数据,最先压入的数据
2.7、set/multiset容器
2.7.1、set/multiset容器基本概念
1、所有元素都会根据元素的键值自动被排序,元素即是键值又是实值。Set不允许两个元素有相同的键值(multiset允许键值重复,自定义类中的键值就是指被比较的数)
2、不能通过迭代器(const_iterator)改变元素的键值,关系到set元素的排序规则。
3、底层实现是红黑树,红黑树为平衡二叉树的一种。
2.7.2、set构造函数
//定义仿函数,才能在容器中放入自定义类
struct FuncT
{
bool operator()(const T& left,const T& right)
{
......//定义大小比较规则
}
}
bool operator < (const T& left, const T& right)
{
if (left.Year < right.Year)
{
return true;
}
return false;
}//如果T为自定义类,一定要对'<'进行重载,且不能将其定义为成员函数。
//普通数据类型
set<T> st;
mulitset<T> mst;
set<T> st1(st);
//自定义数据类型
set<T,FuncT> st;
mulitset<T,FuncT> mst;
set<T,FuncT> st1(st);
//对于自定义类,也可以重载'<';T为自定义类
set<T> st;
mulitset<T> mst;
set<T> st1(st);
2.7.3、set赋值操作
与2.4.3、stack赋值操作一样。
2.7.4、set大小操作
与2.4.4、stack大小操作一样
2.7.5、set插入和删除操作
set<T,FuncT> st;
set<T,FuncT>::iterator it_start=++st.begin();
set<T,FuncT>::iterator it_end=--st.end();
T object;
st.insert(object);//在容器中插入元素。
st.clear();//清除所有元素
st.erase(it_start);//删除it_start迭代器所指的元素,返回下一个元素的迭代器。
st.erase(it_start, it_end);//删除区间[it_start, it_end)的所有元素 ,返回下一个元素的迭代器。
st.erase(object);//删除容器中值为object的元素(键值相等的数据)。
pair<set<T,FuncT>::iterator,bool> ret = s.insert(object);//通过ret.second判定插入是否成功。
2.7.6、set查找操作
set<T,FuncT> st;
set<T,FuncT>::iterator it;
T object;
st.find(object);//在容器中寻找与object键值相同的元素,若存在,返回该元素迭代器,不存在返回st.end();
st.count(object);//查看与object键值相同的元素个数
st.lower_bound(object);//返回第一个与object键值相同元素的迭代器。
st.upper_bound(object);//返回第一个与object键值相同元素后面一个元素的迭代器。
pair<set<T, FuncT>::iterator, set<T, FuncT>::iterator > mypair;
mypair=st.equal_range(object);//将上面俩个迭代器都返回到mypair中,通过mypair.firs,mypair.second获取俩个迭代器。
2.8、map/multimap容器
2.8.1、map/multimap容器基本概念
1、所有元素都会根据元素的键值自动排序。Map所有的元素都是pair,同时拥有实值和键值,pair的第一元素被视为键值,第二元素被视为实值,map不允许两个元素有相同的键值(multimap允许相同的存在)。
2、不能通过map的迭代器改变map中元素的键值,但可以通过迭代器改变map元素中的实值。
3、以红黑树为底层实现机制。
2.8.2、map构造函数
bool operator < (const T& left, const T& right)
{
if (left.Year < right.Year)
{
return true;
}
return false;
}//如果T为自定义类,一定要对'<'进行重载,且不能将其定义为成员函数。
map<T, T1> v;
map<T, T1> v1(v);
//或者运用仿函数
struct FuncT
{
bool operator()(const T& left,const T& right)
{
......//定义大小比较规则
}
}
map<T, T1,FuncT> v;
map<T, T1,FuncT> v1(v);
2.8.3、map赋值操作
与2.4.3、stack赋值操作一样。
2.8.4、map大小操作
与2.4.4、stack大小操作一样
2.8.5、map删除操作
与2.7.5、set删除操作一样
2.8.6、map插入操作
T object;
T1 object1;
map<T,T1> m;
m.insert(pair<T, T1>(object, object1));//通过pair的方式插入对象
m.insert(make_pair(object, object1));//通过make_pair的方式插入对象
m.insert(map<T, T1>::value_type(object, object1));//通过value_type的方式插入对象
m[object] = object1;//通过数组的方式插入值
2.8.7、map查找操作
与2.7.6、set查找操作一样
2.9、STL使用分析及注意事项
2.9.1、常用容器情况比较
| vector | deque | list | set | multiset | map | multimap |
内存结构 | 单端数组(内存连续) | 双端数组(内存连续) | 双向链表 | 二叉树 | 二叉树 | 二叉树 | 二叉树 |
可随机存取 | 是 | 是 | 否 | 否 | 否 | 对key而言:不是 | 否 |
元素搜寻速度 | 慢 | 慢 | 非常慢 | 快 | 快 | 对key而言:快 | 对key而言:快 |
元素安插移除 | 尾端 | 头尾两端 | 任何位置 | - | - | - | - |
2.9.2、STL容器元素深/浅拷贝问题
STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素再另行拷贝一份放入到容器中,而不是将原数据元素直接放进容器中,也就是说我们提供的元素必须能够被拷贝(自定义类成员有有指针的情况,需要特别注意,一定要重写拷贝构造函数(vector示例))。
3、常用算法
3.1、函数对象
重载函数调用操作符的类,得类对象可以像函数那样调用,行为类似函数的对象。将这对象常称为函数对象(function object)也叫仿函数(functor),例如:重载“()”操作符,使得类对象可以像函数那样调用。
函数对象定义及分类:
1.函数对象(仿函数)是一个类,不是一个函数。
2.重载函数调用操作符的类,使得它可以像函数一样调用。
3、假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为“一元仿函数”(unary functor);相反,如果重载的operator()要求获取两个参数,就将这个类称为“二元仿函数”(binary functor)。
特性(见 Common_Algorithms中示例):
1、函数对象超出了普通函数的概念,可以保存函数的调用状态。()
2、函数对象可以做参数和返回值
定义方式(2.7.2与2.8.2定义有):
struct 函数对象名称
{
类型 operator()(参数1,参数2)
{
具体实现
}
}
3.2 内建函数对象
STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。使用内建函数对象,需要引入头文件 #include<functional>。(设置重载函数,不能是类的成员方法)
1、算数类函数对象,除了negate是一元运算,其他都是二元运算。
//返回类型为T,T如果为自定也类,需要对相应符号进行重载
T object;
T object1;
plus<T> FuncT;//加法仿函数
minus<T> FuncT;//减法仿函数
multiplies<T> FuncT;//乘法仿函数
divides<T> FuncT;//除法仿函数
modulus<T> FuncT;//取模仿函数
negate<T> FuncT;//取反仿函数
//运用
FuncT(object,object1);
2、关系运算类函数对象,每一种都是二元运算。
//返回类型为T,T如果为自定也类,需要对相应符号进行重载
T object;
T object1;
equal_to<T> FunT//等于
not_equal_to<T> FunT//不等于
greater<T> FunT//大于
greater_equal<T> FunT//大于等于
less<T> FunT//小于
less_equal<T> FunT//小于等于
//运用
FunT(object,object1)
3、逻辑运算类运算函数,not为一元运算,其余为二元运算。
//返回类型为bool,T如果为自定也类,需要对相应符号进行重载
T object;
T object1;
logical_and<T> FuncT//逻辑与
logical_or<T> FuncT//逻辑或
//运用:
FuncT(object,object1)
logical_not<T> FuncT//逻辑非
3.3、适配器
函数对象适配器主要运用于STL算法中,为容器中的元素指定输出、比较、排序规则
1、将函数对象定义为一个适配器
//仿函数适配器,无参类型
struct FuncT1
{
void operator()(T object)
{
......;//具体方法
}
};
//仿函数适配器,多参类型
struct FuncT2:public binary_function<T, T1, T2>// binary_fucntion<参数类型,参数类型,返回值类型>
{
T2 operator()(T t1,T1 a)const
{
......;//具体方法
}
};
vector<T> v;
T2 t2;
for_each(v.begin(), v.end(), FuncT1());
for_each(v.begin(), v.end(), bind2nd(FuncT2(),t2));
2、将普通函数定义为一个适配器
//将普通函数转换成函数对象适配器,无参类型
T2 FuncT1(T t1)
{
.....;//具体实现方法
}
//将普通函数转换成函数对象适配器,有参类型
T2 FuncT2(T t1,T1 a)
{
.....;//具体实现方法
}
vector<T> v;
T1 a;
for_each(v.begin(), v.end(), ptr_fun(FuncT1));
for_each(v.begin(), v.end(), bind2nd(ptr_fun(FuncT2), a));
3、将成员函数定义为一个适配器
//FuncT函数不能有参
vector<T*> v;//'*'表示储存对象指针
//如果存储的是对象指针,需要使用mem_fun
for_each(v.begin(), v.end(), mem_fun(&T::FuncT));
//如果存储的是对象,需要使用mem_fun_ref
vector<T> v;
for_each(v.begin(), v.end(), mem_fun_ref(&T::print2));
3.4、常用算法
算法主要是由头文件<algorithm> <functional> <numeric>组成。
<algorithm>是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等。
<numeric>体积很小,只包括在几个序列容器上进行的简单运算的模板函数.
<functional> 定义了一些模板类,用以声明函数对象。
3.4.1、遍历算法
//参数1:开始迭代器;参数2:结束迭代器;参数3:函数回调或者函数对象(返回值为void类型)
for_each(iterator beg, iterator end, FuncT());
3.4.2、将一个容器中的值搬运到另一个容器中
将vSource中的元素搬运到vTarget,参数4:回调函数或者函数对象,返回值类型为T
vector<T>::iterator it = transform(vSource.begin(), vSource.end(), vTarget.begin(), FuncT());
//将容器1和容器2中的元素相按照FuncT()函数中的处理方法后,放入到vTarget容器中去。
transform(vSource1.begin(), vSource1.end(), vSource2.begin(),vTarget.begin(), FuncT());
3.4.3、查找元素
//参数3:如果为自定义类,类中需要重载‘==’,定义相等规则,返回值类型为bool
/*例:bool operator==(const T & obj2) const
{
if(this->getItemId().compare(obj2.getItemId()) == 0)
return true;
else
return false;
}*/
T object;
vector<T>::iterator it;
//返回相等元素的第一个位置的迭代器
it=find(iterator beg, iterator end,object)//返回容器中与object相等的元素迭代器。
//查找相邻重复元素,返回相邻元素的第一个位置的迭代器
adjacent_find(iterator beg, iterator end, object);
//二分查找法,查找返回true 否则false
binary_search(iterator beg, iterator end, object);
//条件查找,bool 查找返回true 否则false
find_if(iterator beg, iterator end, _callback);//此方法思路待验证
//统计元素出现次数
count(iterator beg, iterator end, object);
//统计元素出现次数
count_if(iterator beg, iterator end, _callback);//此方法思路待验证
3.4.4、排序算法
//merge算法 容器元素合并,并存储到另一容器中
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)//待验证
//容器元素排序
sort(iterator beg, iterator end, FuncT)
//对指定范围内的元素随机调整次序
random_shuffle(iterator beg, iterator end)
//反转指定范围的元素
reverse(iterator beg, iterator end)
3.4.5、常用拷贝算法和替换算法
//将容器内指定范围的元素拷贝到另一容器中
copy(iterator beg, iterator end, iterator dest)
//将容器内指定范围的旧元素修改为新元素,需要重载‘==’
replace(iterator beg, iterator end, oldvalue, newvalue)
//将容器内指定范围满足条件的元素替换为新元素,参数3为仿函数,定义相等规则
replace_if(iterator beg, iterator end, bind2nd(FuncT2(),object), newvalue)
//互换两个容器的元素
swap(container c1, container c2)
3.4.6、常用算数生成算法
struct FuncT
{
T operator()(test_class& T1, test_class& T2)
{
t2.Year = t1.Year + t2.Year;
return t1;//定义相加规则,只有这样,值才被加上
}
};
//容器元素都加上object
accumulate(iterator beg, iterator end, object,FuncT())
//向容器中添加元素
fill(iterator beg, iterator end, object)
3.4.7、常用集合算法
set<test_class, SetFunc> s4;
//注:集合算法中,容器元素排列必须是有序的。参数5的写法inserter(s3, s3.begin())
//求两个容器的交集
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
//求两个容器的并集
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
//求两个容器差集
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
以上源码见:https://github.com/w-x-me/Demon_Code/tree/master/C%2B%2B/STL