C++的STL
六大组件: 容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。
1.常用容器
C++ 常用的stl容器包括:
- vector 可变大小的数组,支持随机访问。在尾部之外位置插入或删除元素很慢。
- deque 双端队列,支持快速随机访问,在头尾位置插入删除速度很快。
- list 双向链表,支持双向访问,任何位置插入和删除都很快
- forward_list 单向链表,只支持单向访问,在列表任何位置插入和删除都很快
- array 固定大小数组,支持快速随机访问,不能添加和删除元素。
- string 与vector相似,专门用于处理字符串。
注意:list, deque支持pop_back()弹出最后一个元素,以及pop_front弹出第一个元素。
forward_list不支持pop_back和push_back, vector和string 不支持pop_front和push_front.
forward_list只支持在头部进行插入或删除操作
顺序容器适配器, stack,queue和priority_queue
STL容器使用时机
- | vector | deque | list | set | multiset | map | multimap |
---|---|---|---|---|---|---|---|
典型内存结构 | 单端数组 | 双端数组 | 双向链表 | 二叉树 | 二叉树 | 二叉树 | 二叉树 |
可随机存取 | 是 | 是 | 否 | 否 | 否 | 对key而言:不是 | 否 |
元素搜寻速度 | 慢 | 慢 | 非常慢 | 快 | 快 | 对key而言:快 | 对key而言:快 |
元素安插移除 | 尾端 | 头尾两端 | 任何位置 | - | - | - | - |
vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。
vector与deque的比较:
一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置 却是不固定的。
二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
三:deque支持头部的快速插入与快速移除,这是deque的优点。
list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。
顺序容器:vector 、deque、list、forward_list、array、string
其中list只支持双向顺序访问,不支持快速随机访问、forward_list只支持单向顺序访问
顺序容器类型 | |
---|---|
vector | 可变数组。支持快速访问。在尾部之外的位置插入或删除元素可能很慢 |
deque | 双端队列。支持快速访问。在头尾位置插入或删除速度很快 |
list | 双向链表。只支持双向顺序访问。在list中任何位置进行插入或删除操作速度很快 |
forward_list | 单向链表。只支持单项顺序访问。在链表任何位置插入或删除操作速度都很快 |
array | 固定大小数组。支持快速随机访问。不能添加或删除元素 |
string | 与vector容器相似。但专门用于保存字符。随机访问快。在尾部插入或删除速度快 |
关联容器:
1.有序 set、multiset、map、multimap
共同点是:使用二叉搜索树(准确来说是红黑树,即平衡二叉搜索树)作为其底层结果,容器中的元素是一个有序的序列
2.无序 unordered_map、unordered_set
array
array是C++11新增的容器,效率与普通数据相差无几,比vector效率要高,自身添加了一些成员函数。
和其它容器不同,array 容器的大小是固定的,无法动态的扩展或收缩,只允许访问或者替换存储的元素。
没有弹出插入操作
例:std::array<int,4> arr = {1,2,3,4};
array.size() //返回容器当前元素的数量
array.max_size() //返回容器可容纳的最大数量
array.empty() //判断容器是否为空
array.at(n) //返回容器在n处的引用,有错误检测
array.front() //返回容器第一个元素的引用
array.back() //返回容器的最后一个元素的引用
array.data() //返回指向首个元素的指针
array.fill() //填充元素
array.swap() //交换容器中的元素
list
list容器是一个双向链表。链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
相较于vector的连续线性空间,list的好处是每次插入或者删除一个元素,就是配置或者释放一个元素的空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素的移除,list永远是常数时间。
list.reverse(); //反转链表
list.sort(); //list排序
list.swap(lis); //将list和lis的元素全部交换
forward_list
底层实现和 list 容器一样,采用的也是链表结构,只不过 forward_list 使用的是单链表,而 list 使用的是双向链表。
只提供前向迭代器,不具备rbegin()、rend()之类的函数
forward_list不存在size()函数,如果想要获取 forward_list 容器中存储元素的个数,可以使用头文件 中的 distance() 函数。
std::forward_list<int> my_words{1,2,3,4};
int count = std::distance(std::begin(my_words), std::end(my_words));
forward_list 容器迭代器的移动除了使用 ++
运算符单步移动,还能使用 advance() 函数
before_begin() //返回一个前向迭代器,其指向容器中第一个元素之前的位置。
cbefore_begin() //和 before_begin() 功能相同,只不过在其基础上增加了 const 属性,不能用于修改元素
remove_if() //删除容器中满足条件的元素
merge() //合并两个事先已排好序的 forward_list 容器,并且合并之后的 forward_list 容器依然是有序的
提供了特殊的删除操作erase_after 和特殊的插入操作insert_after。
insert_after //在指定位置之后插入一个新元素,并返回一个指向新元素的迭代器。
erase_after //删除容器中某个指定位置或区域内的所有元素,返回删除元素的下一个元素
vector
Vector所采用的数据结构非常简单,线性连续空间,它以两个迭代器_ Myfirst和_ Mylast分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器_Myend指向整块连续内存空间的尾端。
注意:单向开口,不能在开始处进行插入弹出操作 string也不能在开始处进行插入弹出操作
Vector支持随机存取,而普通指针正有着这样的能力。所以vector提供的是随机访问迭代器
一个vector的容量永远大于或等于其大小,一旦容量等于大小,便是满载,下次再有新增元素,整个vector容器就得另觅居所
注意:1.所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就都失效了
2.在局部区域中(比如局部函数里面)开vector数组,是在堆空间里面开的。在局部区域开数组是在栈空间开的,而栈空间比较小,如果开了非常长的数组就会发生爆栈。故局部区域不可以开大长度数组,但是可以开大长度vector
deque
首尾都可插入和删除的队列为双端队列
Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间
对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque。
deque是由一段一段的定量的连续空间构成,deque采取一块所谓的map(不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。
set/multiset
set容器中的元素不会重复,当插入集合中已有的元素时,并不会插入进去,而且set容器里的元素自动从小到大排序。
所有元素都会根据元素的键值自动被排序。set的元素不像map那样可以同时拥有实值和键值,set的元素即是键值又是实值。
注意:set不允许两个元素有相同的键值。
即:set里面的元素不重复 且有序
我们不可以通过set的迭代器改变set元素的值,因为set元素值就是其键值,关系到set元素的排序规则。如果任意改变set元素值,会严重破坏set组织。换句话说,set的iterator是一种const_iterator.
set默认使用less比较器,即从小到大排序(常用)
set<int> s; // 默认从小到大排序
set<int, greater<int> > s2; // 从大到小排序
s.lower_bound(x); //返回一个迭代器,指向第一个键值不小于x的元素
s.upper_bound(x); //返回一个迭代器,指向第一个键值大于x的元素
s.find(x); //在s中查找x,返回x的迭代器,若x不存在,则返回指向s尾部的迭代器即 s.end()
s.size(); //返回s中元素的个数
multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。set和multiset的底层实现是红黑树.
pair
pair只含有两个元素,可以看作是只有两个元素的结构体
对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用pair的两个公有属性first和second访问
类模板:template <class T1, class T2> struct pair.
//第一种创建方法
pair<string,int> pair1(string("age"),20);
//第二种创建方法
pair<string,int> pair2= make_pair("age",20);
//第三种创建方法 pair = 赋值
pair<string,int> pair3 = pair2;
访问pair
pair<string,int> p[10];
for(int i = 0;i < 10;i++){
std::cout<<p[i].first<<" "<<p[i].second<<std::endl;
}
tuple
tuple模板是pair的泛化,可以封装不同类型任意数量的对象。
可以把tuple理解为pair的扩展,tuple可以声明二元组,也可以声明三元组。
tuple可以等价为结构体使用
元组tuple是C++的一个模板,不同tuple类型的成员类型也不相同,但是一个tuple可以有任意数量的成员。
每个tuple类型的成员个数是一定的,但是不同的tuple成员个数可以不同。
//第一种创建方法
tuple < string, int, vector<int> > tp = tuple < string, int, vector<int> >("sss", 3,{2,3,5});
//第二种创建方法
tuple < string, int, string> tp = make_tuple("sss",3,“asers");
如果添加一个vector作为成员的话
vector<int> nums = {2,3,4};
tuple < string, int, vector<int> > tp = make_tuple("sss",3,nums);
成员访问
在C++标准库里,有一个名为get的函数模板。为了使用get,我们必须指定一个显式模板实参来指示访问的第几个成员,并在函数参数中给予它一个tuple对象。
tuple<int, string, vector<int>> test{ 1,"hello,world",{4,5,6} };
cout << get<0>(test) << endl; //打印test第一个成员,其类型为int
cout << get<1>(test) << endl; //打印test第二个成员,其类型为string
cout << get<2>(test)[0] << endl; //打印test第三个成员vector<int>的第一个元素
获取元素个数
tuple<int, int, int> t(1, 2, 3);
cout << tuple_size<decltype(t)>::value << "\n"; // 3
获取元素值
通过tie解包,tie可以让tuple变量中的三个值依次赋到tie中的三个变量中
int one, three;
string two;
tuple<int, string, int> t(1, "hahaha", 3);
tie(one, two, three) = t;
cout << one << two << three << "\n"; // 1hahaha3
注意:在项目开发时,如果我们想让一个函数返回多个不同类型的值的话可以使用tuple。
map/multimap
map是STL的一个关联容器,它提供一对一的hash。
- 第一个可以称为关键字(key),每个关键字只能在map中出现一次;
- 第二个可能称为该关键字的值(value);
map以模板(泛型)方式实现,可以存储任意类型的数据,包括使用者自定义的数据类型。map主要用于资料一对一映射(one-to-one)的情況,map內部的实现自建一颗红黑树,这颗树具有对数据自动排序的功能。
注意:在map内部所有的数据都是有序的,
功能:自动建立key - value的对应。key 和 value可以是任意你需要的类型,包括自定义类型。
初始化map以及插入数据
std::map<std::string, int> mp;
mp.insert(std::pair<std::string, int>("张三", 20)); //用insert函數插入pair
mp.insert(std::map<std::string, int>::value_type("李四",28));//用insert函数插入value_type数据
mp["王五"] = 38;
C++ maps是一种关联式容器,包含“关键字/值”对
begin() 返回指向map头部的迭代器
clear() 删除所有元素
count() 返回指定元素出现的次数, (因为key值不会重复,所以只能是1 or 0)
empty() 如果map为空则返回true
end() 返回指向map末尾的迭代器
equal_range() 返回特殊条目的迭代器对
erase() 删除一个元素
find() 查找一个元素,找到则返回对于迭代器,否则返回end()
get_allocator() 返回map的配置器
insert() 插入元素
key_comp() 返回比较元素key的函数
lower_bound() 返回键值>=给定元素的第一个位置
max_size() 返回可以容纳的最大元素个数
rbegin() 返回一个指向map尾部的逆向迭代器
rend() 返回一个指向map头部的逆向迭代器
size() 返回map中元素的个数
swap() 交换两个map
upper_bound() 返回键值>给定元素的第一个位置
value_comp() 返回比较元素value的函数
我们不可以通过map的迭代器改变map的键值, 因为map的键值关系到map元素的排列规则,任意改变map键值将会严重破坏map组织。如果想要修改元素的实值,那么是可以的。
Map和list拥有相同的某些性质,当对它的容器元素进行新增操作或者删除操作时,操作之前的所有迭代器,在操作完成之后依然有效,当然被删除的那个元素的迭代器必然是个例外。
Multimap和map的操作类似,唯一区别multimap键值可重复。
Map和multimap都是以红黑树为底层实现机制。
无序容器
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。
无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。
容器将具有一个特定哈希值的所有元素都保存在相同的桶中。
如果容器允许重复关键字,所有具有相同关键字的元素也都会在同一个桶中。
因此,无序容器的性能依赖于哈希函数的质量和桶的数量和大小。
unordered_set/unordered_multiset
- unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素。
- 在unordered_set中,元素的值同时也是唯一地标识它的key。
- 在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key,unordered_set将相同哈希值的键值放在相同的桶中。
- unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。
它的迭代器至少是前向迭代器。
unordered_multiset容器与unordered_set容器的底层数据结构是一样的,都是哈希表,其次,它们所提供的成员函数的接口都是基本一致的。
unordered_multiset容器允许键值冗余,即unordered_multiset容器当中存储的元素是可以重复的
成员函数find | 功能 |
---|---|
unordered_set | 返回键值为val的元素的迭代器 |
unordered_multiset | 返回底层哈希表中第一个找到的键值为val的元素的迭代器 |
成员函数count | 功能 |
---|---|
unordered_set | 键值为val的元素存在则返回1,不存在则返回0(find成员函数可替代) |
unordered_multiset | 返回键值为val的元素个数(find成员函数不可替代) |
unordered_map/unordered_multimap
- unordered_map是存储<key, value>键值对的关联式容器,其允许通过key值快速的索引到与其对应的value。
- 在unordered_map中,键值通常用于唯一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
- 在内部,unordered_map没有对<key, value>按照任何特定的顺序排序,为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。 - unordered_map实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
它的迭代器至少是前向迭代器。
unordered_map容器当中还实现了[ ]运算符重载函数,该重载函数的功能非常强大:[key]
若当前容器中已有键值为key的键值对,则返回该键值对value的引用。
若当前容器中没有键值为key的键值对,则先插入键值对<key, value()>,然后再返回该键值对中value的引用。
unordered_multimap容器与unordered_map容器的底层数据结构是一样的,都是哈希表,其次,它们所提供的成员函数的接口都是基本一致的,这里就不再列举了,这两种容器唯一的区别就是,unordered_multimap容器允许键值冗余,即unordered_multimap容器当中存储的键值对的key值是可以重复的。
成员函数find | 功能 |
---|---|
unordered_map | 返回键值为key的键值的迭代器 |
unordered_multimap | 返回底层哈希表中第一个找到的键值为key的键值对的迭代器 |
成员函数count | 功能 |
---|---|
unordered_map | 键值为key的元素存在则返回1,不存在则返回0(find成员函数可替代) |
unordered_multimap | 返回键值为key的元素个数(find成员函数不可替代) |
2.迭代器
迭代器 | 功能 | 描述 |
---|---|---|
输入迭代器 | 提供对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 提供对数据的只写访问 | 只写,支持++ |
前向迭代器 | 提供读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 提供读写操作,并能向前和向后操作 | 读写,支持++、– |
随机访问迭代器 | 提供读写操作,并能以跳跃的方式访问容器的任意数据,是功能最强的迭代器 | 读写,支持++、–、[n]、-n、<、<=、>、>= |
迭代器失效的原因
因为插入或者删除元素,导致原来内存的数据产生移动,就会导致原有的迭代器失效。
会产生迭代器失效的是使用连续内存存储数据的容器,比如vector,deque,unordered_map,unordered_set;而list,map,set这一类容器使用的不是连续内存,除了被删除的节点的迭代器失效外,其他迭代器不会失效。
迭代器种类
正向迭代器
容器类名::iterator 迭代器名
常量正向迭代器
容器类名::const_iterator 迭代器名
反向迭代器
容器类名::reverse_iterator 迭代器名
常量反向迭代器
容器类名::const_reverse_iterator 迭代器名
通过迭代器可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。通过非常量迭代器还能修改其指向的元素。
迭代器都可以进行++操作。反向迭代器和正向迭代器的区别在于:
- 对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;
- 而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素;
迭代器 的辅助函数
STL 中有用于操作迭代器的三个函数模板,它们是:
advance(p, n):使迭代器 p 向前或向后移动 n 个元素。
distance(p, q):计算两个迭代器之间的距离,即迭代器 p 经过多少次 + + 操作后和迭代器 q 相等。如果调用时 p 已经指向 q 的后面,则这个函数会陷入死循环。
iter_swap(p, q):用于交换两个迭代器 p、q 指向的值。
3.常用算法
函数对象
函数对象的概念:
- 重载操作符()的类,其对象常称为函数对象
- 函数对象使用重载的()时,行为类似函数调用,因此也叫仿函数,其实就是重载“()”操作符,使得类对象可以像函数那样调用
- 本质:函数对象(仿函数)是一个类,而不是一个函数。函数对象(仿函数)重载了”() ”操作符使得它可以像函数一样调用。
class MyPrint{
void operator() (int num) {
cout << num << endl;
m_Num++;
}
}
MyPrint()//仿函数
使用:
函数对象可以像普通函数那样调用,可以有参数和返回值
函数对象超出普通函数之处是,函数对象可以有自己的状态(就是对象的成员变量)
函数对象可以作为参数传递
分类:假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为“一元仿函数”;相反,如果重载的operator()要求获取两个参数,就将这个类称为“二元仿函数”。
总结:
1、函数对象通常不定义构造函数和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用的运行时问题。
2、函数对象超出普通函数的概念,函数对象可以有自己的状态
3、函数对象可内联编译,性能好。用函数指针几乎不可能
4、模版函数对象使函数对象具有通用性,这也是它的优势之一
谓词
谓词是指普通函数或重载的operator()返回值是bool类型的函数对象(仿函数)。如果operator接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词,谓词可作为一个判断式。
//二元谓词 operator接受两个个参数,返回值是bool类型的且为仿函数
class MyCompare {
public: bool operator()(int num1, int num2) {
return num1 > num2;
}
};
内建函数对象
STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。使用内建函数对象,需要引入头文件
#include<functional>
6个算数类函数对象,除了negate是一元运算,其他都是二元运算。
template<class T> T plus<T>//加法仿函数
template<class T> T minus<T>//减法仿函数
template<class T> T multiplies<T>//乘法仿函数
template<class T> T divides<T>//除法仿函数
template<class T> T modulus<T>//取模仿函数
template<class T> T negate<T>//取反仿函数
6个关系运算类函数对象,每一种都是二元运算。
template<class T> bool equal_to<T>//等于
template<class T> bool not_equal_to<T>//不等于
template<class T> bool greater<T>//大于
template<class T> bool greater_equal<T>//大于等于
template<class T> bool less<T>//小于
template<class T> bool less_equal<T>//小于等于
逻辑运算类运算函数,not为一元运算,其余为二元运算。
template<class T> bool logical_and<T>//逻辑与
template<class T> bool logical_or<T>//逻辑或
template<class T> bool logical_not<T>//逻辑非
示例:
//取反仿函数
void test01() {
negate<int> n;
cout << n(50) << endl;
}
//加法仿函数
void test02() {
plus<int> p;
cout << p(10, 20) << endl;
}
函数对象适配器
函数适配器辅助函数
bind1st 函数 : 辅助构造 std::binder1st 绑定适配器 实例对象 , 可以 为 二元函数 第一个参数 绑定一个固定的值 ;
bind2nd 函数 : 辅助构造 std::binder2nd 绑定适配器 实例对象 , 可以 为 二元函数 第二个参数 绑定一个固定的值;
not1 函数 : 辅助构造 unary_negate 组合适配器 实例对象 , 将 一元谓词 的返回值 , 进行 逻辑取反 操作 ;
not2 函数 : 辅助构造 unary_negate 组合适配器 实例对象 , 将 二元谓词 的返回值 , 进行 逻辑取反 操作 ;
函数适配器bind1st bind2nd
如果我们想使用绑定适配器,需要我们自己的函数对象继承binary_function 或者 unary_function
class MyPrint :public binary_function<int,int,void>
{
public:
void operator()(int v1,int v2) const{
cout << "v1 = : " << v1 << " v2 = :" <<v2 << " v1+v2 = :" << (v1 + v2) << endl
}
};
//1、函数适配器
void test01()
{
vector<int>v;
for (int i = 0; i < 10; i++){
v.push_back(i);
}
cout << "请输入起始值:" << endl;
int x;
cin >> x;
for_each(v.begin(), v.end(), bind1st(MyPrint(), x));
//for_each(v.begin(), v.end(), bind2nd( MyPrint(),x ));
}
总结: bind1st和bind2nd区别?
bind1st : 将参数绑定为函数对象的第一个参数
//bind2nd : 将参数绑定为函数对象的第二个参数
//bind1st bind2nd将二元函数对象转为一元函数对象
取反适配器 not1 not2
void test02(){
vector <int> v;
for (int i = 0; i < 10;i++){
v.push_back(i);
}
// vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterThenFive()); //返回第一个大于5的迭代器
// vector<int>::iterator it = find_if(v.begin(), v.end(), not1(GreaterThenFive())); //返回第一个小于5迭代器
//自定义输入
vector<int>::iterator it = find_if(v.begin(), v.end(), not1 ( bind2nd(greater<int>(),5)));
if (it == v.end()){
cout << "没找到" << endl;
}else{
cout << "找到" << *it << endl;
}
//排序 二元函数对象
sort(v.begin(), v.end(), not2(less<int>()));
for_each(v.begin(), v.end(), [](int val){cout << val << " "; });
}
//not1 对一元函数对象取反
//not2 对二元函数对象取反
成员函数适配器
// for_each(v.begin(), v.end(), MyPrint04);
//利用 mem_fun_ref 将Person内部成员函数适配
for_each(v.begin(), v.end(), mem_fun_ref(&Person::ShowPerson));
// for_each(v.begin(), v.end(), mem_fun_ref(&Person::Plus100));
// for_each(v.begin(), v.end(), mem_fun_ref(&Person::ShowPerson));
for_each(v1.begin(), v1.end(), mem_fun(&Person::ShowPerson));
//如果容器存放的是对象指针, 那么用mem_fun
//如果容器中存放的是对象实体,那么用mem_fun_ref
4.常用遍历算法
for_each
for_each(iterator beg,iterator end,_func);-----遍历容器元素
- beg–开始迭代器
- end–结束迭代器
- _func–函数或函数对象
例:
#include<algorithm>
#include<vector>
//遍历for_each
//普通函数
void print1(int val) {
cout << val << " ";
}
//仿函数
class print2 {
public:
void operator()(int val) {
cout << val << " ";
}
};
void Test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
for_each(v.begin(), v.end(), print1);//0 1 2 3 4 5 6 7 8 9
cout << endl;
for_each(v.begin(), v.end(), print2()); //print2()--匿名函数对象
//0 1 2 3 4 5 6 7 8 9
cout << endl;
}
int main() {
Test01();
return 0;
}
transform
搬运容器到另一个容器中
transform(iterator beg1,iterator end1,iterator beg2,_func);
- beg1–源容器开始迭代器
- end1–源容器结束迭代器
- beg2–目标容器开始迭代器
- _func–函数或函数对象
注意: 搬运的目标容器必须提前开辟空间,否则无法正常搬运
class Transform1 {
public:
int operator()(int val) {
return val + 100;
}
};
Transform1()是仿函数,在这里添加仿函数,是可以在仿函数中对源数据进行一些操作之后再搬到目标容器中
transform(v1.begin(), v1.end(), v2.begin(), Transform1());
5.常用查找算法
find
按值查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()
find(iterator beg,iterator end,value);
-
beg–开始迭代器
-
end–结束迭代器
-
value–查找的元素
-
find–在容器中查找指定元素,返回值是迭代器
-
对于查找自定义数据类型来说,需要重载==,让底层find知道如何对比自定义数据类型
//查找自定义数据类型
class Person {
public:
string m_Name;
int m_Age;
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
//重载==,让底层find知道如何对比Person数据类型
bool operator==(const Person& p) {
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
return true;
}else {
return false;
}
}
};
vector<Person> v;
Person p1("刘晨", 19);
Person p2("王敏", 20);
Person p3("张立", 17);
Person p4("李四", 24);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
vector<Person>::iterator it = find(v.begin(), v.end(), p2);
if (it == v.end()) {//注意此处:需要重载==,让底层find知道如何对比Person数据类型
cout << "未找到!" << endl;
}
find_if
按条件查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()
find_if(iterator beg,iterator end,_Pred);
- beg–开始迭代器
- end–结束迭代器
- _Pred–函数或谓词(返回bool类型的仿函数)
- 返回值是迭代器
//1、查找内置数据类型
class GreaterFive {
public:
bool operator()(int val) {
return val > 5;
}
};
void Test05() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//查找容器中是否有大于5的数
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
if (it == v.end()) {
cout << "未找到该元素!" << endl;
}else {
cout << "找到该元素:" << *it << endl;
}
}
//2、查找自定义数据类型
class Person {
public:
string m_Name;
int m_Age;
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
};
class Greater20 {
public:
bool operator()(Person& p) {
return p.m_Age > 20;
}
};
void Test06() {
vector<Person> v;
Person p1("刘晨", 19);
Person p2("王敏", 20);
Person p3("张立", 17);
Person p4("李四", 24);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
//找找容器中是否有年龄>20的元素
vector<Person>::iterator it = find_if(v.begin(), v.end(), Greater20());//此处谓词作用就是指定查找规则
if (it == v.end()) {
cout << "未找到!" << endl;
}
adjacent_find
查找相邻重复元素,找到返回相邻元素的第一个位置的迭代器,找不到返回结束迭代器
adjacent_find (iterator beg,iterator end);
- beg–开始迭代器
- end–结束迭代器
- 返回值是迭代器
//adjacent_find--查找相邻重复元素
void Test07() {
vector<int> v;
v.push_back(1);
v.push_back(0);
v.push_back(2);
v.push_back(4);
v.push_back(0);
v.push_back(3);
v.push_back(4);
v.push_back(4);
//查找容器中是否有相邻重复元素
vector<int>::iterator it = adjacent_find(v.begin(), v.end());
if (it == v.end()) {
cout << "未找到相邻重复元素!" << endl;
}
else {
cout << "找到相邻重复元素:" << *it << endl;
}
}
binary_search
-----------二分查找 在有序序列中才可使用
查找指定元素是否存在,查到返回true,否则返回false
查找效率很高!
binary_search (iterator beg,iterator end,value);
- beg–开始迭代器
- end–结束迭代器
- value–查找的元素
- 在无序序列中不可用,如果是无序序列,则结果未知
- 返回值是bool型
count
统计元素个数 自定义的类型需要重载==
count(iterator beg,iterator end,value);
- beg–开始迭代器
- end–结束迭代器
- value–统计的元素
count_if
按条件统计元素个数
count_if (iterator beg,iterator end,_Pred);
- beg–开始迭代器
- end–结束迭代器
- _Pred–谓词(返回bool类型的仿函数)
class greater3 {
public:
bool operator()(int val){
return val > 3;
}
};
void Test11() {
vector<int> v1;
v1.push_back(0);
v1.push_back(2);
v1.push_back(4);
v1.push_back(3);
v1.push_back(5);
v1.push_back(4);
v1.push_back(4);
v1.push_back(1);
//统计大于3的数有多少个
int num = count_if(v1.begin(), v1.end(),greater3());//通过仿函数指定规则
cout << "大于3的元素个数为:" << num << endl;//4
}
6.常用排序算法
sort
对容器内元素进行排序 默认升序
sort(iterator beg,iterator end,_Pred);
//第三个参数可写可不写,如果有自定义排序规则,就需要写谓词
- beg–开始迭代器
- end–结束迭代器
- _Pred–谓词(返回bool类型的仿函数)
class myCompare {
public:
bool operator()(int v1, int v2) {
return v1 > v2;
}
};
//sort(v1.begin(), v1.end(), myCompare());//自定义降序仿函数
sort(v1.begin(), v1.end(), greater<int>());//使用内建仿函数
is_sorted
is_sorted(beg, end)
判断序列是否有序(升序),返回bool值
random_shuffle
洗牌 指定范围内的元素随机调整顺序
random_shuffle(iterator beg,iterator end);
- beg–开始迭代器
- end–结束迭代器
srand((unsigned int)time(NULL));//加上随机数种子之后,每一次随机打乱的结果就都是不一样的
//利用洗牌算法 打乱顺序
random_shuffle(v1.begin(), v1.end());
for_each(v1.begin(), v1.end(), myPrint);//8 1 9 2 0 5 7 3 4 6
如果不加随机数种子的话,每次的执行结果都是8 1 9 2 0 5 7 3 4 6
加上之后,每次的执行结果都不一样
- 随机打乱序列的顺序
- random_shuffle在 C++14中被弃用,在 C++17中被废除,C++11之后应尽量使用shuffle来代替。
merge
merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
- beg1–容器1开始迭代器
- end1–容器1结束迭代器
- beg2–容器2开始迭代器
- end2–容器2结束迭代器
- dest–目标容器开始迭代器
- 注意:两个容器必须是有序的,合并完之后也是一个有序序列
- 目标容器需要提前开辟空间
void myPrint(int val) {
cout << val << " ";
}
void Test15() {
vector<int> v1;
vector<int> v2;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
v2.push_back(i + 1);
}
vector<int> v;
//目标容器需要提前开辟空间
v.resize(v1.size() + v2.size());
merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v.begin());
for_each(v.begin(), v.end(), myPrint);//0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10
cout << endl;
}
reverse
将容器内元素反转
reverse(iterator beg,iterator end);
- beg–开始迭代器
- end–结束迭代器
reverse(v1.begin(), v1.end());
7.常用拷贝和替换算法
copy
容器内指定范围内元素拷贝到另一容器中
copy(iterator beg,iterator end,iterator dest);
- beg–开始迭代器
- end–结束迭代器
- dest–目标起始迭代器
- 目标容器需要提前开辟空间
vector<int> v;
v.resize(v1.size());
copy(v1.begin(), v1.end(), v.begin());
replace
将容器内指定范围的旧元素修改为新元素
replace(iterator beg,iterator end,oldvalue,newvalue);
- beg–开始迭代器
- end–结束迭代器
- oldvalue–旧元素
- newvalue–新元素
replace(v1.begin(), v1.end(), 20, 200);
replace_if
将容器内指定范围满足条件的元素替换为新元素
replace_if(iterator beg,iterator end,_Pred,newvalue);
- beg–开始迭代器
- end–结束迭代器
- _Pred–谓词
- newvalue–替换的新元素
class greater30 {
public:
bool operator()(int val) {
return val > 30;
}
};
replace_if(v1.begin(), v1.end(),greater30(), 400);
swap
swap(container c1,container c2);
- c1–容器1
- c2–容器2
- 交换的容器要是同类型的
8.常用算数生成算法
accumulate
计算区间内容器元素累计总和
accumulate(iterator beg,iterator end,value);
- beg–开始迭代器
- end–结束迭代器
- value–起始累加值
//参数3是一个起始累加值
int total=accumulate(v.begin(), v.end(), 0);
fill
像容器中填充指定元素
fill(iterator beg,iterator end,value);
- beg–开始迭代器
- end–结束迭代器
- value–填充的值
fill(v.begin(), v.end(), 100);
for_each(v.begin(), v.end(), myPrint());*//100 100 100 100 100 100 100 100 100 100*
注意区分memset:
memset()是按字节进行赋值,对于初始化赋0或-1有比较好的效果.
如果赋某个特定的数会出错,赋值特定的数建议使用fill()
9.常用集合算法
set_intersection
求两个容器的交集
set_intersection(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
- beg1–容器1开始迭代器
- end1–容器1结束迭代器
- beg2–容器2开始迭代器
- end2–容器2结束迭代器
- dest–目标容器开始迭代器
- 注意:两个集合必须是有序序列
- 返回值:交集中最后一个元素的迭代器位置
- 目标容器开辟空间需要从两个容器中取小值,也就是最特殊的那种情况–大容器包小容器
即新的目标容器开辟的空间和两个容器中较小的那个的空间大小相同
//目标容器需要提前开辟空间
//最特殊的时候,大容器包小容器,取小容器的size即可
v.resize(min(v1.size(), v2.size()));
//返回目标容器的最后一个元素的迭代器位置
vector<int>::iterator itEnd=set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v.begin());
for_each(v.begin(), itEnd, myPrint());//5 6 7 8 9
//for_each(v.begin(), v.end(), myPrint());//5 6 7 8 9 0 0 0 0 0
set_union
求两个容器的并集
set_union (iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
- beg1–容器1开始迭代器
- end1–容器1结束迭代器
- beg2–容器2开始迭代器
- end2–容器2结束迭代器
- dest–目标容器开始迭代器
- 注意:两个集合必须是有序序列
- 返回值:并集中最后一个元素的迭代器位置
- 目标容器开辟空间需要取两个容器的大小之和,也就是最特殊的那种情况–大容器和小容器无交集
//目标容器需要提前开辟空间
//最特殊的时候,大容器和小容器中没有交集
v.resize(v1.size()+v2.size());
//返回目标容器的最后一个元素的迭代器位置
vector<int>::iterator itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), v.begin());
for_each(v.begin(), itEnd, myPrint());//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
set_difference
求两个容器的差集
set_difference (iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
- beg1–容器1开始迭代器
- end1–容器1结束迭代器
- beg2–容器2开始迭代器
- end2–容器2结束迭代器
- dest–目标容器开始迭代器
- 注意:两个集合必须是有序序列
- 返回值:差集中最后一个元素的迭代器位置
- 目标容器开辟空间需要取两个容器中大的那个的空间大小,也就是最特殊的那种情况–大容器和小容器无交集
//目标容器需要提前开辟空间
//最特殊的时候,大容器和小容器中没有交集,需取两容器大的那个
v.resize(max(v1.size() ,v2.size()));
//返回差集中最后一个元素的迭代器位置
vector<int>::iterator itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v.begin());
cout << "v1和v2的差集为:" << endl;
for_each(v.begin(), itEnd, myPrint());//0 1 2 3 4
cout << endl;
cout << "v2和v1的差集为:" << endl;
itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), v.begin());
for_each(v.begin(), itEnd, myPrint());//10 11 12 13 14
10.补充
atoi
atoi(const char*)
- 将字符串转换为int类型
注意参数为char型数组,如果需要将string类型转换为int类型,可以使用stoi函数,或者将string类型转换为const char *类型。
string s = "1234";
int a = atoi(s.c_str());
cout << a << "\n"; // 1234
关于输出数字的范围:
atoi不做范围检查,如果超出上界,输出上界,超出下界,输出下界。
stoi会做范围检查,默认必须在int范围内,如果超出范围,会出现RE(Runtime Error)错误。
c_str()函数
功能:c_str() 函数可以将 const string* 类型 转化为 const char* 类型
头文件:#include
c_str()就是将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址
因为在c语言中没有string类型,必须通过string类对象的成员函数 c_str() 把 string 转换成c中的字符串样式
注意:c_str() 这个函数转换后返回的是一个临时指针,不能对其进行操作
所以因为这个数据是临时的,所以当有一个改变这些数据的成员函数被调用后,该数据就会改变失效;
因此c_str()常常和其他函数搭配使用
soti
stoi(const string*)
- 将对应string类型字符串转换为数字
string s = "1234";
int a = stoi(s);
cout << a << "\n"; // 1234
to_string
将数字转化为字符串,支持小数(double)
int a = 12345678;
cout << to_string(a) << '\n';
unique
unique(beg, end)
- 消除重复元素,返回消除完重复元素的下一个位置的地址
如:a[] = {1, 3, 2, 3, 6};
unique 之后 a数组为{1, 2, 3, 6, 3}前面为无重复元素的数组,后面则是重复元素移到后面,返回a[4]位置的地址(不重复元素的尾后地址)
消除重复元素一般需要原序列是有序序列