文章目录
前言
本文仅仅对stl基本理论以及常用容器、算法进行简单概括,对常用的数据结构使用特点进行了描述,列举了容器的部分API,简述了API用途,方便查阅,对于使用中我所遇见的问题进行了总结,并给出了部分问题的解决建议。
stl的分配器,此处不做详细介绍,此部分会在c++内存管理部分深入。至于仿函数、适配器文中的函数对象以及stack会涉及。
1.01^365 = 37.7834343329
1.02^365 = 1377.40829197
只比你努力一点的人其实已经甩你很远
第一章 STL理论基础
1.1 STL基本概念
STL(Standard Template Library,标准模板库),是惠普实验室开发的一系列软件的统称。现在主要出现在c++中,但是在引入c++之前该技术已经存在很长时间了。
STL从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。STL几乎所有的代码都采用了模板类或者模板函数,这相比传统的由函数和类组成的库来说提供了更好的代码重用机会。
STL(Standard Template Library)标准模板库,在我们c++标准程序库中隶属于STL的占到了80%以上。
在c++标准中,STL被组织成以下13个头文件:
<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 、<utility>
我们为什么要使用STL?因为STL封装了常用数据结构与算法,方便我们开发,况且我们自己造轮子很难比标准的好吧,费时费力。
STL还有什么优点呢?
-
STL是C++的一部分,因此不用额外安装什么,它被内建在你的编译器之内。
-
STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但是这种分离确实使得STL变得非常通用。例如:在STL的vector容器中,可以放入元素、基础数据类型变量、元素的地址;STL的sort()
排序函数可以用来操作vector,list等容器。 -
程序员可以不用思考STL具体的实现过程,只要能够熟练使用STL就行了。这样他们就可以把精力放在程序开发的别的方面。
-
STL具有高可重用性,高性能,高移植性,跨平台的优点。
高可重用性:STL中几乎所有的代码都采用了模板类和模版函数的方式实现,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。关于模板的知识,已经给大家介绍了。
高性能:如map可以高效地从十万条记录里面查找出指定的记录,因为map是采用红黑树的变体实现的。(红黑树是平衡二叉树的一种)
高移植性:如在项目A上用STL编写的模块,可以直接移植到项目B上。
跨平台:如用windows的Visual Studio编写的代码可以在Mac OS的XCode上直接编译。
1.2 STL三大组件介绍
1.2.1 容器概念介绍
STL中容器是指存储有限数据元素的一种数据结构。比如栈(stack),队列(queue)…那么什么是数据结构,我们研究把数据按照特定的方式排列起来,便于我们查找 删除 排序或者其他一些目的,这种不同的排列方式我们就可以叫数据结构。容器可以简单理解为存放数据的工具,不同的容器,就是将数据以不同的结构存储起来。因此根据场景的不同,我们选择的容器也不同。
1.2.2 迭代器介绍
对于迭代器的概念,其实不用去太纠结,它就是为了遍历容器而诞生的,只是c++里面用了模板来定义容器类,这里就不得不新定义一个东西来统一来描述容器的元素,就像所有的基础数据类型,都可以用指针去指向,通过* 来取地址里面的值。
官方的说,迭代器是一种抽象出来的概念,现实中不容易找出来某项事物与之对应,所以较难理解。但是在我们程序中,比如我们写的数据,我们通过[]操作符遍历取值,那么[]就是一个迭代器,也必须说我们经常用的指针,他也是一种迭代器。
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器,也就是说迭代器就是对我们普通的指针做了一层封装,其行为也类似指针。我们现在呢?可以单纯得把迭代器理解为,它就是一个指针,用来指向不同的元素,既然是指针,那么指针的一些基本运算操作,比如*、++、==、!=、=,迭代器也可以进行这样的操作。
1.2.3 算法介绍
以有限的步骤,解决逻辑或者数学上的问题,这门学科我们就叫做算法。一般来说,我们每天都在写各种各样的算法,比如我们写的每一个函数,它被用来解决或大或小的问题。
在我们工作中,我们要写一个算法来解决一个问题的时候,那么需要考虑你写的算法需要消耗的计算机资源,包括时间和空间,如果你写一个算法需要消耗1G内存来解决这个问题,那么你这个算法也就没有什么价值了。
STL为我们的提供的算法,都很高效,而且还有个最大的特点,可复用性。那么我们学习算法,就很简单了,我们只需要去熟悉并且能熟练应用STL为我们提供的常用算法就OK了。
STL提供了大约100个实现算法的模版函数,比如算法for_each将为指定序列中的每一个元素调用指定的函数等。这样一来,只要我们熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法函数,就可以完成所需要的功能并大大地提升效率
1.2.4 总结
容器就是数据结构,用来将数据元素按照一定的规则进行排列,不同的容器拥有不同的排列规则,不同的排列规则可以达到不同的数据操作特点,比如数据这种数据结构,我们随机存取就很高效,算法就是提供对容器数据元素的一些操作,比如遍历容器元素,删除容器元素等迭代器就是容器和算法之间的桥梁,粘合剂,用来将两个相对独立的部件建立起关系。
STL中容器和算法的设计是彼此分离,这样的好处就是:
1 )容器和算法的编写可以分别编写,互补影响
2 )容器只需要提供迭代器,算法只需要拿到迭代器就可以完成容器和算法之间的关联和操作
第二章 常用容器
关于容器API部分由于篇幅有限,仅列举部分。
2.1 string容器
2.1.1 string的特性
string封装了char*,管理这个字符串,是一个char*型的容器。
string封装了字符串常用的很多实用的成员方法。
不用考虑内存释放和越界。
2.1.2 string 常用API
2.1.2.1 构造、赋值
string();//创建一个空的字符串 例如: string str;
string(const string& str);//使用一个string对象初始化另一个string对象
string(const char* s);//使用字符串s初始化
string(int n, char c);//使用n个字符c初始化
//例子:
//默认构造函数
string s1;
//拷贝构造函数
string s2(s1);
string s2 = s1;
//带参数构造函数
char* str = "itcast";
string s3(str);
string s4(10, 'a');
string& operator=(const char* s);//char*类型字符串 赋值给当前的字符串
string& operator=(const string &s);//把字符串s赋给当前的字符串
string& operator=(char c);//字符赋值给当前的字符串
string& assign(const char *s);//把字符串s赋给当前的字符串
string& assign(const char *s, int n);//把字符串s的前n个字符赋给当前的字符串
string& assign(const string &s);//把字符串s赋给当前字符串
string& assign(int n, char c);//用n个字符c赋给当前字符串
string& assign(const string &s, int start, int n);//将s从start开始n个字符赋值给字符串
2.1.2.2 存取
char& operator[](int n);//通过[]方式取字符
char& at(int n);//通过at方法获取字符
//例子:
string s = "qwerty";
char s1 = s[0];
char s2 = s.at(2);
[]和at的区别:
at访问越界会抛出异常,[]越界会直接程序会挂掉
2.1.2.3 拼接、查找、替换
string& operator+=(const string& str);//重载+=操作符
string& operator+=(const char* str);//重载+=操作符
string& operator+=(const char c);//重载+=操作符
string& append(const char *s);//把字符串s连接到当前字符串结尾
string& append(const char *s, int n);//把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s);//同operator+=()
string& append(const string &s, int pos, int n);//把字符串s中从pos开始的n个字符连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加n个字符c
int find(const string& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找
int find(const char* s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找
int find(const char* s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置
int find(const char c, int pos = 0) const; //查找字符c第一次出现位置
int rfind(const string& str, int pos = npos) const;//查找str最后一次位置,从pos开始查找
int rfind(const char* s, int pos = npos) const;//查找s最后一次出现位置,从pos开始查找
int rfind(const char* s, int pos, int n) const;//从pos查找s的前n个字符最后一次位置
int rfind(const char c, int pos = 0) const; //查找字符c最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
string& replace(int pos, int n, const char* s); //替换从pos开始的n个字符为字符串s
这里注意find的返回值是查找到字符位置,通常通过返回值进行比较字符在某个字符之前
2.1.2.4 比较
/*
compare函数在>时返回 1,<时返回 -1,==时返回 0。
比较区分大小写,比较时参考字典顺序,排越前面的越小。
大写的A比小写的a小。
*/
int compare(const string &s) const;//与字符串s比较
int compare(const char *s) const;//与字符串s比较
2.1.2.5 子串
string substr(int pos = 0, int n = npos) const;//返回由pos开始的n个字符组成的字符串
2.1.2.6 插入、删除
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入n个字符c
string& erase(int pos, int n = npos);//删除从Pos开始的n个字符
2.1.3 string练习
//用户邮箱地址验证
// 1 判断邮箱有效性 是否包含@和. 并且.在@之后
// 2 判断用户输入的用户名中是否包含除了小写字母之外字符(ASCII范围97~122)
// 3 判断用户输入的邮箱地址是否正确(alne@mavenir.cn)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
// 1 判断邮箱有效性 是否包含@和.并且.在@之后
bool Check_Valid(string& email){
int pos1 = email.find("@");
int pos2 = email.find(".");
//判断@和.是否存在
if (pos1 == -1 || pos2 == -1){
return false;
}
//判断@在.之前
if (pos1 > pos2){
return false;
}
return true;
}
//2 判断用户输入的用户名中是否包含除了小写字母之外字符(ASCII范围97~122)
bool Check_Username(string& email){
int pos = email.find("@");
string username = email.substr(0,pos-1);
for (string::iterator it = username.begin(); it != username.end(); it++){
if (*it < 97 || *it > 122){
return false;
}
}
return true;
}
// 3 判断用户输入的邮箱地址是否正确(alne@mavenir.cn)
bool Check_EqualtTo(string& email){
string rightEmail = "zhaosi@itcast.cn";
if (email.compare(rightEmail) != 0){
return false;
}
return true;
}
void testEmail(){
//用户邮箱地址验证
// 1 判断邮箱有效性 是否包含@和. 并且.在@之后
// 2 判断用户输入的用户名中是否包含除了小写字母之外字符(ASCII范围97~122)
// 3 判断用户输入的邮箱地址是否正确(alne@mavenir.cn)
string email;
cout << "请输入您的邮箱:" << endl;
cin >> email;
bool flag = Check_Valid(email);
if (!flag){
cout << "emain格式不合法!" << endl;
return;
}
flag = Check_Username(email);
if (!flag){
cout << "用户名中包含除小写字母之外的字母!" << endl;
return;
}
flag = Check_EqualtTo(email);
if (!flag){
cout << "邮箱地址不正确!" << endl;
return;
}
cout << "邮箱输入正确!" << endl;
}
int main(){
testEmail();
system("pause");
return EXIT_SUCCESS;
}
2.1.4 string使用注意事项
1、不要忽略string::size_type
类型,string::size_type
的类型是unsigned
类型,代码中通过宏判断程序编译的位数,从而决定是32还是64。另外string中使用find
频繁,所以建议单独去看看find的使用,熟能生巧。
2、关于char数组和string
char s[]="abcd";
这个是字符串,以'\0'
结束的,用strlen(s)
是4,'\0'
不计,但是它是有五个字符的
char s1[]={'a','b','c','d'};
这个是字符数组,没有'\0'
,用strlen(s1)
是不确定的,它有四个字符
char数组转换string
char ch[]="hello world!";
//1.向构造函数传入c字符串创建string对象:
string str(ch);
//2.使用拷贝构造函数创建string对象:
string str = ch;
//3.对已有的string对象调用string类内部定义的赋值运算符:
string str;
str = ch;
3、当函数使用string临时对象并返回时出现乱码
返回的是临时对象,外面要使用变量接住,否则马上就析构了。
#include<iostream>
#include <string>
using namespace std;
char* Fun(char* Char1, const char * Char2)
{
string str1 = Char1;
const string str2 = Char2;
str1 = str1 + str2;
return (const_cast<char*>(str1.c_str()));
}
int main(void)`在这里插入代码片`
{
char* CharStr1 = "ab";
const char* CharStr2 = "cd";
cout << Fun(CharStr1,CharStr2) << endl;
return 0 ;
}
4、string转char * 乱码问题
C++提供了两个函数:c_str()
和data()
当处理较短的string时,直接调用这两个函数没有出现问题,调用格式如下:
std::string str = "Hello Word";
char* p1 = str.c_str();
char* p2 = str.data();
当字符串比较长时,采用这两个函数,转换出来的char* 出现乱码,采用下边这种方式是完全没问题的:
char * strc = new char[strlen(s.c_str()) + 1];
strcpy_s(strc, s.length() + 1,s.c_str());
string转const char* ,可以尝试以下操作
const char *str;
str = _strdup(s.c_str());
示例代码:
#define _CRT_SECURE_NO_WARNINGS
#define STR "世界上第一种计算机高级语言是诞生于1954年的FORTRAN语言。之后出现了多种计算机高级语言。1970年,AT&T的Bell实验室的D.Ritchie和K.Thompson共同发明了C语言。研制C语言的初衷是用它编写UNIX系统程序,因此,它实际上是UNIX的副产品。它充分结合了汇编语言和高级语言的优点,高效而灵活,又容易移植。 1971年,瑞士联邦技术学院N.Wirth教授发明了Pascal语言。Pascal语言语法严谨,层次分明,程序易写,具有很强的可读性,是第一个结构化的编程语言。20世纪70年代中期,Bjarne Stroustrup在剑桥大学计算机中心工作。他使用过Simula和ALGOL,接触过C。他对Simula的类体系感受颇深,对ALGOL的结构也很有研究,深知运行效率的意义。既要编程简单、正确可靠,又要运行高效、可移植,是Bjarne Stroustrup的初衷。以C为背景,以Simula思想为基础,正好符合他的设想。1979年,Bjame Sgoustrup到了Bell实验室,开始从事将C改良为带类的C(C with classes)的工作。1983年该语言被正式命名为C++。自从C++被发明以来,它经历了3次主要的修订,每一次修订都为C++增加了新的特征并作了一些修改。第一次修订是在1985年,第二次修订是在1990年,而第三次修订发生在c++的标准化过程中。在20世纪90年代早期,人们开始为C++建立一个标准,并成立了一个ANSI和ISO(Intemational Standards Organization)国际标准化组织的联合标准化委员会。该委员会在1994年1月25曰提出了第一个标准化草案。在这个草案中,委员会在保持Stroustrup最初定义的所有特征的同时,还增加了一些新的特征。在完成C++标准化的第一个草案后不久,发生了一件事情使得C++标准被极大地扩展了:Alexander stepanov创建了标准模板库(Standard Template Library,STL)。STL不仅功能强大,同时非常优雅,然而,它也是非常庞大的。在通过了第一个草案之后,委员会投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出了C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,但也因此延缓了C++标准化的进程。委员会于1997年11月14日通过了该标准的最终草案,1998年,C++的ANSI / IS0标准被投入使用。通常,这个版本的C++被认为是标准C++。所有的主流C++编译器都支持这个版本的C++,包括微软的Visual C++和Borland公司的C++Builder。"
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
using namespace std;
int main()
{
const string s = STR;
const char* p1 = s.c_str();
const char* p2 = s.data();
cout << p1 << endl << endl;
cout << p2 << endl << endl;
const char *str;
str = _strdup(s.c_str());
cout << str <<endl << endl << endl;
char * strc = new char[strlen(s.c_str()) + 1];
strcpy_s(strc, s.length() + 1,s.c_str());
cout << strc << endl << endl;
return 0;
}
5、cctype中一些常用的字符函数
2.2 vector容器
2.2.1 vector特性
vector容器是一个长度动态改变的动态数组,既然也是数组,那么其内存是一段连续的内存,具有数组的随机存取的优点。
vector是动态数组,连续内存空间,具有随机存取效率高的优点。
vector是单口容器,在队尾插入和删除元素效率高,在指定位置插入会导致数据元素移动,效率低。
vector如何实现动态增长?
答: 当vector空间满的时候,再当插入新元素的时候,vector会重新申请一块更大的内存空间,将原空间数据拷贝到新的内存空间,然后释放旧的内存空间,再将新元素插入到新空间中,以此可以看出vector的空间动态增长效率较低。
动态增长是怎样的?增长多大?
答: 当添加元素时,如果vector空间大小不足,则会以原大小的两倍另外配置一块较大的新空间,然后将原空间内容拷贝过来,在新空间的内容末尾添加元素,并释放原空间。vector的空间动态增加大小,并不是在原空间之后的相邻地址增加新空间,因为vector的空间是线性连续分配的,不能保证原空间之后有可供配置的空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就会失效。
2.2.2 vector常用API
2.2.2.1 vector构造函数及其初始化
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将n个elem拷贝给本身。
vector(const vector &vec);//拷贝构造函数。
vector<int> a(10); //定义了10个整型元素的向量(尖括号中为元素类型名,它可以是任何合法的数据类型),但没有给出初值,其值是不确定的。
vector<int> a(10,1); //定义了10个整型元素的向量,且给出每个元素的初值为1
vector<int> a(b); //用b向量来创建a向量,整体复制性赋值
vector<int> a(b.begin(),b.begin+3); //定义了a值为b中第0个到第2个(共3个)元素
int b[7]={1,2,3,4,5,9,8};
vector<int> a(b,b+7); //从数组中获得初值
2.2.2.1 vector赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将vec与本身的元素互换。
//第一个赋值函数,可以这么写:
int arr[] = { 0, 1, 2, 3, 4 };
assign(arr, arr + 5);//使用数组初始化vector
2.2.2.1 vector大小操作
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。
2.2.2.1 vector数据存取操作
at(int idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。
operator[];//返回索引idx所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素
2.2.2.1 vector插入和删除
insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele.
push_back(ele); //尾部插入元素ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素
例子
vec.insert(vec.begin()+i,a);//在第i+1个元素前面插入a
vec.erase(vec.begin()+2);//删除第3个元素
vec.erase(vec.begin()+i,vec.end()+j);//删除区间[i,j-1];区间从0开始
注意:
end()指向的是最后一个元素的下一个位置,所以访问最后一个元素的正确操作为:v.end() - 1
2.2.3 vector练习
删除数组中的奇数
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (vector<int>::iterator it = a.begin(); it != a.end();)
{
if ((*it) % 2 == 1)
it = a.erase(it);
else
it++;
}
for (vector<int>::iterator it = a.begin(); it != a.end(); it++)
cout << *it;
return 0;
}
2.2.4 vector使用注意事项
1.reserve和resize的区别
答:reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
而resize在开空间的同时还会进行初始化,会影响size。
2.vector迭代器失效
insert(可能会增容)或erase之后,会引发迭代器失效 解决办法:重置迭代器,让迭代器指向新的有效的空间
解决erase迭代器失效的办法:迭代器在设置的时候,有一个返回值,它会返回最近删除位置下一个位置的有效迭代器
2.1当插入(push_back)一个元素后,end操作返回的迭代器肯定失效。
2.2当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时first和end操作返回的迭代器都会失效。不增容不会导致迭代器失效。
2.3当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效
2.2.5 vector总结
总结: vector是个动态数组,当空间不足的时候插入新元素,vector会重新申请一块更大的内存空间,将旧空间数据拷贝到新空间,然后释放旧空间。vector是单口容器,所以在尾端插入和删除元素效率较高,在指定位置插入,势必会引起数据元素移动,效率较低。
2.3 deque容器
2.3.1 deque特性
deque是“double-ended queue”的缩写,和vector一样,deque也支持随机存取。vector是单向开口的连续性空间,deque则是一种双向开口的连续性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,vector当然也可以在头尾两端进行插入和删除操作,但是头部插入和删除操作效率奇差,无法被接受。
deque和vector的最大差异是
一在于deque允许常数时间内对头端进行元素插入和删除操作。
二在于deque没有容量的概念,因为它是动态的以分段的连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样“因旧空间不足而重新分配一块更大的空间,然后再复制元素,释放空间”这样的操作不会发生在deque身上,也因此deque没有必要提供所谓的空间保留功能。
特点:
1、双端插入和删除元素效率较高.
2、指定位置插入也会导致数据元素移动,降低效率.
3、可随机存取,效率高.
2.3.2 deque常用API
2.3.2.1 deque构造函数
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
2.3.2.2 deque赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将deq与本身的元素互换
#include <iostream>
#include <deque>
int main()
{
std::deque<int> first;
std::deque<int> second;
std::deque<int> third;
first.assign(7, 100); // 7 ints with a value of 100
std::deque<int>::iterator it;
it = first.begin() + 1;
second.assign(it, first.end() - 1); // the 5 central values of first
int myints[] = { 1776,7,4 };
third.assign(myints, myints + 3); // assigning from array.
std::cout << "Size of first: " << int(first.size()) << '\n';
std::cout << "Size of second: " << int(second.size()) << '\n';
std::cout << "Size of third: " << int(third.size()) << '\n';
return 0;
}
2.3.2.3 deque大小操作
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
2.3.2.4 deque双端插入和删除操作
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据
2.3.2.5 deque数据存取
at(idx);//返回索引idx所指的数据,如果idx越界,抛出out_of_range。
operator[];//返回索引idx所指的数据,如果idx越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据
2.3.2.6 deque插入操作
insert(pos,elem);//在pos位置插入一个elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
经验之谈 : deque是分段连续的内存空间,通过中控器维持一种连续内存空间的状态,其实现复杂性要大于vector queue stack等容器,其迭代器的实现也更加复杂,在需要对deque容器元素进行排序的时候,建议先将deque容器中数据数据元素拷贝到vector容器中,对vector进行排序,然后再将排序完成的数据拷贝回deque容器。
2.3.2.7 deque删除操作
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
2.3.5 deque使用注意事项
1、不遍历deque
deque 是由一段一段的定量的连续空间构成。一旦有必要在 deque 前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在 deque 的头端或者尾端。deque 最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。
因此在需要遍历时,不建议使用它。
2、deque迭代器失效
成员函数push_back():会直接在容器末尾添加一个元素。原迭代器中end()会失效,其他的都不会失效。
成员函数push_front():会直接在容器头部添加一个元素。原迭代器中begin()会失效,其他的都不会失效。
成员函数pop_back():会直接删除最后一个元素。原迭代器中end()会失效,其他的都不会失效
成员函数pop_front():会直接在容器头部删除一个元素。原迭代器中begin()会失效,其他的都不会失效。
成员函数insert(iterator, n):如果插入点之前的元素较少,会在容器头部插入一个元素,然后将插入点及其之前的所有元素向前移动一位,再在插入点创建新元素。否则,将插入点及其之后的元素向后移动一位,再在插入点创建新元素。因此,向前移动则导致原迭代器中插入点及插入点之前的迭代器都失效;向后移动则导致迭代器中插入点及插入点之后的迭代器都失效。
成员函数erase(iterator):如果删除点之前的元素较少,将删除点之前的所有元素向后移动一位,再删除第一个元素。否则,将删除点之后的所有元素向前移动一位,再删除最后一个元素。因此,向前移动将导致原迭代器中删除点及删除点之后的迭代器失效;向后移动将导致原迭代器中删除点及删除点之前的迭代器都失效
原文链接:https://blog.csdn.net/xp178171640/article/details/104905338
2.3.6 deque总结
deque它支持高效的在其首部插入和删除元素。
最后关于vector、list、deque三个容器使用选择的一些准则
如果我们需要随机访问一个容器,则vector要比List好得多。
如果我们一直要存储元素的个数,则vector又是一个比list好的选择。
如果我们需要的不只是在容器两端插入和删除元素,则list显然比vector好。
除非我们需要在容其首部插入和删除元素,否则vector要比deque好。
如果只需要在读取输入时在容器的中间位置插入元素,然后需要随机访问元素,则可考虑输入时将元素读入到一个List容器,然后排序,然后将排序后的list容器复制到一个vector容器中
2.4 stack容器
2.4.1 stack特性
stack是一种先进后出(first in last out,FILO)的数据结构,它只有一个出口,stack只允许在栈顶新增元素,移除元素,获得顶端元素,但是除了顶端之外,其他地方不允许存取元素,只有栈顶元素可以被外界使用,也就是说stack不具有遍历行为,没有迭代器。
因此,栈不能遍历,不支持随机存取,只能通过top从栈顶获取和删除元素。
2.4.2 stack常用API
2.4.2.1 stack构造函数
stack<T> stkT;//stack采用模板类实现, stack对象的默认构造形式:
stack(const stack &stk);//拷贝构造函数
2.4.2.2 stack赋值操作
stack& operator=(const stack &stk);//重载等号操作符
2.4.2.3 stack数据存取操作
push(elem);//向栈顶添加元素
pop();//从栈顶移除第一个元素
top();//返回栈顶元素
2.4.2.3 stack大小操作
empty();//判断堆栈是否为空
size();//返回堆栈的大小
2.4.3 stack注意事项
在使用pop()和top()函数之前必须先使用empty()函数判断栈是否为空。
2.4.4 stack总结
算法笔记中这样提到:stack用来模拟实现一些递归,防止程序对栈内存的限制而导致程序运行出错。一般来说,程序的栈内存空间很小,对有些题目来说,如果用普通的函数来进行递归,一旦递归层数过深(不同机器不同,约几千至几万层),则会导致程序运行崩溃。如果用栈来模拟递归算法的实现,则可以避免这一方面的问题(不过这种应用出现较少)。
2.5 queue
2.5.1 queue特性
queue是一种先进先出(first in first out, FIFO)的数据类型,他有两个口,数据元素只能从一个口进,从另一个口出.队列只允许从队尾加入元素,队头删除元素,必须符合先进先出的原则,queue和stack一样不具有遍历行为。
特点:
1、必须从一个口数据元素入队,另一个口数据元素出队。
2、不能随机存取,不支持遍历
2.5.2 queue常用API
2.5.2.1 queue构造函数
queue<T> queT;//queue采用模板类实现,queue对象的默认构造形式:
queue(const queue &que);//拷贝构造函数
2.5.2.2 queue存取、插入和删除操作
push(elem);//往队尾添加元素
pop();//从队头移除第一个元素
back();//返回最后一个元素
front();//返回第一个元素
2.5.2.3 queue赋值操作
queue& operator=(const queue &que);//重载等号操作符
2.5.2.4 queue大小操作
empty();//判断队列是否为空
size();//返回队列的大小
2.5.3 queue使用注意事项
1、优先队列
priority_queue 对于基本类型的使用方法相对简单。他的模板声明带有三个参数:
priority_queue<Type, Container, Functional>
其中Type 为数据类型, Container 为保存数据的容器,Functional 为元素比较方式。
Container 必须是用数组实现的容器,比如 vector, deque 但不能用 list.STL里面默认用的是 vector. 比较方式默认用 operator< , 所以如果你把后面俩个参数缺省的话,优先队列就是大顶堆,队头元素最大。
#include <iostream>
#include <queue>
using namespace std;
int main(){
priority_queue<int> q;
for( int i= 0; i< 10; ++i ) q.push( rand() );
while( !q.empty() ){
cout << q.top() << endl;
q.pop();
}
getchar();
return 0;
}
如果要用到小顶堆,则一般要把模板的三个参数都带进去。
STL里面定义了一个仿函数 greater<>
,对于基本类型可以用这个仿函数声明小顶堆
#include <iostream>
#include <queue>
using namespace std;
int main(){
priority_queue<int, vector<int>, greater<int> > q;
for( int i= 0; i< 10; ++i ) q.push( rand() );
while( !q.empty() ){
cout << q.top() << endl;
q.pop();
}
getchar();
return 0;
}
对于自定义类型,则必须自己重载 operator< 或者自己写仿函数
#include <iostream>
#include <queue>
using namespace std;
struct Node{
int x, y;
Node( int a= 0, int b= 0 ):
x(a), y(b) {}
};
bool operator<( Node a, Node b ){
if( a.x== b.x ) return a.y> b.y;
return a.x> b.x;
}
int main(){
priority_queue<Node> q;
for( int i= 0; i< 10; ++i )
q.push( Node( rand(), rand() ) );
while( !q.empty() ){
cout << q.top().x << ' ' << q.top().y << endl;
q.pop();
}
getchar();
return 0;
}
2.5.4 queue总结
共同点:都是只允许在端点处插入和删除元素的数据结构;
不同点:栈是仅在栈顶进行访问,遵循后进先出的原则(LIFO);队列是在队尾插入数据,在队头删除数据(FIFO)
2.6 list
2.6.1 list特性
对于链表,我们应该再熟悉不过了。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
特点:
采用动态存储分配,不会造成内存浪费和溢出
链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
链表灵活,但是空间和时间额外耗费较大
2.6.2 list常用API
2.6.2.1 list构造函数
list<T> lstT;//list采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将n个elem拷贝给本身。
list(const list &lst);//拷贝构造函数。
2.6.2.2 list数据元素插入和删除操作
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。
2.6.2.3 list大小操作
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为num,
若容器变长,则以默认值填充新位置。
如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem);//重新指定容器的长度为num,
若容器变长,则以elem值填充新位置。
如果容器变短,则末尾超出容器长度的元素被删除。
2.6.2.4 list赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将lst与本身的元素互换。
2.6.2.5 list数据的存取
front();//返回第一个元素。
back();//返回最后一个元素。
2.6.2.6 list反转排列排序
reverse();//反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
sort(); //list排序
2.6.3 list使用注意事项
1、list使用迭代器删除元素
#include <iostream>
#include <list>
using namespace std;
int main(int argc, char* argv[])
{
std::list<int> mList;
mList.push_back(1);
mList.push_back(2);
mList.push_back(0);
mList.push_back(3);
mList.push_back(4);
mList.push_back(0);
mList.push_back(0);
mList.push_back(6);
mList.push_back(0);
mList.push_back(0);
std::list<int>::iterator iter = mList.begin();
for (; iter != mList.end(); )
{
if (0 != *iter)
{
++iter;
}
else
{
mList.erase(iter++);
}
}
iter = mList.begin();
for (; iter != mList.end(); ++iter)
{
std::cout << " " << *iter << " ";
}
return 0;
}
- 对于list中只有元素的删除操作会导致指向该元素的迭代器失效,其他元素迭代器不受影响。
- 对于vector元素的删除、插入操作会导致指向该元素以及后面的元素的迭代器失效。
2、链表和数组的区别:
- 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
- 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据元素。(数组中插入、删除数据项时,需要移动其它数据项)
2.6.4 list总结
1、list是可以在常数范围类任意位置进行插入和删除的序列式容器,可以前后双向迭代
2、list的底层是双向带头循环链表
3、list个forword_list类似,最大的不同是forword只能向前迭代。
4、list在任意位置插入删除的效率较高
5、list不支持随机访问
2.7 set/multiset容器
2.7.1 set/multiset特性
set/multiset的特性是所有元素会根据元素的值自动进行排序。set是以RB-tree(红黑树,平衡二叉树的一种)为底层机制,其查找效率非常好。set容器中不允许重复元素,multiset允许重复元素。
通过红黑树实现的set能够在log2n 的时间内完成插入、查找、删除的数据结构。
补充概念:
1、二叉树就是任何节点最多只允许有两个字节点。分别是左子结点和右子节点。
2、二叉搜索树,是指二叉树中的节点按照一定的规则进行排序,使得对二叉树中元素访问更加高效。二叉搜索树的放置规则是:任何节点的元素值一定大于其左子树中的每一个节点的元素值,并且小于其右子树的值。因此从根节点一直向左走,一直到无路可走,即得到最小值,一直向右走,直至无路可走,可得到最大值。那么在儿茶搜索树中找到最大元素和最小元素是非常简单的事情。
3、平衡二叉树
-
是「二叉排序树」
二叉排序树又称二叉排序树。它或者是一个空树,或者是一个具有下列性质的二叉树:
若它的左子树不空,则左子树上所有节点的值均小于它的根结构的值
若它的右子树不空,则右子树上所有结点的值均大于它的根节点的值
它的左、右子树也分别是二叉排序树 -
任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)
2.7.2 set 常用API
2.7.2.1 set构造函数
set<T> st;//set默认构造函数:
mulitset<T> mst; //multiset默认构造函数:
set(const set &st);//拷贝构造函数
2.7.2.2 set赋值操作
set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器
2.7.2.3 set大小操作
size();//返回容器中元素的数目
empty();//判断容器是否为空
2.7.2.4 set插入和删除操作
insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为elem的元素。
2.7.2.5 set查找操作
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
2.7.3 set使用注意事项
1、set升序与降序
#include <iostream>
#include <set>
using namespace std;
struct myfunc
{
bool operator()(int v1,int v2) const
{
return v1 > v2;
}
};
int main()
{
//set升序排列
set<int> Tmp;
//初始化
Tmp.insert(8);
Tmp.insert(10);
Tmp.insert(100);
Tmp.insert(9);
Tmp.insert(20);
Tmp.insert(3);
for (set<int>::iterator it = Tmp.begin(); it != Tmp.end(); ++it)
{
cout << "升序排列:" << *it << endl;
}
//set降序排列
set<int, myfunc> setTmp;
//初始化
setTmp.insert(8);
setTmp.insert(10);
setTmp.insert(100);
setTmp.insert(9);
setTmp.insert(20);
setTmp.insert(3);
for (set<int,myfunc>::iterator it = setTmp.begin(); it != setTmp.end(); ++it)
{
cout << "降序排列:" << *it << endl;
}
}
2、我们不可以通过set的迭代器改变元素的值,因为set集合是根据元素值进行排序,关系到set的排序规则,如果任意改变set的元素值,会严重破坏set组织。
3、set删除操作
S.erase(key); //删除一个值,如果没有就无操作
S.erase(S.find(key));//删除迭代器代表的值,若果没有,就会删除S.end(),会导致在遍历set的时候无法终止。
S.erase(S.lower_bound(key1), S.lower_bound(key2));//删除[key1,key2)之内的所有值。
2.8 map/multimap容器
2.8.1 map/multimap特性
map相对于set区别,map具有键值和实值,所有元素根据键值自动排序。pair的第一元素被称为键值,第二元素被称为实值。map也是以红黑树为底层实现机制。
2.8.2 对组
对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用pair的两个公有函数first和second访问。
类模板:template <class T1, class T2> struct pair
.
创建对组方式
//第一种方法创建一个对组
pair<string, int> pair1(string("name"), 20);
cout << pair1.first << endl; //访问pair第一个值
cout << pair1.second << endl;//访问pair第二个值
//第二种
pair<string, int> pair2 = make_pair("name", 30);
cout << pair2.first << endl;
cout << pair2.second << endl;
//pair=赋值
pair<string, int> pair3 = pair2;
cout << pair3.first << endl;
cout << pair3.second << endl;
2.8.3 map常用API
2.8.3.1 map构造函数
map<T1, T2> mapTT;//map默认构造函数:
map(const map &mp);//拷贝构造函数
初始化
typedef std::map<std::string,std::string> stringmap;
stringmap first; // empty
stringmap second ( {{"apple","red"},{"lemon","yellow"}} ); // init list
stringmap third ( {{"orange","orange"},{"strawberry","red"}} ); // init list
stringmap fourth (second); // copy
stringmap fifth (merge(third,fourth)); // move
stringmap sixth (fifth.begin(),fifth.end()); // range
2.8.3.2 map赋值操作
map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器
2.8.3.3 map大小操作
size();//返回容器中元素的数目
empty();//判断容器是否为空
2.8.3.4 map插入数据元素操作
map.insert(...); //往容器插入元素,返回pair<iterator,bool>
map<int, string> mapStu;
// 第一种 通过pair的方式插入对象
mapStu.insert(pair<int, string>(3, "james"));
// 第二种 通过pair的方式插入对象
mapStu.inset(make_pair(-1, "wade"));
// 第三种 通过value_type的方式插入对象
mapStu.insert(map<int, string>::value_type(1, "paul"));
// 第四种 通过数组的方式插入值
mapStu[3] = "kobe";
mapStu[5] = "jordan";
注意:
前三种方法,采用的是insert()方法,该方法返回值为pair<iterator,bool>
第四种方法非常直观,但存在一个性能的问题。插入3时,先在mapStu中查找主键为3的项,若没发现,则将一个键为3,值为初始化值的对组插入到mapStu中,然后再将值修改成“小刘”。若发现已存在3这个键,则修改这个键对应的value。
string strName = mapStu[2];
//取操作或插入操作
只有当mapStu存在2这个键时才是正确的取操作,否则会自动插入一个实例, 键为2,值为初始化值。
2.8.3.5 map删除操作
clear();//删除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中key为keyElem的对组。
2.8.3.6 map查找操作
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回map.end();
count(keyElem);//返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1。对multimap来说,值可能大于1。
lower_bound(keyElem);//返回第一个key<=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
2.8.4 map使用注意事项
1、operator[]的语义导致的问题
例如定义 map<string, mystruct*> map_sample
mystruct
为自定义类型。
通过操作符[], 我们可以像数组一样访问map中的成员, 非常的方便, 如:
mystruct *ptr = map_sample["alen"];
当键alen存在不会导致问题,但是当键 ‘alen’在当前map_sample中不存在时,操作符[]会自动在map_sample中添加键alen,并且将对应的值初始化缺省值(这里将导致值被初始化为NULL)。
这个特性在某些应用场景中可能会带来问题,如果值mystruct*每次都是malloc,这样在另外任务中通过[]访问后直接使用,碰到上面这种不存在情况时将导致访问空指针或者在调用free时导致程序core dump。
看到这里你会说,那是因为你使用[]后没有判断指针是否为NULL导致的,不是[]的问题,但是成员’alen’是实实在在的被添加了进去了,在你发现返回指针为NULL时,是不是要再去erase掉这个值为空的键值对呢?所以如果你的程序不能确定某个key是不是存在,建议不要使用操作符’[]', 而是通过使用接口find判断后再访问,可以避免出现上述问题。
2、通过map的迭代器不可以修改map的键值,键值关系到容器内元素的排列规则,任意改变键值会破坏容器的排列规则,但是你可以改变实值。
2.8.4 map总结
1、map与unordered_map相比:
map底层实现为红黑数,undered_map底层实现为哈希表,两者均不能有重复的建,均支持[]运算符
2、map与multimap相比:
两者底层实现均为红黑树,map不允许相同key值存在,但是multimap支持重复的键,不支持[]运算符。
2.9 STL容器共性机制
STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素再另行拷贝一份放入到容器中,而不是将原数据元素直接放进容器中,也就是说我们提供的元素必须能够被拷贝。
除了queue和stack之外,每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素。
通常STL不会抛出异常,需要使用者传入正确参数。
每个容器都提供了一个默认的构造函数和默认的拷贝构造函数。
大小相关的构造方法: 1 size()返回容器中元素的个数 2 empty()判断容器是否为空
2.10 STL容器使用时机
vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。
vector与deque的比较:
一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。
二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
三:deque支持头部的快速插入与快速移除,这是deque的优点。
list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。
更多容器的特点及时间复杂度对比参考
https://blog.csdn.net/qq_39382769/article/details/102441699
第三章 常用算法
3.1 内建函数对象
STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。使用内建函数对象,需要引入头文件 #include。
6个算数类函数对象,除了negate是一元运算,其他都是二元运算。
template<class T> T plus<T>//加法仿函数
template<class T> T minute<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>//逻辑非
使用例子:
//使用内建函数对象声明一个对象
plus<int> myPlus;
cout << myPlus(5, 3) << endl;
//使用匿名临时对象
cout << plus<int>()(5, 6) << endl;
sort排序使用预定义函数对象进行排序。
count_if equal_to 参数绑定
3.2 算法概述
算法主要是由头文件<algorithm> <functional> <numeric>
组成。
<algorithm>
是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等…
<numeric>
体积很小,只包括在几个序列容器上进行的简单运算的模板函数.
<functional>
定义了一些模板类,用以声明函数对象。
STL算法分为:质变算法和非质变算法。
所有的STL算法都作用在由迭代器[first,end)所标示出来的区间上,所谓质变算法,是指运算过程中会改变区间内的(迭代器所指)的元素内容。比如,拷贝(copy)、互换(swap)、替换(replace)、填写(fill)、删除(remove)、排序(sort)等算法都属于此类。
非质变算法是指是指在运算过程中不会区间内(迭代器所指)的元素内容,比如查找(find)、计数(count)、遍历(for_each)、寻找极值(max,min)等,都属于此类。但是如果你在for_each遍历每个元素的时候试图应用一个会改变元素内容的仿函数,那么元素当然也会改变。
3.3 常用遍历算法
/*
遍历算法 遍历容器元素
@param beg 开始迭代器
@param end 结束迭代器
@param _callback 函数回调或者函数对象
@return 函数对象
*/
for_each(iterator beg, iterator end, _callback);
/*
transform算法 将指定容器区间元素搬运到另一容器中
注意 : transform 不会给目标容器分配内存,所以需要我们提前分配好内存
@param beg1 源容器开始迭代器
@param end1 源容器结束迭代器
@param beg2 目标容器开始迭代器
@param _cakkback 回调函数或者函数对象
@return 返回目标容器迭代器
*/
transform(iterator beg1, iterator end1, iterator beg2, _callbakc)
3.3.1 for_each:
基本正向遍历和逆向遍历
for_each绑定参数输出
for_each修改容器元素
for_each返回值
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<algorithm>
#include<functional>
using namespace std;
//for_each正向遍历 反向遍历
struct print01{
void operator()(int v){
cout << v << " ";
}
};
void test01(){
vector<int> v;
for (int i = 0; i < 10;i++){
v.push_back(rand() % 100);
}
//正向遍历
for_each(v.begin(), v.end(), print01());
cout << endl;
//反向遍历
for_each(v.rbegin(), v.rend(), print01());
cout << endl;
}
//for_each算法 绑定参数
//将容器中的元素加上100 再输出
struct print2 : public binary_function<int,int,void>{
void operator()(int v1,int v2) const{
cout << v1+v2 << " ";
}
};
void print21(int v1, int v2){
cout << v1 + v2 << " ";
}
void test02(){
vector<int> v;
for (int i = 0; i < 10; i++){
v.push_back(rand() % 100);
}
for_each(v.begin(), v.end(), print01());
cout << endl;
//函数对象做参数
for_each(v.begin(), v.end(), bind2nd(print2(), 100));
cout << endl;
//普通回调函数做参数,并且绑定参数
for_each(v.begin(), v.end(), bind2nd(ptr_fun(print21), 100));
cout << endl;
}
//for_each修改元素值
struct print3 {
void operator()(int& v1) const{
v1 = v1 + 100;
cout << v1 << " ";
}
};
void test03(){
vector<int> v;
for (int i = 0; i < 10; i++){
v.push_back(rand() % 100);
}
for_each(v.begin(), v.end(), print01());
cout << endl;
for_each(v.begin(), v.end(), print3());
cout << endl;
for_each(v.begin(), v.end(), print01());
cout << endl;
}
//for_each返回值
struct print4 {
print4() :count(0){}
void operator()(int v1){
count++;
cout << v1 << " ";
}
int count;
};
void test04(){
vector<int> v;
for (int i = 0; i < 10; i++){
v.push_back(rand() % 100);
}
print4 temp1;
print4 temp2 = for_each(v.begin(), v.end(), temp1);
cout << endl;
cout << "temp1:" << temp1.count << endl;
cout << "temp2:" << temp2.count << endl;
}
int main(){
//test01();
test02();
test03();
test04();
system("pause");
return EXIT_SUCCESS;
}
3.3.2 transform:
从一个容器经过处理搬运到另一个容器:
两个容器数据处理搬运到第三个容器
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<algorithm>
#include<functional>
using namespace std;
//容器中元素加10 搬运到另一容器中
void print1(int v){
cout << v << " ";
}
struct myplus01{
int operator()(int v1){
return v1 + 100;
}
};
void test01(){
vector<int> v,dest;
for (int i = 0; i < 10; i++){
v.push_back(rand() % 100);
}
for_each(v.begin(), v.end(), print1);
cout << endl;
//首先给dest开辟足够内存
dest.resize(v.size());
//搬运元素
/*
template<class _InIt,
class _OutIt,
class _Fn1> inline
_OutIt _Transform(_InIt _First, _InIt _Last,
_OutIt _Dest, _Fn1 _Func)
{ // transform [_First, _Last) with _Func
for (; _First != _Last; ++_First, ++_Dest)
*_Dest = _Func(*_First);
return (_Dest);
}
*/
transform(v.begin(), v.end(), dest.begin(), myplus01());
for_each(dest.begin(), dest.end(), print1);
cout << endl;
}
//容器1的元素 + 容器2的元素 搬运到 第三个容器中
struct myplus02{
int operator()(int v1,int v2){
return v1 + v2;
}
};
void test02(){
vector<int> v1,v2, dest;
for (int i = 0; i < 10; i++){
v1.push_back(i);
v2.push_back(i + 1);
}
for_each(v1.begin(), v1.end(), print1);
cout << endl;
/*
template<class _InIt1,
class _InIt2,
class _OutIt,
class _Fn2> inline
_OutIt transform(_InIt1 _First1, _InIt1 _Last1,
_InIt2 _First2, _OutIt _Dest, _Fn2 _Func)
{ // transform [_First1, _Last1) and [_First2, ...) with _Func
_DEBUG_RANGE(_First1, _Last1);
_DEBUG_POINTER(_Dest);
_DEBUG_POINTER(_Func);
if (_First1 != _Last1)
return (_Transform2(_Unchecked(_First1), _Unchecked(_Last1),
_First2, _Dest, _Func,
_Is_checked(_Dest)));
return (_Dest);
}
template<class _InIt1,
class _InIt2,
class _OutIt,
class _Fn2> inline
_OutIt _Transform(_InIt1 _First1, _InIt1 _Last1,
_InIt2 _First2, _OutIt _Dest, _Fn2 _Func)
{ // transform [_First1, _Last1) and [_First2, ...) with _Func
for (; _First1 != _Last1; ++_First1, ++_First2, ++_Dest)
*_Dest = _Func(*_First1, *_First2);
return (_Dest);
}
*/
dest.resize(v1.size());
transform(v1.begin(), v1.end(), v2.begin(), dest.begin(), myplus02());
for_each(dest.begin(), dest.end(), print1);
cout << endl;
}
int main(){
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
3.4 常用查找算法
/*
find算法 查找元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return 返回查找元素的位置
*/
find(iterator beg, iterator end, value)
/*
adjacent_find算法 查找相邻重复元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param _callback 回调函数或者谓词(返回bool类型的函数对象)
@return 返回相邻元素的第一个位置的迭代器
*/
adjacent_find(iterator beg, iterator end, _callback);
/*
binary_search算法 二分查找法
注意: 在无序序列中不可用
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return bool 查找返回true 否则false
*/
bool binary_search(iterator beg, iterator end, value);
/*
find_if算法 条件查找
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回bool类型的函数对象)
@return bool 查找返回true 否则false
*/
find_if(iterator beg, iterator end, _callback);
/*
count算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value回调函数或者谓词(返回bool类型的函数对象)
@return int返回元素个数
*/
count(iterator beg, iterator end, value);
/*
count算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回bool类型的函数对象)
@return int返回元素个数
*/
count_if(iterator beg, iterator end, _callback);
3.4.1 find算法案例
find查找基本数据类型,类对象,类指针:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<algorithm>
#include<functional>
using namespace std;
//find 算法
void test01(){
int arr[] = {5,2,8,9,1,3};
vector<int> v(arr, arr + sizeof(arr) / sizeof(int));
/*
template<class _InIt,
class _Ty> inline
_InIt find(_InIt _First, _InIt _Last, const _Ty& _Val)
{ // find first matching _Val
_DEBUG_RANGE(_First, _Last);
return (_Rechecked(_First,
_Find(_Unchecked(_First), _Unchecked(_Last), _Val)));
}
*/
//这里注意find返回值 如果没有找到 返回 v.end()
vector<int>::iterator it = find(v.begin(),v.end(),1);
//可以这样判断是否找到元素
if (it == v.end()){
cout << "没有找到!" << endl;
}
else{
cout << *it << endl;
}
}
//find查找对象
class sutdent{
public:
sutdent(int age, int salary) :age(age), salary(salary){}
int age;
int salary;
bool operator==(const sutdent& stu){
if (this->age == stu.age && this->salary == stu.salary){
return true;
}
else{
return false;
}
}
};
void test02(){
//对象查找 重载==操作符
sutdent s1(1, 2), s2(3, 4), s3(5, 6);
vector<sutdent> vs;
vs.push_back(s1);
vs.push_back(s2);
vs.push_back(s3);
vector<sutdent>::iterator its = find(vs.begin(), vs.end(), s2);
if (its == vs.end()){
cout << "s2没有找到!" << endl;
}
else{
cout << "s2找到!" << endl;
}
}
int main(){
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
3.5 常用排序算法
/*
merge算法 容器元素合并,并存储到另一容器中
@param beg1 容器1开始迭代器
@param end1 容器1结束迭代器
@param beg2 容器2开始迭代器
@param end2 容器2结束迭代器
@param dest 目标容器开始迭代器
*/
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
/*
sort算法 容器元素排序
注意:两个容器必须是有序的
@param beg 容器1开始迭代器
@param end 容器1结束迭代器
@param _callback 回调函数或者谓词(返回bool类型的函数对象)
*/
sort(iterator beg, iterator end, _callback)
/*
sort算法 对指定范围内的元素随机调整次序
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/
random_shuffle(iterator beg, iterator end)
/*
reverse算法 反转指定范围的元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/
reverse(iterator beg, iterator end)
3.6 常用拷贝和替换算法
/*
copy算法 将容器内指定范围的元素拷贝到另一容器中
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param dest 目标容器结束迭代器
*/
copy(iterator beg, iterator end, iterator dest)
/*
replace算法 将容器内指定范围的旧元素修改为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param oldvalue 旧元素
@param oldvalue 新元素
*/
replace(iterator beg, iterator end, oldvalue, newvalue)
/*
replace_if算法 将容器内指定范围满足条件的元素替换为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback函数回调或者谓词(返回Bool类型的函数对象)
@param oldvalue 新元素
*/
replace_if(iterator beg, iterator end, _callback, newvalue)
/*
swap算法 互换两个容器的元素
@param c1容器1
@param c2容器2
*/
swap(container c1, container c2)
3.7 常用算数生成算法
/*
accumulate算法 计算容器元素累计总和
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value累加值
*/
accumulate(iterator beg, iterator end, value)
/*
fill算法 向容器中添加元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value t填充元素
*/
fill(iterator beg, iterator end, value)
3.8 常用集合算法
/*
set_intersection算法 求两个set集合的交集
注意:两个集合必须是有序序列
@param beg1 容器1开始迭代器
@param end1 容器1结束迭代器
@param beg2 容器2开始迭代器
@param end2 容器2结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
/*
set_union算法 求两个set集合的并集
注意:两个集合必须是有序序列
@param beg1 容器1开始迭代器
@param end1 容器1结束迭代器
@param beg2 容器2开始迭代器
@param end2 容器2结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
/*
set_difference算法 求两个set集合的差集
注意:两个集合必须是有序序列
@param beg1 容器1开始迭代器
@param end1 容器1结束迭代器
@param beg2 容器2开始迭代器
@param end2 容器2结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)