C++ STL六大组件-简析
----------------------------------------------------------------------------------------------------------------------------------------------
C++ STL六大组件-1-Container(容器)
C++ STL六大组件-2-Adapter(适配器)
C++ STL六大组件-3-Algorithm(算法)
C++ STL六大组件-4-Iterator(迭代器)
C++ STL六大组件-5-Function object(函数对象)
C++ STL六大组件-6-Allocator(分配器)
-----------------------------------------------------------------分割线---------------------------------------------------------------------------------
在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。
容器就是保存其它对象的对象,当然这是一个朴素的理解,这种“对象”还包含了一系列处理“其它对象”的方法。
容器是STL中很重要的一种数据结构。常见的容器包括
常用的容器类型分为:
vector是一种动态数组,在内存中具有连续的存储空间,支持快速随机访问。由于具有连续的存储空间,所以在插入和删除操作方面,效率比较慢。vector有多个构造函数,默认的构造函数是构造一个初始长度为0的内存空间,且分配的内存空间是以2的倍数动态增长的。在push_back的过程中,若发现分配的内存空间不足,则重新分配一段连续的内存空间,其大小是现在连续空间的2倍,再将原先空间中的元素复制到新的空间中,性能消耗比较大。
vector的另一个常见的问题就是clear操作。clear函数只是把vector的size清为零,但vector中的元素在内存中并没有消除,所以在使用vector的过程中会发现内存消耗会越来越多,导致内存泄露,现在经常用的方法是swap函数来进行解决:
vector V;
V.push_back(1);
V.push_back(2);
V.push_back(1);
V.push_back(2);
vector().swap(V);
//或者 V.swap(vector());
vector 基本用法:
front()
返回头部元素的引用,可以当左值back()
返回尾部元素的引用,可以当左值push_back()
添加元素,只能尾部添加pop_back()
移除元素,只能在尾部移除 vec1.push_back(100); //添加元素
int size = vec1.size(); //元素个数
bool isEmpty = vec1.empty(); //判断是否为空
cout<=、<=...
vector::iterator iter = vec1.begin(); //获取迭代器首地址
vector::const_iterator c_iter = vec1.begin(); //获取const类型迭代器
vec1.clear(); //清空元素
vector有4种方式初始化,有直接初始化,也要通过拷贝构造函数初始化。
vector vec1; //默认初始化,vec1为空
vector vec2(vec1); //使用vec1初始化vec2
vector vec3(vec1.begin(),vec1.end());//使用vec1初始化vec2
vector vec4(10); //10个值为0的元素
vector vec5(10,4); //10个值为4的元素
vector vec6(10,"null"); //10个值为null的元素
vector vec7(10,"hello"); //10个值为hello的元素
int main(int argc, const char * argv[]) {
//直接构造函数初始化
vector v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
//通过拷贝构造函数初始化
vector v2 = v1;
//使用部分元素来构造
vector v3(v1.begin(), v1.begin() + 1);
vector v4(v1.begin(), v1.end());
//存放三个元素,每个元素都是9
vector v5(3,9);
return 0;
}
vector的遍历有多种方式,可以根据[]
或者迭代器遍历。
需要注意的是:
[]
方式,如果越界或出现其他错误,不会抛出异常,可能会崩溃,可能数据随机出现at
方式,如果越界或出现其他错误,会抛出异常,需要捕获异常并处理int main(int argc, const char * argv[]) {
//创建vector
vector v1;
//插入元素
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
//遍历-[]取值
for (int i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << endl;
//遍历-at取值
for (int i = 0; i < v1.size(); i++) {
cout << v1.at(i) << " ";
}
cout << endl;
//遍历-迭代器遍历
for (vector::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
//遍历-迭代器逆向遍历
for (vector::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++) {
cout << *it << " ";
}
cout << endl;
//测试越界
cout << "[]越界:" << v1[20] << endl; //不会抛出异常,可能会崩溃,可能会乱码
cout << "at越界:" << v1.at(20) << endl; //会抛出异常,需要捕获异常
return 0;
}
deque和vector类似,支持快速随机访问。二者最大的区别在于,vector只能在末端插入数据,而deque支持双端插入数据。
deque的内存空间分布是小片的连续,小片间用链表相连,实际上内部有一个map的指针。deque空间的重新分配要比vector快,重新分配空间后,原有的元素是不需要拷贝的。
与vector不同的是,deque还支持从开始端插入数据:push_front()。其余类似vector操作方法的使用。
push_back
从尾部插入元素push_front
从头部插入元素pop_back
从尾部删除元素pop_front
从头部删除元素
插播一个知识点:
distance
函数可以求出当前的迭代器指针it距离头部的位置,也就是容器的指针用法:
distance(v1.begin(), it)
list是一个双向链表,因此它的内存空间是可以不连续的,通过指针来进行数据的访问,这使list的随机存储变得非常低效,因此list没有提供[]操作符的重载。但list可以很好地支持任意地方的插入和删除,只需移动相应的指针即可。
list的定义和初始化:
list lst1; //创建空list
list lst2(3); //创建含有三个元素的list
list lst3(3,2); //创建含有三个元素的值为2的list
list lst4(lst2); //使用lst2初始化lst4
list lst5(lst2.begin(),lst2.end()); //同lst4
list的常见操作:
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(); //删除相邻重复元素
实际使用时,如何选择这三个容器中哪一个,应根据你的需要而定,一般应遵循下面的原则:
1) 如果你需要高效的随机存取,而不在乎插入和删除的效率,使用vector
2) 如果你需要大量的插入和删除,而不关心随即存取,则应使用list
3) 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque
顺序容器使用方法公共点:
添加元素
函数名 | 意义 |
c.push_back(t) | 在容器c的尾部添加值为t的元素。返回void 类型 |
c.push_front(t) | 在容器c的前端添加值为t的元素。返回void 类型 只适用于list和deque容器类型。 |
c.insert(p,t) | 在迭代器p所指向的元素前面插入值为t的新元素。返回指向新添加元素的迭代器。 |
c.insert(p,n,t) | 在迭代器p所指向的元素前面插入n个值为t的新元素。返回void 类型 |
c.insert(p,b,e) | 在迭代器p所指向的元素前面插入由迭代器b和e标记的范围内的元素。返回 void 类型 |
查看容器大小:
函数名 | 意义 |
c.size() | 返回容器c中元素个数。返回类型为 c::size_type |
c.max_size() | 返回容器c可容纳的最多元素个数,返回类型为c::size_type |
c.empty() | 返回标记容器大小是否为0的布尔值 |
c.resize(n) | 调整容器c的长度大小,使其能容纳n个元素,如果n |
c.resize(n,t) | 调整容器c的长度大小,使其能容纳n个元素。所有新添加的元素值都为t |
访问元素:
函数名 | 意义 |
c.back() | 返回容器 c 的最后一个元素的引用。如果 c 为空,则该操作未定 义 |
c.front() | 返回容器 c 的第一个元素的引用。如果 c 为空,则该操作未定义 |
c[n] | 返回下标为 n 的元素的引用。如果 n <0 或 n >= c.size(),则该操作未定义 只适用于 vector 和 deque 容器 |
c.at(n) | 返回下标为 n 的元素的引用。如果下标越界,则该操作未定义 只适用于 vector 和 deque 容器 |
删除元素:
函数名 | 意义 |
c.erase(p) | 删除迭代器p所指向的元素。返回一个迭代器,它指向被删除元素后面的元素。如果p指向容器内的最后一个元素,则返回的迭代器指向容器超出末端的下一位置。如果p本身就是指向超出末端的下一位置的迭代器,则该函数未定义 |
c.erase(b,e) | 删除迭代器b和e所标记的范围内所有的元素。返回一个迭代器,它指向被删除元素段后面的元素。如果e本身就是指向超出末端的下一位置的迭代器,则返回的迭代器也指向容器的超出末端的下一位置 |
c.clear() | 删除容器c内的所有元素。返回void |
c.pop_back() | 删除容器c的最后一个元素。返回void。如果c为空容器,则该函数未定义 |
c.pop_front() | 删除容器c的第一个元素。返回void。如果c为空容器,则该函数未定义 只适用于 list 或 deque 容器 |
赋值与swap
函数名 | 意义 |
c1 = c2 | 删除容器c1的所有元素,然后将c2的元素复制给c1。c1和c2的类型(包括容器类型和元素类型)必须相同 |
c1.swap(c2) | 交换内容:调用完该函数后,c1中存放的是 c2 原来的元素,c2中存放的则是c1原来的元素。c1和c2的类型必须相同。该函数的执行速度通常要比将c2复制到c1的操作快 |
c.assign(b,e) | 重新设置c的元素:将迭代器b和e标记的范围内所有的元素复制到c中。b和e必须不是指向c中元素的迭代器 |
c.assign(n,t) | 将容器c重新设置为存储n个值为t的元素 |
关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
关联容器支持高效的关键字查找与访问。两个主要的关联容器类型是map与set。
在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。所用的比较函数必须在键类型上定义严格弱排序(strict weak ordering):可理解为键类型数据上的“小于”关系,虽然实际上可以选择将比较函数设计得更复杂。
对于键类型,唯一的约束就是必须支持 < 操作符,至于是否支持其他的关系或相等运算,则不作要求。
C++中map容器提供一个键值对(key/value)容器,map与multimap差别仅仅在于multiple允许一个键对应多个值。对于迭代器来说,可以修改实值,而不能修改key。Map会根据key自动排序。
map 是键-值对的集合。map 类型通常可理解为关联数组:可使用键作为下标来获取一个值,正如内置数组类型一样。而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取。
map内部自建一棵红黑树(一种自平衡二叉树),这棵树具有数据自动排序的功能,所以在map内部所有的数据都是有序的,以二叉树的形式进行组织。
map的定义与初始化:
函数名 | 意义 |
map |
创建一个名为m的空map对象,其键和值的类型分别为k和v |
map m(m2) |
创建m2的副本m,m与m2必须有相同的键类型和值类型 |
map m(b, e) |
创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本。元素的类型必须能转换为pair |
map添加元素:
函数名 | 意义 |
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中具有给定键的元素 |
map删除元素:
函数名 | 意义 |
m.erase(k) | 删除m中键为k的元素。返回size_type类型的值,表示删除的元素个数 |
m.erase(p) | 从m中删除迭代器p所指向的元素。p必须指向m中确实存在的元素,而且不能等于m.end()。返回void |
m.erase(b, e) |
从m中删除一段范围内的元素,该范围由迭代器对b和e标记。b和e必须标记m中的一段有效范围:即b和e都必须指向m中的元素或最后一个元素的下一个位置。而且,b和e要么相等(此时删除的范围为空),要么b所指向的元素必须出在e所 指向的元素之前。返回 void 类型 |
map遍历元素:
函数名 | 意义 |
m.count(k) | 返回 m 中 k 的出现次数 |
m.find(k) | 如果m容器中存在按k索引的元素,则返回指向该元素的迭代器。如果不存在,则返回超出末端迭代器。 |
map元素查找:
map提供了两个函数进行key的查找:find和equal_range。
map和multimap的对比:
唯一区别是multimap支持多个键值。由于支持多个键值,multimap提供了cout函数来计算同一个key的元素个数。
set也是一种关联性容器,它同map一样,底层使用红黑树实现,插入删除操作时仅仅移动指针即可,不涉及内存的移动和拷贝,所以效率比较高。
set的含义是集合,它是一个有序的容器,里面的元素都是排序好的,支持插入,删除,查找等操作,就像一个集合一样。所有的操作的都是严格在logn时间之内完成,效率非常高。
set中的元素都是唯一的,而且默认情况下会对元素进行升序排列。set和multiset的区别是:set插入的元素不能相同,但是multiset可以相同。Set默认自动排序。使用方法类似list。
所以在set中,不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,再插入新元素。不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。
set的定义和使用:
set容器是有序的集合,默认的顺序是从小到大的。
创建集合的方式:
set
创建默认的从小到大的int类型的集合setless>
创建一个从小打到大的int类型的集合setgreater>
创建一个从大到小的int类型的集合上面的less和greater就是仿函数,集合会根据这个仿函数的返回值是否为真类进行排序。
set 容器的每个键都只能对应一个元素。以一段范围的元素初始化set对象,或在set对象中插入一组元素时,对于每个键,事实上都只添加了一个元素。
vector ivec;
for (vector::size_type i = 0; i != 10; ++i) {
ivec.push_back(i);
ivec.push_back(i);
}
set iset(ivec.begin(), ivec.end());
cout << ivec.size() << endl; //20个
cout << iset.size() << endl; // 10个
set添加和删除元素:set提供了insert
和erase
函数,用来对元素进行插入和删除操作。
set set1;
set1.insert("the"); //第一种方法:直接添加
set iset2;
iset2.insert(ivec.begin(), ivec.end());//第二中方法:通过指针迭代器
//删除集合
while(!set1.empty())
{
//获取头部
set::iterator it = set1.begin();
//打印头部元素
cout << *it << endl;
//从头部删除元素
set1.erase(set1.begin());
}
set获取元素:不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。
set 容器不提供下标操作符。为了通过键从 set 中获取元素,可使用 find运算。
如果只需简单地判断某个元素是否存在,同样可以使用 count 运算,返回 set 中该键对应的元素个数。
当然,对于 set 容器,count 的返回值只能是1(该元素存在)或 0(该元素不存在)。
set 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
迭代器的关联容器操作:
函数名 | 意义 |
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) |
适配器(Adaptors)是标准库中的一个通用概念,容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
一个容器适配器(Container adaptors)接受一种已有的容器类型,使其行为看起来像一种不同的类型。标准库定义了三个序列容器适配器:stack、queue和priority_queue。
queue是一个队列,实现先进先出功能,queue不是标准的STL容器,却以标准的STL容器为基础。queue是在deque的基础上封装的。之所以选择deque而不选择vector是因为deque在删除元素的时候释放空间,同时在重新申请空间的时候无需拷贝所有元素。
queue 和 stack 有一些成员函数相似,但在一些情况下,工作方式有些不同:
- front():返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
- back():返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
- push(const T& obj):在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。
- push(T&& obj):以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。
- pop():删除 queue 中的第一个元素。
- size():返回 queue 中元素的个数。
- empty():如果 queue 中没有元素的话,返回 true。
- emplace():用传给 emplace() 的参数调用 T 的构造函数,在 queue 的尾部生成对象。
- swap(queue
&other_q):将当前 queue 中的元素和参数 queue 中的元素交换。它们需要包含相同类型的元素。也可以调用全局函数模板 swap() 来完成同样的操作。
和 stack 一样,queue 也没有迭代器。访问元素的唯一方式是遍历容器内容,并移除访问过的每一个元素。
q.pop() | 移除q的首元素或priority_queue的最高优先级的元素,返回void |
q.front() | 访问首元素或尾元素 |
q.back() | 访问尾元素,只适用于queue |
q.top() | 访问最高优先级元素,只适用于priority_queue |
q.push(item) | 在queue末尾或priority_queue中恰当位置创造一个元素,其值为item, 或者又args构造 |
q.emplace(args) | 同上 |
stack是实现先进后出的功能,和queue一样,也是内部封装了deque。stack的源代码原理和实现方式均跟queue相同。
stack的定义:
int main(int argc, const char * argv[]) {
//定义stack对象
stack s1;
//入栈
s1.push(1);
s1.push(2);
s1.push(3);
s1.push(4);
//打印栈顶元素,并出栈
while (!s1.empty()) {
//取出栈顶元素
cout << "当前栈顶元素" << s1.top() << endl;
//获取栈的大小
cout << "当前栈的大小" << s1.size() << endl;
//出栈
s1.pop();
}
return 0;
}
//定义类
class Teacher {
public:
char name[32];
int age;
void printT()
{
cout << "age = " << age << endl;
}
};
int main(int argc, const char * argv[]) {
Teacher t1, t2, t3;
t1.age = 22;
t2.age = 33;
t3.age = 44;
//定义栈容器
stack s1;
//入栈
s1.push(t1);
s1.push(t2);
s1.push(t3);
//出栈并打印
while (!s1.empty()) {
//打印栈顶元素
Teacher tmp = s1.top();
tmp.printT();
//出栈
s1.pop();
}
return 0;
}
stk.top() | 访问栈顶元素 |
stk.push(value) | 将value压入栈内 |
stk.emplace(seq) | 用seq构造元素并压栈 |
stk.pop() | 将当前栈顶元素出栈,返回类型void |
参考文档:
https://blog.csdn.net/u014465639/article/details/70241850
https://blog.csdn.net/u013443618/article/details/49964299
https://www.jianshu.com/p/497843e403b4
https://blog.csdn.net/v_JULY_v/article/details/6105630
https://blog.csdn.net/qq_37653144/article/details/79334725