C++与STL入门(未完)

写在前面

本文是我在C语言的基础上学习C++时的总结,学习的参考书籍是刘汝佳《算法竞赛入门经典》(第二版)。该书”C++于STL入门“章节讲解了在算法竞赛中常用的一些C++特性,因此书中只是简略的介绍了一些重要的知识点。我也是围绕着书中所介绍的内容进行学习,对书中提及但没能详细讲解的地方进行了一些补充和深入,因此在这里写一篇博文进行总结。

C++的特性

C++与C语言最大的不同是,C语言是面向过程的语言,而C++是面向对象的语言。面向过程编程(OPP)是一种以过程为中心的编程方法,它在解决问题的时候专注于发生的问题、解决的方法以及处理的步骤。而面对对象编程(OOP)是以事物为中心的编程思想,它在于将问题所涉及的对象、对象的行为等进行抽象。抽象的目的在于更好的描述问题,从而能够更好的分析问题和解决问题。

面向过程思想解决问题采取的策略一般为自顶向下步步深入,首先将问题模块化,分解成若干的小问题,然后将问题逐个解决。因此面向过程编程主要的思想是模块化思想。实际上,当问题规模较小时,可以通过分析出问题解决的流程,根据流程进行模块化编程,从而还具有一定的优势。

面向对象思想首先对事物进行对象化,对象还具有独特的属性和方法。于是对于大型问题的分析和描述上,可以不用分析整个问题的过程和解决方法。面对对象要做的便是分析各个对象受到的影响和它们能够处理的问题(属性的变化和具有的方法等)。

1.头文件的变化

在C语言中,当需要用到标准输入输出时(实际上大多数情况都需要用到),需要包含头文件stdio.h。在C++程序中,为了兼容性,我们也可以使用stdio.h,但这并不是C++的推荐写法,C++中推荐的头文件是cstdio(注意不是#include “cstdio.h"而是“cstdio”!)。类似的,string.h也应改写为cstring,math.h改写为cmath,ctype.h改写为cctype。如下程序:

#include <cstdio>

int main() {
	printf("Hello world!");
	return 0;
}

因此,如果需要在C++程序中继续使用C语言的一些函数,更推荐的方法应该是在以前的头文件前加上c,而不是继续使用之前的.h文件。

当然,C++也有其特有的头文件,如提供输入输出流的iostream、提供一些常用算法的algorithm、提供字符串操作的string等。为了与C语言中的头文件进行区分,在使用它们的时候都去掉了尾部.h后缀。此外,包含上述文件的C++的标准库都包含在“std”命名空间(后面将详细讨论命名空间)中。下面是利用C++标准程序库的demo:

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100 + 10;
int A[maxn];
int main() {
	long long a, b;
	while (cin >> a >> b) {
		cout << min(a, b) << endl;
	}
	return 0;
}

上面的程序与纯C语言程序已经有很大的区别了:
1.C++中特有的头文件iostream和algorithm
2.命名空间std的声明
3.利用cin、cout进行输入输出
ps:注意这里使用const来定义常量

2.C++标准程序库

上面提到的iostream和algorithm属于C++标准程序库,所以C++标准程序库中到底都包含些什么呢?下面是它的定义(摘自百度百科):

C++中的标准程序库是类库函数的集合,其使用核心语言写成。标准程序库提供若干泛型容器函数对象泛型字符串(包含交互和文件I/O),支持部分语言特性常用函数,如开平方根。C++标准程序库也吸收了ISO C90 C标准程序库。标准程序库的特性声明于std命名空间之中。

C++标准程序库主要包含以下几类:
1.通用(general):algorithm、iterator、memory等
2.流(stream):iostream、fstream、ostream等
3.容器(containers):map、set、vector、stack、queue等
4.数值(numerics):numeric、complex、random、valarray
5.辅助(support):exception、typeinfo、new、limits
6.线程(thread):thread、mutex、future、condition_variable
7.字符串(strings): string、regex
8.本地化(localization):locale、codecvt
9.C标准库(c standard library)
注:分类可能不同,没有严格定义
可以看出,C++标准程序库提供了丰富的内容,使得C++相对于C语言更加的强大和灵活,但同时也更加的复杂。
C++标准程序库也兼容所有C语言的标准库。正如前面所说,这里的C标准库是前面对C库进行加头(c)去尾(.h)的结果。值得注意的是,C++标准程序库中的所有标识符都被定义于一个名为std的namespace中,连cstdio等也不例外。

3.命名空间(namespace)

在前面我们提及,C++标准程序库中的所有标识符都被定义在一个名为std的namespace中。因此,我们可以把命名空间简单的理解为名称的集合。
这里空间的概念和数学中的向量空间有着异曲同工之妙,将需要研究的一类事物进行封装,使得不同程序库中名称相同的元素能够区分开来。例如:数学中向量乘法和数值乘法计算是不同的,但它们可以有相同的名字(乘法)。同理,程序库a和程序库b中也可以有相同的类或者函数,它们可以有相同的名称和参数,但可以通过定义在不同的namespace来区分开。
使用命名空间中标识符的三种选择(以std为例):

1.使用using namespace std
这是最简单的一种方法,使得名称空间std中的所有标识符都得到了定义。因此,后面的程序中就可以使用C++标准库中的所有内容。
值得注意的一点是,该语句是有作用范围的。如果将该语句声明在main函数里面,则其他函数要使用std中的标识符时需要再次声明,如下例:

#include <iostream>

void HelloWorld();
int main() {
	using namespace std;
	cout << "Hello C++ !" << endl;
	HelloWorld();
	return 0;
}
void HelloWorld() {
	using namespace std;
	cout << "Hello World !" << endl;
}

当然,也可以选择将其声明在函数外面,相当于定义了全局变量。

#include <iostream>
using namespace std;
void HelloWorld();
int main() {
	cout << "Hello C++ !" << endl;
	HelloWorld();
	return 0;
}
void HelloWorld() {
	cout << "Hello World !" << endl;
}

显然,将std声明在函数外面这种做法是很方便的,但这样也增加了命名冲突的风险,因此更推荐的做法应该是将std声明在函数内部。实际上,大多数情况下,大家为了图方便都会将std声明在函数外面,这在大型工程中是不可取的。

2.直接指定标识符

 #include <iostream>
int main() {
	std::cout << "Hello World !" << std::endl;
	return 0;
}

这种方式直接使用名称空间std的标识符cout和endl,可以很大程度上的避免命名冲突。但如果该名称空间中的标识符在程序中大量的使用,则采用这样的方式就不是很方便。这种情况下,采用声明名称空间的方式会显得更加方便,当然,还可以采取下面这种方式。

3.使用using关键字

#include <iostream>
using std::cout;
using std::endl;
int main() {
	cout << "Hello World !" << endl;
	return 0;
}

这里相当于声明了std中的cout和endl,后面就可以直接使用cout和endl标识符了。注意using关键字是有作用范围的!

#include <iostream>
int main() {
   using std::cout;
   using std::endl;
   cout << "Hello World !" << endl;
   return 0;
}
void HelloCpp() {
   using std::cout;
   using std::endl;
   cout << "Hello C++ !" << endl;
}

ps:在讨论完如何使用命名空间中标识符这个问题后,我们回到本文的第一个程序。可以看到我们使用的cstdio中的printf函数,这里cstdio也属于C++标准程序库,按理说其printf标识符应该也存在于std命名空间,需要使用printf函数也需要按上述方法来进行声明。事实上,C++标准确实这样要求,但大多数编译器并未严格执行这个要求。因此,我们可以直接使用cstdio中的标识符而不需要声明名称空间。注意这并不是C++推荐的方式!

#include <cstdio>

int main() {
	printf("Hello world!");
	return 0;
}

4.数据类型的变化

C/C++的数据类型有两种,预定义类型和自定义数据类型。C语言和C++中的预定义类型如下:
C:整型、实型、字符型、(空型)
C++:整型、实型、字符型、布尔型、(空型)
其中,最明显的变化是C++中增加了布尔数据类型。在C语言中,我们常用整型数据来表示真假;在C++中,使用布尔型变量可以让程序变得更加清晰。

5.指针与引用

在C语言中,经典的交换变量值的程序如下,这里采用的是地址传递的方式:

#include <stdio.h>
void swap(int *a, int *b);
int main() {
	int a = 3, b = 4;
	swap(&a, &b);
	printf("%d %d\n", a, b);
	return 0;
}
void swap(int *a, int *b) {
	int temp = *a; *a = *b; *b = temp;
}

在C++中,可以有更加简洁的方式,采用引用传递!

#include <iostream>
using namespace std;
int main() {
	int a = 3, b = 4;
	swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}
void swap(int &a, int &b) {
	int temp = a; a = b; b = temp;
}

引用是C语言中没有的,是C++提供的特有的功能。在引用传递中,&不是表示取地址,而是标记一个变量是引用变量,而引用实际上就是另一个变量的别名。引用变量有如下几点特性:
1.引用就是另一个变量的别名,不额外开辟空间
2.声明引用的同时必须初始化,因为引用在初始化时就必须与一个变量建立联系。
3.引用不能再作为其它变量的引用,意思是引用自初始化之后就与一个变量建立了确定的关系,该引用不能与其他变量建立关系。如:给变量a取别名aa后,不能将aa作为另一个变量b的别名。

#include <iostream>
using namespace std;
int main() {
	int a = 1;
	int &aa = a;
	cout << "变量a的地址为:" << &a << endl;
	cout << "引用aa的地址为:" << &aa << endl;
	a=2;
	cout << "变量a的值为:" << a << endl;
	cout << "引用a的值为:" << aa << endl;
	aa = 3;
	cout << "变量a的值为:" << a << endl;
	cout << "引用a的值为:" << aa << endl;
	return 0;
}
变量a的地址为:004FFE90
引用aa的地址为:004FFE90
变量a的值为:2
引用a的值为:2
变量a的值为:3
引用a的值为:3

从上述代码以及运行结果可以看出:①变量a和引用aa地址相同②改变其中一个的值,另一个也会发生相应的变化。
同样的,我们也可以对数组和指针进行引用,示例如下:

#include <iostream>
using namespace std;
int main() {
	int a[3] = { 1,2,3 };
	int (&aa)[3] = a;
	for (int i = 0; i < 3; ++i)
		cout << a[i] << " " << aa[i] << endl;
	return 0;
}
#include <iostream>
using namespace std;
int main() {
	int a=1;
	int* ptr=&a;
	int* &ptr1 = ptr;
	*ptr1 = 2;
	cout << "a的值为:" << a << endl;
	return 0;
}

从上面的讨论可以看出,引用是变量的一个别名,因此二者用起来几乎没有任何差别。下面将讨论引用在函数参数的传递中起到的巨大作用。
在上面变量交换的例子中使用到了引用传递。引用传递中,函数的形式参数定义如下:
void swap(int &a, int &b) ;
函数调用时传递参数形式如下:
swap(a, b);
在函数调用时,函数的形参就会被当作实参的别名来使用。因此,在函数内部对引用所进行的所有操作都等同于对变量进行的操作。当然,使用指针也可以 实现这样的效果,但引用会使得代码更加的简洁,并且不需要额外的内存消耗。
当然,引用传递也可以传递数组。在C语言中,传递数组实际上就是传递数组的地址,函数中形参可以是指针,也可以是数组。但实际上,二者的作用都是一样的。下面是C语言中传递数组的两种方式:

#include <stdio.h>
void swap1(int a[2]);
void swap2(int* a);
int main() {
	int a[2] = { 1, 2 };
	printf("Outside: Length=%d ", sizeof(a) / sizeof(int));
	printf("{ %d %d }\n", a[0], a[1]);
	swap1(a);
	printf("{ %d %d }\n", a[0], a[1]);
	swap2(a);
	printf("{ %d %d }\n", a[0], a[1]);
	return 0;
}
void swap1(int a[2]) {
	int temp = a[0]; a[0] = a[1]; a[1] = temp;
	printf("swap1  : Length=%d ", sizeof(a)/sizeof(int));
}
void swap2(int* a) {
	int temp = a[0]; a[0] = a[1]; a[1] = temp;
	printf("swap2  : Length=%d ", sizeof(a) / sizeof(int));
}
Outside: Length=2 { 1 2 }
swap1  : Length=1 { 2 1 }
swap2  : Length=1 { 1 2 }

可以看出,无论形参是数组还是指针,函数中均不能通过sizeof来获取数组的大小。并且,形参中定义的数组大小并不重要,因为函数传入的是数组的地址,而地址中不携带长度信息。下面是引用传递的例子:

#include <iostream>
using namespace std;
void swap1(int(&a)[2]);
int main() {
	int a[2] = { 1, 2 };
	cout << "Outside: Length=" << sizeof(a) / sizeof(int) << " ";
	cout << "{ " << a[0] << " " << a[1] << " }" << endl;
	swap1(a);
	cout << "{ " << a[0] << " " << a[1] << " }" << endl;
	return 0;
}
void swap1(int(&a)[2]) {
	int temp = a[0]; a[0] = a[1]; a[1] = temp;
	cout << "swap1  : Length=" << sizeof(a) / sizeof(int) << " ";
}
Outside: Length=2 { 1 2 }
swap1  : Length=2 { 2 1 }

由于是引用传递,swap1函数中的引用a就是数组a的别名,因此不仅可以利用引用来更改数组中的值,还可以通过引用获得数组的大小引用传递数组时也就必须指明数组的大小

最后,讨论引用作为函数的返回值的情况。我们都知道,函数内的临时变量在函数运行完之后便会被销毁。因此,如果我们把临时变量的引用作为函数的返回值,并将其作为新的引用的初始值,则新的引用也将被销毁。

#include <iostream>
using namespace std;
int &swap1();
int aa();
int main() {
	int &a=swap1();
	cout << a << endl;
	aa();
	cout << a << endl;
	return 0;
}
int &swap1() {
	int temp = 1;
	return 1;
}
int aa() {
	return 1;
}
1
-858993460

当然,也可以使用变量来接收函数的返回值,这样函数返回的值就会利用该变量进行保存而不会被销毁。但是这样做和直接让函数返回值没有任何区别,我们让函数返回引用的目的就是为了让其返回变量本身,而不是返回变量的值。
因此,当将引用作为函数的返回值时,函数应当返回全局变量而不是局部变量

6.字符串(string)

C语言提供了string.h来方便程序员处理字符串,string.h中主要包含以下用于字符串处理的函数:

C++中除了支持C中的字符数组外,还提供了一个更加强大的string类。但由于string类涉及太多面向对象的内容,这里只作简单介绍,在后面会进行详细讨论。
(1)字符串创建
C++对string的 构造函数实现了多个重载,因此有多种方法创建并初始化一个字符串,下面是几个常用的创建方式。

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s1;//创建空串
	string s2("Hello World!");          //以cstring作为初值
	string s3(s2);                      //以string作为初值,相当于拷贝
	string s4(s2, 5);                   //取string第5个字符之后的所有字符
	string s5("Hello World!", 5);       //取cstring的前5个字符
	string s6(s2, 6, 5);                //取string第6个字符之后的5个字符
	string s7("Hello World", 6, 5);     //取cstring第6个字符之后的5个字符
	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;
	cout << "s3 = " << s3 << endl;
	cout << "s4 = " << s4 << endl;
	cout << "s5 = " << s5 << endl;
	cout << "s6 = " << s6 << endl;
	cout << "s7 = " << s7 << endl;
	return 0;
}
s1 =
s2 = Hello World!
s3 = Hello World!
s4 =  World!
s5 = Hello
s6 = World
s7 = World

这里需要注意的是s4和s5的不同,分别以string和cstring作为源创建string时,两种重载的第二个参数意义是不同的,前者为起始位置,后者为字符数。

(2)字符元素存取
C++提供了三种方式对string中的字符进行索引,分别为:下标索引 [ i ],at( i )访问,back()/front()访问首字符和末字符。

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s("Hello World!");//以cstring作为初值
	cout << s[0] << endl;
	cout << s.at(0) << endl;
	cout << s.front() << endl;
	cout << s.back() << endl;
	return 0;
}
H
H
H
!

(3)字符串赋值
string类重载了“=”操作符,因此可以直接用"="进行赋值,此外,C++还提供了更加灵活的assign成员函数来对字符串进行赋值。

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s1, s2, s3, s4, s5, s6;
	s1 = "Hello World !";//=cstring
	s2 = s1;			 //=string
	s3.assign(s1);		 //assign(string)
	s3.assign("Hello World !");//assign(cstring)
	s4.assign("Hello World !", 5);//assign(cstring, n)	
	s5.assign(s1, 5);		//assign(string, pos)
	s6.assign(12, '-');		//assign(n, char)
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	return 0;
}
Hello World !
Hello World !
Hello World !
Hello
 World !
------------

同使用构造函数创建字符串时相同,这里的s4和s5也得到了不同的结果,因此对于ctring和string,assign实现了不同的重载,意义同构造函数。

(4)字符串操作
C++提供了许多对字符串进行操作的方法,包括增、删、查找、替换、交换、属性获取等许多方便的功能。下面就几个常用的方法进行简要的总结。
增删操作
string重载了”+=操作符",因此可以利用“+=”来增长字符串。此外,C++还提供了append()和push_back()来对字符串进行增操作,erase()来对字符串进行减操作,clear()来对字符串进行清空操作。

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s = "Hello World !";
	cout << "s = " << s << endl;
	s += " I'm";//后面增加字符串" I'm"
	s.append(" C++");//后面增加字符串" C++"
	s.push_back('!');//后面增加'!'字符
	cout << "s = " << s << endl;
	s.erase(5);//删除第5个字符后的所有字符
	cout << "s = " << s << endl;
	s.erase(1,3);//删除第1到3个字符
	cout << "s = " << s << endl;
	s.clear();//清空字符串
	cout << "s = " << s << endl;
	return 0;
}
s = Hello World !
s = Hello World ! I'm C++!
s = Hello
s = Ho
s =

查找操作
string类提供了一些用于字符查找和字符串查找的方法,主要有:
find()
rfind()
find_first_of()
find_last_of()
find_first_not_of()
find_last_not_of()
string类对上述6个方法进行了多个重载,可以满足大多数情况下的要求。

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s = "Hello World ! Hello World !";
	int pos0 = s.find('l');//查找字符‘l’的位置
	cout << pos0 << endl;
	int pos1 = s.find('l', 10);//从下标10开始查找字符‘l'的位置
	cout << pos1 << endl;
	int pos2 = s.find("World");//查找字符串"World"的位置
	cout << pos2 << endl;
	int pos3 = s.find("World", 10);//从下标10开始查找字符串"World"的位置
	cout << pos3 << endl;
	int pos4 = s.find("World????", 10, 5);//从下标10开始查找字符串"Wordl????"的前五个字符"World"的位置
	cout << pos4 << endl;
	return 0;
}
2
16
6
20
20

这里仅给出了find的用法,其余五个方法的用法类似。
此外,STL中还提供了许多功能强大的查找功能,在后面会进行讨论。

子串操作
string类提供了提取字串的方法substr(),用法如下:

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s = "Hello World !";
	string s1 = s.substr();    //全部
	string s2 = s.substr(6);   //从下标6开始的所有字符
	string s3 = s.substr(2, 7);//从下标2开始7个字符,不是2到7!
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	return 0;
}
Hello World !
World !
llo Wor

属性操作
string类提供了一些关于属性操作的方法,较常用的如下:
size() 返回字符串大小
length() 返回字符串长度,和size()几乎没区别
max_size() 可能的最大大小
empty() 判断字符串是否为空

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s = "Hello World !";
	cout << s.size() << endl;
	cout << s.length() << endl;
	cout << s.max_size() << endl;
	cout << s.empty() << endl;
	return 0;
}

13
13
2147483647
0

(5)字符串流
sstream库中定义了三种类:istringstream、ostringstream和stringstream,分别用来进行字符串流的输入、输出和输入输出操作。
字符串流常用于数据转换和字符串的处理,下面分别简单的介绍一下两个功能的实现:
数据转换
在C语言中,将float型数据与字符数组之间的相互转换可以这样做:

#include <stdio.h>
int main() {
	char s[12];
	float ft0 = 32.23,ft1;
	sprintf(s, "%f", ft0);//float转字符数组
	sscanf(s, "%f", &ft1);//字符数组转float
	for (int i = 0; s[i] != '\0'; ++i) printf("%c ", s[i]);
	printf("\n%f\n", ft1);
	return 0;
}

当然,在C++中也可以这样做,但C++中提供的字符串流可以提供更强大的功能。下面是举一个例子:

#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int main() {
	float data, sum=0;
	string line = "12.34 23.45 34.56 45.67";
    stringstream ss(line);//以string创建字符串流
	//stringstream ss; ss << line;
	while (ss >> data) sum += data;//逐个输出求和
	cout << "sum = " << sum << endl;
	return 1;
}

上例中以字符串"12.34 23.45 34.56 45.67"创建一个字符串流ss,然后依次从字符串流ss中读取数据进行求和,最后输出求和的结果。

下面再举一个数据转换的简单例子:

void float_to_string(string &s, float ft) {
	stringstream ss;
	ss << ft;
	s=ss.str();
	//ss >> s;
}

这个例子通过字符串流将float型数据转换为string,因为不用担心string的长度问题,所以也就不用担心是否会溢出。将其他类型转换为string的做法也是一样的,通过定义模板可以实现任意数据类型之间的转换。

输入和输出
字符串流还可以用于输入与输出。上面提到,istringstream、ostringstream和stringstream,分别用来进行字符串流的输入、输出和输入输出操作。我们利用stringstream可以很方便的对从标准输入读取的数据进行处理。将上面求和的例子稍作修改如下:

#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int main() {
	float data, sum = 0;
	string line;
	stringstream ss;
	while (getline(cin, line)) {
		ss << line; sum = 0;//流入
		while (ss >> data) sum += data;//流出求和
		cout << "sum = " << sum << endl;//打印
		ss.clear();//清空
	}
	return 1;
}

上例中,使用string类提供的getline可以从某个输入流(cin)中读取一行数据赋值给字符串。利用字符串流来临时保存数据,然后逐个读出求和。

7.结构体与类

(1)对比C语言中的结构体
在C语言中,我们经常定义结构体来作为数据结构,而C++也支持定义结构体。C++中的结构体与C语言中的结构体主要有以下几点区别:
1.C语言需要使用struct structNme来定义结构体,而C++可以直接用structName来定义结构体名。(C语言可通过typedef来为结构体取别名)

#include <iostream>
using namespace std;
struct Point {
	int x, y;
};
int main() {
	Point p1;
	p1.x = 1;
	p1.y = 0;
	return 1;
}

2.C语言中不能初始化结构体成员,而C++中可以。

#include <iostream>
using namespace std;
struct Point {
	int x=1, y;
};
int main() {
	Point p1;
	cout << p1.x << endl;
	return 1;
}
1

3.C语言中结构体不能有成员函数,而C++中可以有。

struct Point {
	int x=1, y;
	void printxy() {
		cout << "x = " << x << endl;
		cout << "y = " << y << endl;
	}
};

4.C语言结构体中不能声明静态变量,而C++可以。

	struct Point {
		static int x;
	};

5.C语言结构体中的变量不能有访问修饰符,只能是public,而C++中的可以有。

	struct Point {
		private: int x;
	};

6.C语言中结构体不能继承,而C++中可以。

	#include <iostream>
	using namespace std;
	struct Point {
		int x;
	};
	struct Point2D :Point {
		int y;
	};
	int main() {
		Point2D p1;
		p1.x = 1;
		p1.y = 2;
		return 1;
	}

(2)构造函数与析构函数
为什么需要构造函数
我们知道,对于未初始化的全局变量,编译器会自动将其初始化未0;而未初始化的局部变量,它的值则是未知的。因为全局变量在程序开始运行之后地址便不会发生改变,因此程序启动时将其初始化未0不会浪费多少时间。而局部变量则不同,局部变量的存储空间是动态分配的,每次调用函数时都可能是不同的,如果每次调用该函数都将局部变量进行初始化,则会带来许多的时间耗费。因此,未显式被初始化局部变量的值是未知的,它的值取决于该地址之前的值。
结构体和对象的初始化则是比变量的初始化更为复杂,结构体和对象中可能含有指针等必须进行初始化的成员,有的可能还涉及到动态内存分配等操作。因此,通过定义一个函数来显式的对结构体或者对象进行初始化是有必要的。我们通过自定义一个成员函数来对结构体进行初始化是一种可行的方式,在每次定义结构体之后都利用该函数对结构体进行初始化即可。但这种方式未免显得有些麻烦,由于缺少强制性,程序员可能会忘记调用该函数。因此,C++提供了构造函数,它在结构体或对象定义的时候被自动调用来进行初始化

构造函数有以下几个特点:
1.构造函数是一类特殊的成员函数
2.构造函数的名字和结构体/类的名字相同
3.构造函数可以重载
4.如果不编写构造函数,编译器会自动生成一个构造函数

下面是一个简单的例子:

#include <iostream>
using namespace std;
struct Point {
public:
	int x, y;
	Point(int, int);
};
Point::Point(int a, int b) {
	x = a;
	y = b;
}	
int main() {
	Point p1(2,3);
	cout << p1.x << " " << p1.y << endl;
	return 0;
}

上例中为结构体Point定义了构造函数,函数有两个参数,用于给成员变量赋值。
此构造函数还可以有如下简单写法

struct Point {
public:
	int x, y;
	Point(int, int);
};
Point::Point(int a, int b) :x(a), y(b) {}

这里的x(a), y(b)表示x=a, y=b,还可以进一步简化为:

struct Point {
public:
	int x, y;
	Point(int a, int b) :x(a), y(b) {};
};

构造函数支持重载,它们的名字相同,根据参数列表不同调用不同的构造函数:

struct Point {
public:
	int x, y;
	Point(int a, int b) :x(a), y(b) {};
	Point(int a) :x(a), y(a) {};
};

当使用Point(2)创建结构体时,a=b=2
当使用Point(2, 3)创建结构体时,a=2 b=3

另外,C++中还有另一个和构造函数功能相反的函数–析构函数。它的主要作用是释放资源,清理善后。例子如下:

#include <iostream>
using namespace std;
struct Point {
public:
	int x, y;
	Point(int a, int b) :x(a), y(b) {};
	Point(int a) :x(a), y(a) {};
	~Point() {cout << "Goodbye World" << endl;};
};
int main() {
	Point p1(2, 2);
	cout << p1.x << " " << p1.y << endl;
	return 0;
}
2 2
Goodbye World

上面结构体中的~Point函数为析构函数,在主函数执行完的时候会调用这个函数来处理一些善后事宜。这里自定义了析构函数,于是在程序结束之前会调用该析构函数,从而打印出析构函数中的"Goodbye World"。注意析构函数不能重载!

(3)重载运算符
C++中很多内置数据类型都支持如"+"、"-"、"="、"<"等运算符,这使得表达式非常的简洁。但很多情况下,我们也希望自已定义的一些数据结构也支持类似的运算符。C++提供了这样一种机制,支持我们在不同的数据类型下对运算符进行重载,于是我们可以让自己定义的数据类型同样支持运算符。下面列出C++支持重载的运算符

类型 运算符
双目算术运算符 +(加) 、-(减)、* 、/ 、%
单目运算符 + (正)、-(负)、*(指针) 、&(取址)
关系运算符 >、<、>=、<=、==、!=
逻辑运算符 | |、&&、!
位运算符 &、|、^、~、<<、>>
赋值运算符 =、+=、-=、*=、=、%=、&=、|=、^=、<<=、>>=
其他运算符 ++、–、[]、,(逗号)、->、()(函数)、new、delete、new[]、delete[]、<<(流)、>>

不可重载的运算符:
成员访问运算符(.)
成员指针访问运算符(.*和->* ),
域运算符(::)、
长度运算符(sizeof)、
条件运算符(?:)、
预处理符号(#)

下面看一个简单的例子:

#include <iostream>
using namespace std;
struct Point {
public:
	int x, y;
	Point(int a, int b) :x(a), y(b) {};
	Point(int a) :x(a), y(a) {};
};
Point operator+(const Point& A, const Point& B) {
	return Point(A.x + B.x, A.y + B.y);
}
int main() {
	Point p1(2, 2);
	Point p2(3, 4);
	Point p3 = p1 + p2;
	cout << p3.x << " " << p3.y << endl;
	return 0;
}

例中,为Point结构体重载了"+“运算符,传入的参数是要求和的两个结构体,返回一个新的结构体,它的x和y值是传入的两个结构体的对应值之和。重载了”+“运算符之后就可以通过"p1+p2"的形式对两个结构体求和了。
在这个例子的基础上再重载一个”<<"流运算符。

#include <iostream>
using namespace std;
struct Point {
public:
	int x, y;
	Point(int a, int b) :x(a), y(b) {};
	Point(int a) :x(a), y(a) {};
};
Point operator+(const Point& A, const Point& B) {
	return Point(A.x + B.x, A.y + B.y);
}
ostream& operator<<(ostream& out, const Point& B) {	
	out << "(" << B.x << ", " << B.y << ")";
	return out;
}
int main() {
	Point p1(2, 2);
	Point p2(3, 4);
	Point p3 = p1 + p2;
	cout << p3 << endl;
	return 0;
}

这里对"<<“运算符进行了重载,函数的第一个参数类型是ostream的引用,表示输出流,第二个参数类型是结构体Point的引用,在函数中将Point的变量按照自定义的规则流入到第一个参数所引用的ostream中,然后返回所引用的ostream。重载了”<<“运算符后,Point就可以通过”<<"运算符输出以自定义的格式输出到标准输出了。

再重载一些运算符:

#include <iostream>
using namespace std;
struct Point {
public:
	int x, y;
	Point(int a, int b) :x(a), y(b) {};
	Point(int a) :x(a), y(a) {};
};
Point operator+(const Point& A, const Point& B) {//加
	return Point(A.x + B.x, A.y + B.y);
}
Point operator-(const Point &A) {//负
	return Point(-A.x, -A.y);
}
ostream& operator<<(ostream& out, const Point& B) {//流
	out << "(" << B.x << ", " << B.y << ")";
	return out;
}
bool operator<(const Point& A, const Point& B) {//小于
	return A.x < B.y;
}
int main() {
	Point p1(2, 2);
	Point p2(3, 4);
	Point p3 = p1 + p2;//(5, 6)
	Point p4 = -p3;	   //(-5, -6)
	cout << p3 << " " << p4 << " " << (p1 < p2) << endl;
	return 0;
}

例子中对-(取负)、<(小于)进行了重载,因此可以直接对用-Point格式对Point取负,用Point1<Point2对两个结构体进行比较。这里的<(小于)运算符在一些STL中的算法中是很有用的。

在对运算符进行重载时需要注意以下几点:
1.重载运算符无法改变运算符的原有结构形式
2.重载运算符不能改变操作数的个数
3.重载运算符无法改变运算的优先级

有关于结构体和类的其他面向对象特性在这里不做讨论,后面有机会会专门进行详细的讨论。

8.模板(template)

(1)模板的引入
先不讨论什么是模板,看一下下面这个经典的交换程序:

#include <iostream>
using namespace std;
void swap1(int& a, int& b) {
	int temp = a; a = b; b = temp;
}
int main() {
	int a = 2, b = 3;
	swap1(a, b);
	cout << "a = " << a << " b = " << b << endl;
	return 0;
}

这里定义了一个用于交换两个int类型数据的函数swap1。如果我们还需要一个用于交换float类型数据的函数应该怎么办,在不知道模板的情况下,我们很容易想到函数重载,程序如下:

#include <iostream>
using namespace std;
void swap1(int& a, int& b) {
	int temp = a; a = b; b = temp;
}
void swap1(float& a, float& b) {
	float temp = a; a = b; b = temp;
}
int main() {
	int a = 2, b = 3;
	float c = 2.5, d = 3.5;
	swap1(a, b);
	swap1(c, d);
	cout << "a = " << a << " b = " << b << endl;
	cout << "c = " << c << " d = " << d << endl;
	return 0;
}

这个确实达到了我们的需求,但是如果我们还需要交换其他数据类型的函数则需要增加更多的重载。这里不同重载之间起始只有数据类型不同,其他的业务都是相同的。因此C++提供了一种泛型编程的方法,这是一种独立于任何特定类型的编程方式,可以让程序不受数据类型的限制。

模板就是泛型编程的基础。下面还是通过swap的例子来引入模板:

#include <iostream>
#include <string>
using namespace std;
template<typename T>
void swap1(T& a, T& b) {
	T temp = a; a = b; b = temp;
}
int main() {
	int a = 2, b = 3;
	float c = 2.5, d = 3.5;
	string e = "Hello";
	string f = "World!";
	swap1(a, b);
	swap1(c, d);
	swap1(e, f);
	cout << "a = " << a << " b = " << b << endl;
	cout << "c = " << c << " d = " << d << endl;
	cout << "e = " << e << " f = " << f << endl;
	return 0;
}
a = 3 b = 2
c = 3.5 d = 2.5
e = World! f = Hello

上例通过模板使得算法和数据类型解耦,从而使得swap1函数可以作用于多种数据类型。

(2)函数模板与类模板
模板一把分为两类,函数模板和类模板,下面分别介绍两类模板的格式:
函数模板
template <class type,class type,…>
ret-type func(parameter list)
{
// 函数体
}

这里的class可以有typename替换,type为数据类型,多个数据类型由逗号分开。

类模板
template <class type, class type, …>
class classname{
//类主体
}

类模板的结构和函数模板的结构非常类似,下面通过类模板使得Point类支持多种数据类型:

#include <iostream>
using namespace std;
template <typename T>
struct Point {
public:
	T x, y;
	Point(T a, T b) :x(a), y(b) {};//构造函数1
	Point(T a) :x(a), y(a) {};//构造函数2
};
template <typename T>
Point<T> operator+(const Point<T>& A, const Point<T>& B) {//加
	return Point<T>(A.x + B.x, A.y + B.y);
}
template <typename T>
Point<T> operator-(const Point<T> &A) {//负
	return Point<T>(-A.x, -A.y);
}
template <typename T>
ostream& operator<<(ostream& out, const Point<T>& B) {//流
	out << "(" << B.x << ", " << B.y << ")";
	return out;
}
int main() {
	Point<int> p1(2, 3);
	Point<float> p2(2.5, 3.5);
	cout << p1 << " " << p2 << " " << endl;
	return 0;
}

这样,我们就可以同时使用不同数据类型的Point对象了。注意在创建对象时必须指明类模板的参数列表,如<int>、<float>等。

至此,书中关于C++框架特性的内容就结束了,主要的内容涉及到C++标准程序库、命名空间、数据类型、引用、字符串、结构体与类、函数重载与操作符重载、模板等。当然,C++还有许多特性本文没有涉及到,如继承、接口、封装、异常、多线程等。关于这些特性会在以后进行讨论,接下来的重点是讨论C++强大的标准模板库(STL)。

C++标准模板库(STL)

STL是Standard Template Library的简称,中文名标准模板库,惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。
STL从根本上来看可以说是容器的集合,也可以说是算法和一些其他组件的集合。
STL可分为容器(containers)、迭代器(iterators)、空间配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函数(functors)六个部分。
STL又可分为容器(container)、迭代器(iterator)、算法(algorithm)三类。
STL是C++的一部分。
在C++标准中,STL被组织为下面的13个头文件:<algorithm>、<deque>、<functional>、<iterator>、<<array>、<vector>、<list>、<forward_list>、<map>、<unordered_map>、<memory>、<numeric>、<queue>、<set>、<unordered_set>、<stack>和<utility>。

1.容器(container)

在现实生活中,容器是用来装其他东西的器具。在C++中,容器就是能保存其他对象的对象。STL中提供了丰富的容器,并且它们都是模板容器,可以容纳不同的数据类型。
STL中定义的容器可分为三类:

类别 容器
顺序容器 vector(向量)、list(列表)、deque(队列)
关联容器 map(集合)、set(映射)、multimap(多重集合)、multiset(多重映射)
容器适配器 stack(栈)、queue(队列)、priority_queue(优先队列)

(1)顺序容器
顺序容器中各元素之间有顺序关系的容器,顺序容器中每个元素均有固定的位置,位置的先后关系由添加进容器的先后顺序决定。STL中有三个顺序容器,它们分别是vector、list和deque。

1.vector(向量)
vectors是一种线性顺序结构,与数组很类似,但不用预先指定其大小。当数据所需的存储空间大于当前分配的空间时,vector会自动申请一块更大的内存,然后将原来的数据拷贝到新的内存中并销毁原内存中的数据,最后将原来的内存空间释放掉。因此,当数据量变化较大时,vector的性能就不是很好。
vector的特点如下:
1.不用预先指定大小,可动态分配内存
2.由于是线性结构,可对元素进行随机访问
3.插入和删除的效率非常低
4.当动态添加的数据超过默认分配的大小时,进行动态内存分配,数据拷贝等操作非常消耗性能。

创建
vector的操作定义于<vector>头文件中。vector提供了很多的构造函数重载,因此可以很灵活的定义并初始化一个vector。此外,vector还是一个模板容器,因此适用于不同的数据类型。下面是一个创建vector的简单例子。

#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main() {
	vector<int> vec;
	vector<int> vec1 = vec;
	vector<int> vec2(vec);
	vector<int> vec3(vec.begin(), vec.end());
	vector<int> vec4(10);
	vector<int> vec5(10, 1);
	vector<char> vec6(10, 'X');
	vector<string> vec7(10, "Hello World");
	cout << vec.size() << endl;
	cout << vec1.size() << endl;
	cout << vec5.size() << endl;
	return 0;
}

0
0
10

增删插入
上面说到,由于vector在内存中的存储特性,因此vector在插入与删除性能很差。因此尽量需要频繁的在数据中间进行插入和删除操作时,不宜使用vector容器。vector提供了一个在末尾增删元素的方法,当数据长度在当前分配数据大小以内时,效果还是不错的。

#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
void print_vec(const vector<int> &vec);
int main() {
	vector<int> vec;
	for (int i = 0; i < 10; ++i) 
		vec.push_back(i);//添加10个元素{0, 1, ..., 9}
	print_vec(vec);
	vec.pop_back();//删除最后一个元素
	print_vec(vec);
	vec.pop_back();//删除最后一个元素
	print_vec(vec);
	vec.insert(vec.begin(), 2, 0);//效率很低
	print_vec(vec);
	vec.erase(vec.begin(), vec.end());
	//vec.clear();
	print_vec(vec);
	cout <<  "sizeof vec: " << vec.size() << endl;
	return 0;
}
vec: 0 1 2 3 4 5 6 7 8 9
vec: 0 1 2 3 4 5 6 7 8
vec: 0 1 2 3 4 5 6 7
vec: 0 0 0 1 2 3 4 5 6 7
vec:
sizeof vec: 0

遍历
访问vector中元素的方法一般有两种:
1.由于vector的随机访问特性,因此它可以和数组一样使用下标访问。
2.使用迭代器访问,迭代器也是STL一个非常重要的内容,这里先给出遍历方法,后面会详细讨论。
3.使用range_based for(后面简称rangefor),这是C++11新增的,类似于python中的 for in等,不需要指出开始和结束的条件。

void print_vec(const vector<int> &vec) {
	int length = vec.size();
	for (int i=0; i<length; i++)
		cout << vec[i] << " ";
	cout << endl;
}
void print_vec(const vector<int> &vec) {
	for (vector<int>::const_iterator it = vec.begin(); it != vec.end(); it++)
		cout << *it << " ";
	cout << endl;
}
void print_vec(const vector<int> &vec) {
	for(auto& i:vec)
		cout << i << " ";
	cout << endl;
}

相关函数

函数 作用
size() 返回容器中元素个数
max_size() 返回容器的最大容量
resize() 改变容器的容量
at() 访问某个下标的元素
capacity() 返回当前分配的空间大小(元素个数)
empty() 判断容器是否为空
reserve() 改变capacity大小
shrink_to_fit() 减少capacoty,使之与size大小相同
front() 返回首元素
back() 返回尾元素
data() 返回指向容器中数组的指针
assign 给容器赋值(完全替换掉以前的内容)
push_back() 在容器末尾增加一个元素
pop_back() 在容器末尾移出一个元素
insert() 在某个位置插入元素
erase() 删除某个位置的元素
swap() 交化两个容器的内容
clear() 清除容器内容
emplace() insert的优化版本
emplace_back() push_back的优化版本
emplace_back() 在最后插入一个元素
get_allocator() 给容器分配空间
(c)(r)begin() 返回指向第一个元素的(常)(反)迭代器
(c)(r)end() 返回指向最后一个元素后面的(常)(反)迭代器

2.list(列表)
list是一种线性链表结构,学过数据结构的都知道,链表由若干个结点组成,每个节点包含数据域和指针域。而list就相当于一个双向链表,它的每个结点包含一个指向前驱的指针和一个指向后驱的指针。list在内存中不是连续的,因此无法像vector那样随机存取。但由于list的链式结构,其插入和删除的效率很高。
vector的特点如下:
1.不用预先指定大小,可动态分配内存
2.不能随机访问,查找效率低
3.插入和删除的效率高
4.由于存在指针域,因此比vector占用更多空间

创建
list的操作定义于<list>头文件中。list的定义和初始化与vector非常相似,

#include <iostream>
#include <iterator>
#include <list>
using namespace std;
int main() {
	list<int> list0;
	list<int> list1 = list0;
	list<int> list2(list0);
	list<int> list3(list0.begin(), list0.end());
	list<int> list4(10);
	list<int> list5(10, 1);
	list<char> list6(10, 'X');
	list<string> list7(10, "Hello World");
	cout << list0.size() << endl;
	cout << list2.size() << endl;
	cout << list6.size() << endl;
	return 0;
}
0
0
10

增删插入
list的增删插入操作和vector的也十分类似,不过list中一些操作的效率要高很多。

#include <iostream>
#include <iterator>
#include <list>
using namespace std;
int main() {
	list<int> list1;
	for (int i = 0; i < 10; ++i)
		list1.push_back(i);//添加10个元素{0, 1, ..., 9}
	print_list(list1);
	list1.pop_back();//删除最后一个元素
	print_list(list1);
	list1.pop_back();//删除最后一个元素
	print_list(list1);
	list1.insert(list1.begin(), 2, 0);
	print_list(list1);
	list1.erase(list1.begin(), list1.end());
	//vec.clear();
	print_list(list1);
	cout << "sizeof vec: " << list1.size() << endl;
	return 0;
}

遍历
list容器无法用下标进行遍历,因此只能用迭代器和rangefor对list进行遍历:

void print_list(const list<int> &contain) {
	for (list<int>::const_iterator it = contain.begin(); it != contain.end(); it++)
		cout << *it << " ";
	cout << endl;
}
void print_list(const list<int> &contain) {
	for(auto& it:contain)
		cout << *it << "";
}

相关函数
list容器有关的函数大多数都和vector容器的类似,不过其中也有一些不同,少了关于capacity相关的函数,另外还多了几个特殊的函数。

函数 作用
size() 返回容器中元素个数
max_size() 返回容器的最大容量
resize() 改变容器的容量
at() 访问某个下标的元素
capacity() 返回当前分配的空间大小(元素个数)
empty() 判断容器是否为空
reserve() 改变capacity大小
shrink_to_fit() 减少capacoty,使之与size大小相同
front() 返回首元素
back() 返回尾元素
data() 返回指向容器中数组的指针
assign 给容器赋值(完全替换掉以前的内容)
push_back() 在容器末尾增加一个元素
pop_back() 在容器末尾移出一个元素
insert() 在某个位置插入元素
erase() 删除某个位置的元素
swap() 交化两个容器的内容
clear() 清除容器内容
emplace() insert的优化版本
emplace_back() push_back的优化版本
emplace_back() 在最后插入一个元素
get_allocator() 给容器分配空间
(c)(r)begin() 返回指向第一个元素的(常)(反)迭代器
(c)(r)end() 返回指向最后一个元素后面的(常)(反)迭代器
push_front() 容器最前面插入一个元素
pop_front() 容器最前面移除一个元素
merge() 合并两个容器
splice() 合并两个容器
reverse() 将容器中的元素进行反转
sort() 排序
unique() 删除重复元素
remove() 移除某些特定元素
remove_if() 移除某些满足条件的元素

3.deque(双端队列)
从上面的讨论可以看出,vector的查找效率高,插入和删除的效率低;而list的插入和删除效率低,但查找效率却很低。而deque则是一种结合了vector和list各自有点的容器,它有着较高的查找效率和插入删除效率。当然,deque在查找上肯定不如vector, 插入删除上不如list,但它兼顾了两者的优点,在很多情况下是很好的选择。
因此,deque的特点如下:
1.支持随机访问,但性能不如vector
2.支持在内部进行插入和删除,但性能不如list
3.它也支持双端push和pop

创建
deque的定义和初始化与二者类似:

#include <iostream>
#include <iterator>
#include <deque>
using namespace std;
int main() {
	deque<int> deque0;
	deque<int> deque1 = deque0;
	deque<int> deque2(deque0);
	deque<int> deque3(deque0.begin(), deque0.end());
	deque<int> deque4(10);
	deque<int> deque5(10, 1);
	deque<char> deque6(10, 'X');
	deque<string> deque7(10, "Hello World");
	cout << deque0.size() << endl;
	cout << deque2.size() << endl;
	cout << deque6.size() << endl;
	return 0;
}

增删插入
list的插入与删除与list差不太多,不过在效率上稍低,它同样支持在容器头进行push和pop。

#include <iostream>
#include <iterator>
#include <deque>
using namespace std;
int main() {
	deque<int> deque1;
	for (int i = 0; i < 10; ++i)
		deque1.push_front(i);//从deque头添加10个元素{0, 1, ..., 9}
	print_deque(deque1);
	deque1.pop_back();//删除最后一个元素
	print_deque(deque1);
	deque1.pop_front();//删除第一个元素
	print_deque(deque1);
	deque1.insert(deque1.begin(), 2, 0);
	print_deque(deque1);
	deque1.erase(deque1.begin(), deque1.end());
	//vec.clear();
	print_deque(deque1);
	cout << "sizeof vec: " << deque1.size() << endl;
	return 0;
}

遍历
由于deque支持下标访问,因此deque也支持下标遍历、迭代器遍历以及rangefor三种方式,此处省略,可参考vector的遍历。

相关函数
deque大多数函数和vector一致,不过删除了两个与capacity有关的函数,增加了几个操作首元素的函数,详细变动如下:

函数 作用
size() 返回容器中元素个数
max_size() 返回容器的最大容量
resize() 改变容器的容量
at() 访问某个下标的元素
capacity() 返回当前分配的空间大小(元素个数)
empty() 判断容器是否为空
reserve() 改变capacity大小
shrink_to_fit() 减少capacoty,使之与size大小相同
front() 返回首元素
back() 返回尾元素
data() 返回指向容器中数组的指针
assign 给容器赋值(完全替换掉以前的内容)
push_back() 在容器末尾增加一个元素
pop_back() 在容器末尾移出一个元素
insert() 在某个位置插入元素
erase() 删除某个位置的元素
swap() 交化两个容器的内容
clear() 清除容器内容
emplace() insert的优化版本
emplace_back() push_back的优化版本
get_allocator() 给容器分配空间
(c)(r)begin() 返回指向第一个元素的(常)(反)迭代器
(c)(r)end() 返回指向最后一个元素后面的(常)(反)迭代器
push_front() 容器最前面插入一个元素
emplace_front() push_front的优化版本
pop_front() 容器最前面移除一个元素

(2)关联容器
关联容器内部是一种非线性的树形结构,具体来说是采用的一种高效的平衡检索二叉树-红黑树。关联容器分为两类,集合(set)和映射(map)。

1.set(集合)
set是一种非线性结构,因此它不具有随机访问的特性。set中的元素是唯一的,所以一个set中不允许有重复的元素,并且set会根据元素的值进行自动排序,因此set中的元素是有序的。multiset和set非常相似,不过取消元素值唯一这个约束。
set的特点如下:
1.非线性结构,不能随机访问
2.内部元素是唯一的
3.内部元素自动排序
4.具有优秀的检索以及插入删除特性

创建

#include <iostream>
#include <iterator>
#include <set>
using namespace std;
void print_set(const set<int> &s) {
	for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
		cout << *it << " " ;
	cout << endl;
}
int main() {
	set<int> set1;
	set<int> set2 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
	set<int> set3 = set2;
	set<int> set4(set2);
	cout << "set1: "; print_set(set1);
	cout << "set2: "; print_set(set2);
	cout << "set3: "; print_set(set3);
	cout << "set4: "; print_set(set4);
	return 0;
}
set1:
set2: 1 2 3 4 5 6 9
set3: 1 2 3 4 5 6 9
set4: 1 2 3 4 5 6 9

从结果可以看出,set中元素是唯一的且默认按升序排序。这里读者不妨试试multiset的效果,结果应该如下:

set1:
set2: 1 2 2 2 2 3 3 4 4 5 6 9
set3: 1 2 2 2 2 3 3 4 4 5 6 9
set4: 1 2 2 2 2 3 3 4 4 5 6 9

增删插入
由于set的树形结构,没有树尾的说法,因此未提供push、pop等方法进行插入删除。下面是利用insert\emplace和erase进行插入删除的例子:

int main() {
	set<int> set1 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
	cout << "origin:     "; print_set(set1);
	set1.insert(10);
	cout << "inset(10):  "; print_set(set1);
	set1.emplace(8);
	cout << "emplace(8): "; print_set(set1);
	set1.erase(2);
	cout << "erase(2):   "; print_set(set1);
	return 0;
}
origin:     1 2 3 4 5 6 9
inset(10):  1 2 3 4 5 6 9 10
emplace(8): 1 2 3 4 5 6 8 9 10
erase(2):   1 3 4 5 6 8 9 10

注意这里插入和删除可能不成功,因为set中的元素唯一,如果该元素已经存在,就会插入失败。因此insert和emplace其实是有返回值的,它的类型为:pair<set::iterator, bool>,代表返回一个pair,分别是指向该元素的迭代器和插入成功与否的bool值,用.first和.second来获取二者,如下例:

int main() {
	set<int> set1 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
	cout << *set1.insert(4).first << endl;
	cout << set1.insert(5).second << endl;
	cout << set1.insert(8).second << endl;
	return 0;
}
4
0
1

运行结果中,4代表返回的迭代器指向的是4,0代表插入5失败,因为5已经存在于set中了,1代表插入8成功。

遍历
set只能通过迭代器和rangefor进行遍历,迭代器方法在上面的例子中其实已经给出,下面给出rangefor方法的示例。

	for(auto& it:set)
		cout << *it << " " ;
	cout << endl;

相关函数

函数 作用
(c/r)begin() 返回指向第一个元素的(常/反)迭代器
(c/r)end() 返回指向最后一个元素后面的(常/反)迭代器
clear() 清空容器
count() 对某个元素进行计数
empty() 判断容器是否为空
emplace() insert的优化版本
emplace_hint() 依据提示插入(提示可以是eplace()返回的迭代器)
erase() 删除元素
equal_range() 返回pair<lower_bound, upper_bound>
find() 查找元素返回指向该元素的迭代器
get_allocator() 获取分配器
insert() 插入元素返回pair<set<type>::iter, bool>
key_comp() 返回比较准则
value_comp() 返回对value的比较准则
size() 返回容器中元素个数
max_size() 返回可能的最大容量
swap() 交换两个容器
lower_bound() 返回lower_bound
upper_bound() 返回upper_bound

2.map(映射)
map和set一样是非线性树形结构,其内部实现为红黑树,与set不同的是,map以pair<key/value>作为元素进行管理。map以key的排序准则将元素进行排序。此外,map的元素也是唯一的,当然,这个约束也可以靠multimap来取消。
map的特点如下:
1.非线性结构,不能随机访问
2.内部元素有序排列
3.key-value一一映射

创建
容器的创建方法其实大多相似,不过map由于是以key-value的方式存储元素,因此在创建map时需要以<key/value>pair进行初始化。

#include <iostream>
#include <iterator>
#include <string>
#include <map>
using namespace std;
void print_map(const map<int, string> &map1);
int main() {
	map<int, string> map1 = {{1, "Hello"},{2, "World" }};
	map<int, string> map2 = map1;
	map<int, string> map3(map1);
	cout << "map1: "; print_map(map1);
	cout << "map2: "; print_map(map2);
	cout << "map3: "; print_map(map3);
	return 0;
}

增删插入
map提供了和set类似的插入与删除的方法,不同的是map的操作元素和set不同。

#include <iostream>
#include <iterator>
#include <string>
#include <map>
using namespace std;
int main() {
	map<int, string> map1 = { {1, "Hello"},{2, "World" } };
	map1.insert({ 2, "C++" });//用insert插入{ 2, "C++" }
	print_map(map1);
	map1.insert({ 3, "!" });//用insert插入{ 3, "!" }
	print_map(map1);
	map1.emplace(2, "C++");//用emplace插入{ 2, "C++" }
	print_map(map1);
	map1.emplace(3, "!");//用emplace插入{ 3, "!" }
	print_map(map1);	
	map1.erase(2);//移除{2, "World"}
	print_map(map1);
	return 0;
}
{1:Hello} {2:World}
{1:Hello} {2:World} {3:!}
{1:Hello} {2:World} {3:!}
{1:Hello} {2:World} {3:!}
{1:Hello} {3:!}

从结果可以看出,当用insert/emplace进行插入时,如果key不存在,则插入成功,否则插入失败。所以map1中的{2:World}没变为{2:C++},而{3:!}则成功插入。注意map中insert()和emplace()传入参数的方式不同

STL提供了另一个函数来解决这个问题:insert_or_assign() 从名字可以看出它的作用是在map中要插入的key已经存在时,直接将value的值赋值为要插入的值。

int main() {
	map<int,string> map1 = {{ 1,"Hello"},{ 2,"World" }};
	print_map(map1);
	map1.insert_or_assign(2, "C++");
	print_map(map1);
	return 0;
}
{1:Hello} {2:World}
{1:Hello} {2:C++}

另外,由于map中key的唯一性,因此C++提供了下标的方式来索引元素,并且这种方式可以更改value的值。示例如下:

int main() {
	map<int, string> map1 = { {1, "Hello"},{2, "World" } };
	print_map(map1);
	map1.insert({ 2, "C++" });//用insert插入{ 2, "C++" }
	print_map(map1);
	map1[2] = "C++";
	print_map(map1);
	return 0;
}
{1:Hello} {2:World}
{1:Hello} {2:World}
{1:Hello} {2:C++}

这里利用insert插入改变不了key=2对应的value=“World”,而利用下标的方式成功改变value=“C++”。值得提及的是key的类型不是int时也可以作为下标来索引元素,读者可以尝试验证。

遍历
虽然map可以用下标来索引元素,但在不知道key的取值范围的时候是无法用下标来遍历map的,因此常用的遍历map的方式是迭代器方式和rangefor的方式。

//for (map<int, string>::const_iterator it = map1.begin(); it != map1.end(); it++)
for (auto it = map1.begin(); it != map1.end(); it++)
		cout << "{" << it->first << ":" << it->second << "}" << " ";
	cout << endl; 
for(auto& it:map1)
		cout << "{" << it.first << ":"<< it.second << "}" << " " ;
	cout << endl;

相关函数

函数 作用
(c)(r)begin() 返回指向第一个元素的(常)(反)迭代器
(c)(r)end() 返回指向最后一个元素后面的(常)(反)迭代器
clear() 清空容器
count() 对某个元素进行计数
empty() 判断容器是否为空
emplace() insert的优化版本
emplace_hint() 依据提示插入(提示可以是eplace()返回的迭代器)
try_emplace()
erase() 删除元素
equal_range() 返回pair<lower_bound, upper_bound>
find() 查找元素返回指向该元素的迭代器
get_allocator() 获取分配器
insert() 插入元素返回pair<set<type>::iter, bool>
inset_or_assign() key已经存在的时候直接给value赋值
key_comp() 返回比较准则
value_comp() 返回对value的比较准则
size() 返回容器中元素个数
max_size() 返回可能的最大容量
swap() 交换两个容器
lower_bound() 返回lower_bound
upper_bound() 返回upper_bound
at() 下标访问,返回value

(3)容器适配器
容器适配器的英文全称为container adapter,它们本身不是容器,通过改造标准的STL容器来适应特定的场合。其中stack(栈)和queue(队列)是基于deque,而priority_queue(优先队列)则是基于vector实现的。

1.stack(栈)
stack(栈)的特点是后进先出(LIFO),因此所有的顺序容器都满足它的操作要求,而stack默认是建立在deque容器上的。

创建
stack的初始化非常的简单。

#include <iostream>
#include <iterator>
#include <stack>
using namespace std;
int main() {
	stack<int> stack1;
	stack<int> stack2(stack1);
	stack<int> stack3 = stack1;
	return 0;
}

相关函数
stack的函数也很简单。

函数 作用
push() 压栈
emplace() 压栈
pop() 弹栈
top() 返回栈顶元素
size() 栈中元素个数
empty() 判断栈是否为空
swap() 交换两个栈的内容
#include <iostream>
#include <iterator>
#include <stack>
using namespace std;
int main() {
	stack<int> stack1;
	stack<int> stack2(stack1);
	stack1.push(1);
	stack1.emplace(2);
	stack1.push(3);
	stack1.emplace(4);
	while (stack1.size())//或者while(!stack1.empty())
	{
		cout << stack1.top() << " ";//访问栈顶元素
		stack1.pop();//出栈
	}
	stack1.swap(stack2);
	return 0;
}

黑人问号脸

1.queue(队列)
queue(队列)的特点是先进先出(FIFO),需要操作首元素,顺序容器中vector不能满足它的操作要求,而queue默认是建立在deque容器上的。

创建
queue的初始化与stack类似。

#include <iostream>
#include <iterator>
#include <queue>
using namespace std;
int main() {
	queue<int> queue1;
	queue<int> queue2(queue1);
	queue<int> queue3 = queue1;
	return 0;
}

相关函数
quque的函数和stack也很类似,不过把top()换成了front()和back()。

函数 作用
push() 入队
emplace() 入队
pop() 出队
front() 返回队首元素
back() 返回队尾元素
size() 队列中元素个数
empty() 判断队列是否为空
swap() 交换两个队列的内容
#include <iostream>
#include <iterator>
#include <queue>
using namespace std;
int main() {
	queue<int> queue1;
	queue<int> queue2(queue1);
	queue1.push(1);
	queue1.emplace(2);
	queue1.push(3);
	queue1.emplace(4);
	while (queue1.size())//或者while(!stack1.empty())
	{
		cout << queue1.front() << " ";//访问队首元素
		queue1.pop();//出队
	}
	queue1.swap(queue2);
	return 0;
}

3.priority_queue(优先队列)
priority_queue(优先队列)中元素并不像queue那样元素先进先出,而是具有最高优先级的元素最先出队。其中元素的大小可按默认大小关系,也可以通过重载"<"来规定大小关系。优先队列需要队元素进行随机访问,因此list不能满足要求,只能通过vector和deque来实现,实际上优先队列默认是靠vector来实现的。

创建
priority_queue的初始化比stack和queue要灵活一些,因为其涉及到元素的优先级,因此需要元素能够提供优先级的概念。实际上,就是需要元素能够进行大小比较。C++内置数据结构都有默认的大小比较方法,而自定义数据结构需要通过重载"<“来提供优先级。下面是具体的例子:

#include <iostream>
#include <iterator>
#include <queue>
using namespace std;
struct Point{
	int x, y;
	Point(int a, int b) :x(a), y(b) {};//构造函数
};
bool operator<(const Point &A,const Point &B) {//重载操作符<
	return A.x < B.x;
}
ostream& operator<<(ostream &out, const Point &A) {//重载操作符<<
	out << "(" << A.x << "," << A.y << ")" << " ";
	return out;
}
template<typename T>
void pop_queue(T &q) {//数据出队
	while (q.size())
	{
		cout << q.top() << " ";//访问队首元素
		q.pop();//出队
	}
	cout << endl;
}
int main() {
	Point p1(5, 1), p2(1, 4), p3(2, 2), p4(3, 2);

	priority_queue<int, vector<int>> queue1;//使用默认优先级less<int>
	priority_queue<int, vector<int>, greater<int>> queue2;//更改优先级为greater<int>
	priority_queue<int, deque<int>> queue3;//使用deque<int>
	priority_queue<Point> queue4;//使用自定义数据结构

	//数据入队
	queue1.push(3); queue1.push(2); queue1.push(1); queue1.push(4);
	queue2.push(3); queue2.push(2); queue2.push(1); queue2.push(4);
	queue3.push(3); queue3.push(2); queue3.push(1); queue3.push(4);
	queue4.push(p1); queue4.push(p2); queue4.push(p3); queue4.push(p4);

	//数据出队
	cout << "pop_queue1: "; pop_queue(queue1);
	cout << "pop_queue2: "; pop_queue(queue2);
	cout << "pop_queue3: "; pop_queue(queue3);
	cout << "pop_queue4: "; pop_queue(queue4);
	
	return 0;
}

上面的例子初始化了四个不同的优先队列:
priority_queue<int, vector<int> > queue1;//使用默认优先级less
priority_queue<int, vector<int>, greater<int> > queue2;//更改优先级为greater
priority_queue<int, deque<int> > queue3;//使用deque
priority_queue<Point> queue4;//使用自定义数据结构
其中,第一个优先队列采用vector作为容器,优先规则为默认的less(从大到小)。第二个队列也采用vector作为容器,但配置优先规则为greater(从小到大)。第三个队列采用deque作为容器,优先规则采用默认的less。最后一个队列的元素为自定义数据结构Point,采用默认优先级less,但仍需要对”<"进行操作符重载。从程序中对"<"进行重载的函数里面可以看到,程序采用的x的值作为优先级的判据,因此x的值越大,优先级越高。下面是运行的结果:

pop_queue1: 4 3 2 1
pop_queue2: 1 2 3 4
pop_queue3: 4 3 2 1
pop_queue4: (5,1)  (3,2)  (2,2)  (1,4)

从结果可以看到,当优先级规则为less时,元素的值越大优先级越高因此出队列的顺序时从大到小反之,则按从小到大的顺序出队。从queue4的出队顺序可以看出,出队的顺序是按照x值的大小进行排列的。

相关操作
priority_quque的相关函数和stack是一样的,它不像queue那样提供访问队首和队尾的方法,只提供了top()一个方法来读取队首的元素。具体的函数列表如下,函数的用法也不给出实例了。

函数 作用
push() 入队
emplace() 入队
pop() 出队
top() 返回队首元素
size() 队列中元素个数
empty() 判断队列是否为空
swap() 交换两个队列的内容

(4)总结

至此,我们一共讨论了约5种容器:vector、list、deque、set(multiset)、map(multimap)。以及3种容器适配器:stack、queue、priority_queue。

容器按照在内存中的存储形式分为线性非线性两类,线性的包括:vector、list、deque;非线性的包括set(multiset)和map(multimap)。

  • 访问机制上来看,vector和deque支持随机访问,可通过数字下标来访问容器中的元素;map虽然不支持随机访问,却可以通过key作为下表来访问容器中的元素;其他的,list和map不支持随机访问,因此只能通过迭代器对容器中的元素进行访问。当然,所有的容器提供了对应的迭代器来进行元素访问。

  • 数据结构上来看,vector是利用线性顺序表来实现的,因此在内存中的存储是连续的;list是通过线性链表实现,在内存中的间断的;而deque是介于vector和list之间,在内存中是多个连续的片段组成;map和set和上述三种线性结构的容器不同,它是通过红黑树结构来实现的,红黑树是一种搞笑高效的的自平衡查找二叉树,因此在内存中显然是间断的。从这里我们也可以看出,为什么只有vector和deque支持随机访问,因为它们在内存中的存储形式是连续的。

  • 应用上来看,vector支持随机访问,但进行插入和删除的效率很低,因此多适用于只在末尾进行添加和删除的场合。list不支持随机访问,但由于其链式结构在插入和删除上效率很高,因此适用于经常进行插入和删除的场合。deque既支持随机访问,也有较高的插入删除效率,因此在复杂的情况下,deque作为一种折中也是不错的选择,但大多数情况下,为了追求极致的性能,我们会选择vector或list两种极端的数据结构。而set和map是两个有着特殊功能的关联容器,set是有序集合,其中的元素唯一且有序排列;map是有序映射,它的key/value特性使得我们可以通过键(key)来获取对应的值(value)。

容器适配器实质上就是容器的一种接口转换器,它通过一定的转换机理,将STL中的上述5类容易转换为有特定用途的数据结构提供给开发者更方便的使用。换句话说,容器适配器本身不是容器,它是对容器的一种改造,使之满足我们的特殊要求。

容器适配器为我们提供了三种非常重要的数据结构:stack(栈)、quque(队列)、优先队列(priority_queue),它们都是以依托于现成的容器来实现。

  • 是一种先进后出的数据结构,因此只需要在容器的一端进行增加和删除,由上面的分析可知,vector、deque、list三种线性容器都能满足这个要求。由于vector在内存中线性存储,相对于dque和list更加节省空间,因此大多数情况下将vector作为是实现栈的基本容器。
  • 队列是一种先进先出的数据结构,需要在容器的两端进行增加和删除,只有list和deque满足这个要求,但由于不需要在容器中间进行插入和删除,因此选择deque在空间的利用上更加合理,因此队列经常选择deque来实现。
  • 优先队列中元素出队的顺序是依据优先级来定的,因此需要一种排序算法来计算出各个元素的优先级。在C++中利用堆排序来对优先队列中的元素进行排序。堆排序在这里不展开阐述,但堆排序的实现的依据线性顺序序列,许多基本容器能够提供随机访问的功能,因此使用vector来实现是最好的。当然,也可以利用deque来实现优先队列,但效率上肯定不如vector实现的优先队列好。

综上,我们默认情况下,利用vector来实现stack和priority_quque,而利用deque来实现queue。

2.迭代器(iterator)

在前面我们已经看到,迭代器被用来访问容器中的元素。在使用迭代器的方式上来看,和指针非常的类似,甚至于就已经当作指针来使用了。事实上,迭代器的作用就是为了提供一种访问数据的方式。

容器(container)作为标准模板库中的一个核心内容,给我们提供了非常强大的数据结构的支持,以便于我们能够更加方便、高效的实现我们的算法。为了方便我们对容器中的元素进行操作,标准模板库还给我们提供了另一个强大的功能,那就是迭代器。因此,大家把迭代器作为连接容器和算法的纽带

迭代器实际上是对数据访问的一种抽象,他不是一个固定的对象。由于不同的数据结构具有不同的访问方式,因此不同的容器需要给我们提供不同的迭代器。

(1)迭代器操作
迭代器的行为类似于指针,因此它也有类似指针的操作,主要分为下面五类操作:

操作 作用
读操作 返回迭代器指向元素的引用(=*iter)
写操作 对迭代器所指元素进行更改(*iter=)
成员访问 访问迭代器所指元素的成员(iter->member、[])
迭代 令迭代器指向前(后)一个元素(iter++、iter–、++iter、–iter)
比较 对迭代器对象进行比较(==、!=、>、<、>=、<=)

注意,并不是所有的迭代器都具有这5个功能!

(2)迭代器分类
根据迭代器能执行的操作可将迭代器分为下面五类:

类别 操作
输入迭代器 *p=、++
输出迭代器 =*p、->、++、==、!=
前向迭代器 *p=、=*p、->、++、==、!=
双向迭代器 *p=、=*p、->、++、- -、==、!=
随机访问迭代器 *p=、=*p、->、[]、++、- -、+、-、+=、- =、==、!= 、>、<、>=、<=

(3)容器的迭代器支持
STL中不同容器支持不同的迭代器,下面是它们支持的迭代器类型:

容器 迭代器类型
vector 随机访问迭代器
list 双向迭代器
deque 随机访问迭代器
set 双向迭代器
map 双向迭代器

另外,容器适配器不支持迭代器。

(4)迭代器实例(以vector为例)

定义及初始化
vector提供了下面几种迭代器,这里以int为例:
vector<int>::iterator
vector<int>::const_iterator

vector<int>::reverse_iterator
vector<int>::const_reverse_iterator_iterator

前者可通过迭代器进行读写,而后者不能通过迭代器进行写操作。

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> vec1 = { 1,3,5,7,9 };
	const vector<int> &vec2 = vec1;
	vector<int>::iterator it1 =vec1.begin();
	vector<int>::const_iterator it2 = vec2.end();
	return 0;
}

注1:vec.begin()函数返回指向容器中第一个元素的迭代器;vec.end()函数返回指向容器中最后一个元素后一个位置的迭代器。不要利用vec.end()返回的迭代器进行读写等操作,这样可能导致程序崩溃。
注2:vec.begin()和vec.end()的返回类型为iterator或者const_iterator,这取决于vector的类型。如程序中的vec2是vec1的常引用,因此vec2.end()返回类型为const_iterator,所以需要it2的类型需要声明为const_iterator。
注3:如果要获取普通vector的const_iterator或者reverse_iterator,还可以使用vector提供的vec.cbegin()、vec.rbegin()或者是vec.crbegin()等函数。当然,我们还可以使用C++提供的自动类型auto。

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> vec1 = { 1,3,5,7,9 };
	const vector<int> &vec2 = vec1;
	auto it1 = vec1.begin();
	auto it2 = vec2.end();
	return 0;
}

遍历

迭代器最重要的功能就是对容器中的元素进行访问,下面是利用迭代器对容器进行遍历的程序:
顺序遍历

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> vec1 = { 1,3,5,7,9 };
	for (auto it = vec1.begin(); it != vec1.end(); it++)
		cout << *it << " ";
	cout << endl;
	return 0;
}
1 3 5 7 9

逆序遍历

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> vec1 = { 1,3,5,7,9 };
	for (auto it = vec1.rbegin(); it != vec1.rend(); it++)
		cout << *it << " ";
	cout << endl;
	return 0;
}
9 7 5 3 1

for循环中,循环的结束条件为”it!=vec1.end()”,正说明了vec1.end()函数返回的是容器最后一个元素后一个位置的迭代器。此外,这里没有用”it<vec1.end()”作为结束条件是因为除了vector和queue外其他容器都不提供随机访问迭代器,不能使用“<”、“>”、“<=”、“>=”对迭代器进行比较。因此为了统一,循环条件都写为”it!=vec1.end()”的形式。当然,对于vector和deque,使用”it<vec1.end()”是可以的。

注意,利用迭代器对容器进行遍历的方式并不是C++提供的最简洁的方式。C++还提供了range_based for和lambda函数等方式来对容器进行遍历,这里不展开比较。

(5)迭代器失效
在讨论容器的时候我们提到,当容器中元素增加时,如果当前分配的空间已满,会重新分配一块更大的空间,然后执行复制、销毁等操作。那么这个时候迭代器所指向的元素可能发生了变化或者根本已经不存在了,这就是所谓的迭代器失效。
当然,除了增加元素还有许多使迭代器失效的情况,例如删除、插入等。
下面是一个迭代器失效的简单例子:

#include <iostream>
#include <vector>
#include <exception>
using namespace std;
int main() {
	vector<int> vec1 = { 1,3,5,7,9 };
	auto it_begin = vec1.begin();
	auto it_end = vec1.end();
	vec1.pop_back();//vec1.push_back(11);
		for (auto it = it_begin; it != it_end; it++)
		{
			cout << *it << " ";
		}
	cout << endl;
	return 0;
}


上面的程序中,在第7、8行声明了两个迭代器并初始化为vec1.begin()和vec1.end(),随后执行了pop_back方法将容器中最后一个元素移出了,然后企图继续利用上述两个迭代器对容器元素进行遍历。运行的结果告诉我们这里根本得不到运行结果,使用已经失效的迭代器会发生非常严重的问题,使得程序崩溃。
因此,我们在使用迭代器的时候应该知道什么操作会使得结构体失效,从而采取措施进行避免。

3.算法(algorithm)

STL库中关于算法的内容大部分包含在<algorithm>、<numeric>和<algorithm>中,其中<algorithm>包含了大多数算法的函数,而一些关于数值处理的函数在<numeric>中。当我们需要用到函数对象相关内容时,则需要包含<function>文件。

STM库中的算法大致可以分为以下几类:

  • 非更易型算法(nonmodifying algorithm)
  • 更易型算法(modifying algorithm)
  • 移除型算法(removing algorithm)
  • 变序型算法(mutating algorithm)
  • 排序算法(sorted-range algorithm)
  • 数值算法(numeric algorithm)

当然,这样简单的分类时无法将STL中的算法完全分类开来的,会有一些算法会存在多个分类中。

(1)非更易型算法
非更易型算法既不改动元素的顺序,也不改变元素的值。正如前面提高,迭代器是连接算法和容器的纽带,因此,它们可通过迭代器作用于所有的标准容器上
下面列出STL中的非更易型算法:

函数 作用
count() 返回元素的个数
count_if() 返回满足某一条件的元素的个数
min_element() 返回最小值
max_element() 返回最大值
minmax_element() 返回最小值和最大值元素(始于C++11)
find() 查找与目标值相等的第一个元素
find_if() 查找满足某一条件的第一个元素
find_if_not() 查找不满足某一个条件的第一个元素
search_n() 查找满足某特性的前n个元素
search() 查找某个子区间第一次出现的位置
find_end() 查找某个子区间最后一次出现的位置
find_first_of 查找数个可能元素中出现的第一个
adjacent_find() 查找连续两个相等的(或者满足某个条件)的元素
equal() 判断两个区间是否相等
is_permutation() 判断两个不定序区间是否内含相等的元素 (始于C++11)
mismatch() 返回两序列的各组对应值元素中第一对不相等的元素
lexicographical_compare() 判断在“字典序”下某序列是否小于另一序列
is_sorted() 返回区间内是否已有序(始于C++11)
is_sorted_until() 返回区间内第一个乱序元素
is_patitioned() 返回“区间内的元素是否基于某准则被分割为两组”(始于C++11)
partition_point() 返回区间内的一个分割元素,它把元素分为两个组,其中第一个组满足predicate,另一个组则不然(始于C++11)
is_heap() 返回“区间内的元素是否形成一个heap”(始于C++11)
is_heap_until() 返回“区间内第一为满足heap排序准则的元素”(始于C++11)
all_of() 返回“是否所有元素都满足某准则”(始于C++11)
any_of() 返回“是否存在元素满足某准则”(始于C++11)
none_of() 返回“是否无任何元素满足某准则”(始于C++11)

从列表中可以看出,STL中用了find和search两个单词来表示命名查找函数,看起来十分混乱。并且,他们不是分别用来表示查找元素和查找区间,而是混合使用了find和search。这使得记忆和使用十分的不便。
还有一点值得注意的是,由于string class和STL class是分开设计的,因此一些函数并不是通用的,下面列出了两者的区别:

函数作用 String STL
查找某元素第一次出现的位置 find() find()
查找某元素最后一次出现的位置 rfind() find() (利用reverse_iterator)
查找某个子区间第一次出现的位置 find() search()
查找某个子区间最后一次出现的位置 rfind() find_end()
查找某数个可能元素第一次出现的位置 find_first_of() find_first_of()
查找某数个可能元素最后一次出现的位置 find_last_of() find_first_of() (利用reverse_iterator)
n个连续元素第一次出现的位置 / search_n()

由于STL中算法众多,不可能全部进行讨论,下面挑选了几类常用的算法进行讨论,其实遵循STL标准设计的算法用起来都是差不多的。

计数
计数函数为count()和count_if(),二者的函数声明如下:
count(const _InIt _First, const _InIt _Last, const _Ty& _Val)
count_if(_InIt _First, _InIt _Last, _Pr _Pred)
其中前两个参数为输入迭代器,count_if()的第三个参数指定规则。
另外,它们的返回值都是difference_type,是用以表现iterator间距的类型。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
template<typename T>
void print_container(const T &container);
int main() {
	int num;
	vector<int> vec = { 3, 2, 4, 4, 1, 5, 8, 2, 4 };
	print_container(vec);
	num=count(vec.begin(), vec.end(), 4);//count
	cout << "N(element=4): " << num << endl;
	num = count_if(vec.begin(), vec.end(),
				   [](int elem) {return elem > 4; });//count_if
	cout << "N(element>4): " << num << endl;
	num = count_if(vec.begin(), vec.end(),
		           [](int elem) {return elem % 4 == 0; });//count_if
	cout << "N(element%4=0): " << num << endl;
	return 0;
}
Element: 3 2 4 4 1 5 8 2 4
N(element=4): 3
N(element>4): 2
N(element%4=0): 4

注意这里count_if()函数的第三个参数lambda函数。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
template<typename T>
void print_container(const T &container);
int main() {
	int num;
	vector<int> vec = { 3, 2, -4, 4, -1, 5, -8, 2, 4 };
	print_container(vec);
	//最大值
	num = *max_element(vec.begin(), vec.end());
	cout << "Max of value: " << num << endl;
	//绝对值最大值
	num = *max_element(vec.begin(), vec.end(),
				   [](int elem1, int elem2) {return abs(elem1) < abs(elem2); });//count_if
	cout << "Max of absolute value: " << num << endl;
	//最小最大值
	auto nums = minmax_element(vec.begin(), vec.end());
	cout << "Minmax of value : " << *nums.first << "," << *nums.second << endl;

		return 0;
}
Element: 3 2 -4 4 -1 5 -8 2 4
Max of value: 5
Max of absolute value: -8
Minmax of value : -8,5

注意这里minmax_element()函数的返回类型为pair类型。
min_element()和max_element()返回类型为迭代器,即元素所在的位置。

查找
查找分为查找元素查找区间

查找元素有关的算法如下:
_InIt find(_InIt _First, const _InIt _Last, const _Ty& _Val)
_InIt find_if(_InIt _First, const _InIt _Last, _Pr _Pred)
_InIt find_if_not(_InIt _First, const _InIt _Last, _Pr _Pred)
_FwdIt search_n(const _FwdIt _First, const _FwdIt _Last, const _Diff _Count, const _Ty& _Val)
_FwdIt search_n(_FwdIt _First, const _FwdIt _Last,const _Diff _Count_raw, const _Ty& _Val, _Pr _Pred)

三个find函数用法

int main() {
	vector<int>::iterator it;
	vector<int> vec = { 4, -2, -4, 4, 4, -1, 5, -8, 2, 4 };
	print_container(vec);
	//查找4(find)
	it = find(vec.begin(), vec.end(),4);
	if(it!=vec.end())
		cout << "Find value: " << *it << endl;
	//查找第一个大于4的元素(find_if)
	/*it = find_if(vec.begin(), vec.end(),
		bind(greater<int>(), _1, 4));
	if (it != vec.end())
		cout << "Find value>4: " << *it << endl;
	*/
	//查找第一个大于4的元素(find_if)
	it = find_if(vec.begin(), vec.end(),
		[](int val) {return val > 4; });
	if (it != vec.end())
		cout << "Find value>4: " << *it << endl;
	
	//查找第一个小于等于4的元素(find_if_not)
	it = find_if_not(vec.begin(), vec.end(),
		[](int val) {return val > 4; });
	if (it != vec.end())
		cout << "Find value<=4: " << *it << endl;
	return 0;
Element: 4 -2 -4 4 4 -1 5 -8 2 4
Find value: 4
Find value>4: 5
Find value<=4: 4

search_n用法:

int main() {
	vector<int>::iterator it;
	vector<int> vec = { 4, -2, -4, 4, 4, -1, 5, -8, 2, 4 };
	print_container(vec);
	it = search_n(vec.begin(), vec.end(),
		2,
		4);
	if (it != vec.end()) {
		cout << "Find 2 of 4 : " << *(it++) << " ";;
		cout << *it << endl;
	}

	it = search_n(vec.begin(), vec.end(),
				  2,
		          0,
		[](int elem,int val) {return elem < 0; });
	if (it != vec.end()) {
		cout << "Find 2 of negative val : " << *(it++) << " ";;
		cout << *it << endl;
	}
	return 0;
}
Element: 4 -2 -4 4 4 -1 5 -8 2 4
Find 2 of 4 : 4 4
Find 2 of negative val : -2 -4

注意,由于查找不一定成功,所以需在判断返回的迭代器是否有效:
if (it != vec.end()) {}

查找区间有关的算法如下:
_FwdItHaystack search(const _FwdItHaystack _First1, const _FwdItHaystack _Last1,const _FwdItPat _First2, const _FwdItPat _Last2)
_FwdItHaystack search(_FwdItHaystack _First1, const _FwdItHaystack _Last1,const _FwdItPat _First2, const _FwdItPat _Last2, _Pr _Pred)
_FwdIt1 find_end(_FwdIt1 const _First1, const _FwdIt1 _Last1,const _FwdIt2 _First2, const _FwdIt2 _Last2)
_FwdIt1 find_end(_FwdIt1 const _First1, const _FwdIt1 _Last1,const _FwdIt2 _First2, const _FwdIt2 _Last2,_Pr _Pred)

int main() {
	vector<int>::iterator it;
	vector<int> vec = { 4, -2, -4, 4, 4, -1, 5, 4, 4, -1, 5 };
	vector<int> vec1 = { 4, 4, -1, 5 };
	print_container(vec);
	print_container(vec1);
	it = search(vec.begin(), vec.end(), vec1.begin(), vec1.end());
	if (it != vec.end())
		cout << "find vec1 in vec!" << endl;
	it = search(vec.begin(), ++it, vec1.begin(), vec1.end());
	if (it != vec.end())
		cout << "find vec1 in vec again!" << endl;
	return 0;
}
Element: 4 -2 -4 4 4 -1 5 4 4 -1 5
Element: 4 4 -1 5
find vec1 in vec!
find vec1 in vec again!

区间比较
关于区间比较的函数如下:
bool equal(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _First2)
bool equal(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _First2, _Pr _Pred)
区间比较可以比较某个区间内两容器元素是否相等或者满足某一个条件(如互为相反数)。
下面是示例:

int main() {
	bool b;
	vector<int> vec = { 4, -2, -4, 4, 4, -1, 5, 4, 4, -1, 5 };
	vector<int> vec1 = { -4, 2, 4, -4 };
	print_container(vec);
	print_container(vec1);
	b = equal(vec1.begin(), vec1.end(), vec.begin());
	if (b) cout << "true" << endl;
	else cout << "false" << endl;
	b = equal(vec1.begin(), vec1.end(), vec.begin(),
		      [](int elem1, int elem2) {return (elem1 + elem2) == 0; });
	if (b) cout << "true" << endl;
	else cout << "false" << endl;
	return 0;
}
Element: 4 -2 -4 4 4 -1 5 4 4 -1 5
Element: -4 2 4 -4
false
true

上例比较了vec1和vec的前四个元素,第一次比较对应的四个元素是否相等,而第二次比较对应的四个元素是否为相反数。得到的结果为该区间四个元素不等,但它们互为相反数。

上面的equal函数对区间内对应元素进行比较,而is_permutation()函数对区间内函数进行比较,而顺序无所谓。
该函数声明如下:
bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1,_FwdIt2 _First2)
bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1,_FwdIt2 _First2, _Pr _Pred)

int main() {
	bool b;
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	vector<int> vec1 = { 2, 1, 4, 3 };
	print_container(vec);
	print_container(vec1);
	b = is_permutation(vec1.begin(), vec1.end(), vec.begin());
	if (b) cout << "true" << endl;
	else cout << "false" << endl;
	return 0;
}
Element: 1 2 3 4 5 6 7 8 9
Element: 2 1 4 3
true

从上例中可以看出,两个区间的对应元素不等,但元素却不定序相等,这时也判断为相等。

有序判断
bool is_sorted(_FwdIt _First, _FwdIt _Last)
bool is_sorted(_FwdIt _First, _FwdIt _Last, _Pr _Pred)

int main() {
	bool b;
	vector<int> vec0 = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	map<int, string> map0 = { {1, "Michael"}, {2,"Alex"}, {3, "Stark"} };
	b = is_sorted(vec0.begin(), vec0.end());
	if (b) cout << "true" << endl;
	else cout << "false" << endl; 
	b = is_sorted(map0.begin(), map0.end(),
		[](pair<int, string>elem1, pair<int, string>elem2) {
			return elem1.first < elem1.first; 
		});
	if (b) cout << "true" << endl;
	else cout << "false" << endl;
	return 0;
}

存在性判断
bool any_of(const _InIt _First, const _InIt _Last, _Pr _Pred)
bool all_of(_InIt _First, _InIt _Last, _Pr _Pred)
bool none_of(const _InIt _First, const _InIt _Last, _Pr _Pred)
判断容器中存在/全为/不含满足某条件的元素。

int main() {
	bool b;
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	print_container(vec);
	//存在
	b = any_of(vec.begin(), vec.end(),
		[](int elem) {return elem % 5 == 0; });
	if (b) cout << "true" << endl;
	else cout << "false" << endl;
	//全为
	b = all_of(vec.begin(), vec.end(),
		[](int elem) {return elem % 5 == 0; });
	if (b) cout << "true" << endl;
	else cout << "false" << endl;
	//不含
	b = none_of(vec.begin(), vec.end(),
		[](int elem) {return elem > 10; });
	if (b) cout << "true" << endl;
	else cout << "false" << endl;
	return 0;
}
Element: 1 2 3 4 5 6 7 8 9
true
false
true

(2)更易型算法
非更易型算法会对容器区间的内容造成改变。包括利用迭代器进行遍历的过程中造成的改变和将元素从源区间复制到目标区间的过程中造成的改变。
下面列出STL中的更易型算法:

函数 作用
copy() 从首元素开始,复制某个区间
copy_if() 复制某个区间内满足某个条件的元素
copy_n() 复制n个元素(始于C++11)
copy_backward() 从最后一个元素开始,复制某个区间
move() 从首元素开始,搬移某个区间(始于C++11)
move_backward() 从最后一个元素开始,搬移某个区间
transform() 改动(并复制)元素,将两个区间的元素合并
merge() 合并两个区间
swap_ranges() 交换两个区间的内容
fill() 以给定值填充区间所有元素
fill_n() 以给定值填充区间中n个元素
generate 以某项操作的结果替换每一个元素
generate_n() 以某项操作的结果替换n个元素
iota() 将所有元素以一些列的递增值取代(始于C++11)
replace() 将具有某特定值的元素替换为另一个值
replace_if() 将满足某条件的元素替换为另一个值
replace_copy() 复制整个区间,并将具有某特定值的元素替换为另一个值
replace_copy_if() 复制整个区间,将满足某条件的元素替换为另一个值

下面进行简单的介绍:

复制
_OutIt copy(_InIt _First, _InIt _Last, _OutIt _Dest)
_OutIt copy_if(_InIt _First, _InIt _Last, _OutIt _Dest, _Pr _Pred)
_OutIt copy_n(_SourceTy (&_First)[_SourceSize], _Diff _Count_raw, _OutIt _Dest)
注意上述三个函数再调用时需确保目标区间由足够的空间,否则就需要使用insert iterator。

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	vector<int> vec1, vec2, vec3;
	vec1.resize(10);
	vec3.resize(10);
	cout << "vec : "; print_container(vec);
	cout << endl;
	cout << "vec1: "; print_container(vec1);
	cout << "vec2: "; print_container(vec2);
	cout << "vec3: "; print_container(vec3);
	cout << endl;

	//copy
	copy(vec.begin(), vec.begin() + 5, vec1.begin());
	cout << "vec1: "; print_container(vec1);
	//copy_if
	copy_if(vec.begin(), vec.end(), back_inserter<vector<int>>(vec2),
		[](int elem) { return elem % 2 == 0; });
	cout << "vec2: "; print_container(vec2);
	//cpy_n
	copy_n(vec.begin(), 6, vec3.begin());
	cout << "vec3: "; print_container(vec3);

	return 0;
}
vec : Element: 1 2 3 4 5 6 7 8 9

vec1: Element: 0 0 0 0 0 0 0 0 0 0
vec2: Element:
vec3: Element: 0 0 0 0 0 0 0 0 0 0

vec1: Element: 1 2 3 4 5 0 0 0 0 0
vec2: Element: 2 4 6 8
vec3: Element: 1 2 3 4 5 6 0 0 0 0

copy还可以把标准输出当作目标区间,如下:

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	copy(vec.begin(), vec.end(),
		ostream_iterator<int>(cout, " "));
	return 0;
}
1 2 3 4 5 6 7 8 9

搬移
_OutIt move(_InIt _First, _InIt _Last,_OutIt _Dest)
_BidIt2 move_backward(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest)

int main() {
	vector<int> vec0 = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	vector<int> vec1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	vector<int> vec2, vec3;
	vec2.resize(10);
	vec3.resize(10);
	cout << "vec : "; print_container(vec0);
	cout << "vec1: "; print_container(vec1);
	cout << endl;
	cout << "vec2: "; print_container(vec2);
	cout << "vec3: "; print_container(vec3);
	cout << endl;

	//move
	move(vec0.begin(), vec0.end(), vec2.begin());
	move_backward(vec1.begin(), vec1.end(), vec3.end());

	cout << "vec : "; print_container(vec0);
	cout << "vec1: "; print_container(vec1);
	cout << endl;
	cout << "vec2: "; print_container(vec2);
	cout << "vec2: "; print_container(vec3);
	return 0;
}
vec : Element: 1 2 3 4 5 6 7 8 9
vec1: Element: 1 2 3 4 5 6 7 8 9

vec2: Element: 0 0 0 0 0 0 0 0 0 0
vec3: Element: 0 0 0 0 0 0 0 0 0 0

vec : Element: 1 2 3 4 5 6 7 8 9
vec1: Element: 1 2 3 4 5 6 7 8 9

vec2: Element: 1 2 3 4 5 6 7 8 9 0
vec2: Element: 0 1 2 3 4 5 6 7 8 9

转换
_OutIt transform(const _InIt _First, const _InIt _Last, _OutIt _Dest, _Fn _Func)
下面是单个区间的转换:

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	vector<int> vec1;
	cout << "vec : "; print_container(vec);

	//取负,目标区间为自己
	transform(vec.begin(), vec.end(), vec.begin(), negate<int>());
	//乘10,目标区间为vec1
	transform(vec.begin(), vec.end(), back_inserter(vec1),
		bind(multiplies<int>(), _1, 10));
	cout << "vec1: "; print_container(vec1);
	cout << "cout: ";
	//取反,目标区间为标准输出
	transform(vec1.rbegin(), vec1.rend(), ostream_iterator<int>(cout, " "),
		[](int elem) {return -elem; });

	return 0;
}
vec : Element: 1 2 3 4 5 6 7 8 9
vec1: Element: -10 -20 -30 -40 -50 -60 -70 -80 -90
cout: 90 80 70 60 50 40 30 20 10

下面是两个区间的转换:

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	vector<int> vec1= { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	vector<int> vec3;
	//两区间对应元素求积,目标区间为vec3
	transform(vec.begin(), vec.end(), vec1.begin(),
				back_inserter(vec3),
				multiplies<int>());
	print_container(vec3);
	//两区间对应元素求和,目标区间为标准输出
	transform(vec.begin(), vec.end(), vec1.begin(),
				ostream_iterator<int>(cout, " "),
				plus<int>());
	return 0;
}
Element: 9 16 21 24 25 24 21 16 9
10 10 10 10 10 10 10 10 10

互换元素
_FwdIt2 swap_ranges(const _FwdIt1 _First1, const _FwdIt1 _Last1, _FwdIt2 _First2)

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	vector<int> vec1= { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	print_container(vec);
	print_container(vec1);
	cout << endl;
	swap_ranges(vec.begin(), vec.end(), vec1.begin());
	print_container(vec);
	print_container(vec1);
	return 0;
}
Element: 1 2 3 4 5 6 7 8 9
Element: 9 8 7 6 5 4 3 2 1

Element: 9 8 7 6 5 4 3 2 1
Element: 1 2 3 4 5 6 7 8 9

赋值
void fill(_FwdIt _First, _FwdIt _Last, const _Ty& _Val)
_OutIt fill_n(_OutIt _Dest, _Diff _Count_raw, const _Ty& _Val)
void generate(_FwdIt _First, _FwdIt _Last, _Fn _Func)
_OutIt generate_n(_OutIt _Dest, const _Diff _Count_raw, _Fn _Func)
其中,fill用于赋相同值,generator可用于赋不同值。

int main() {
	vector<string> vec;
	//fill_n
	fill_n(back_inserter(vec), 10, "Hello");
	print_container(vec);
	//fill
	fill(vec.begin(), vec.end(), "World!");
	print_container(vec);
	return 0;
}
Element: Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello
Element: World! World! World! World! World! World! World! World! World! World!

下面是generator相关用法:

int _generator() {
	static int n = 0;
	return n++;
}
int main() {
	vector<int> vec;
	//generate_n
	generate_n(back_inserter(vec), 10, _generator);
	print_container(vec);
	//generate
	generate(vec.begin(), vec.end(), _generator);
	print_container(vec);
	return 0;
}
Element: 0 1 2 3 4 5 6 7 8 9
Element: 10 11 12 13 14 15 16 17 18 19

替换
void replace(const _FwdIt _First, const _FwdIt _Last, const _Ty& _Oldval, const _Ty& _Newval)
void replace_if(const _FwdIt _First, const _FwdIt _Last, _Pr _Pred, const _Ty& _Val)
其中,replace将容器中与_Oldval相等的元素全部替换为_Newval。
replace_if将满足一定条件的所有元素替换为_Val。

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 4, 3, 2, 1 };
	print_container(vec);
	//replace
	replace(vec.begin(), vec.end(), 2, 0);
	print_container(vec);
	replace_if(vec.begin(), vec.end(),
		[](int elem) {return elem > 3; }, 0);
	print_container(vec);
	return 0;
}
Element: 1 2 3 4 5 4 3 2 1
Element: 1 0 3 4 5 4 3 0 1
Element: 1 0 3 0 0 0 3 0 1

以上。

(3)移除型算法
移除型算法会根据元素值或者某一准则,将一个区间内的某些元素进行移除。
下面列出所有的移除型算法:

算法 作用
remove() 将等于某个值的元素移除
remove_if() 将满足某个准则的元素移除
remove_copy() 将不等于某一个的元素复制到他处
remove_copy_if() 将不满足某个准则的元素复制到他处
unique() 移除毗邻的相同元素
unique_copy() 移除毗邻的相同元素并复制到他处

**注意:**上述算法都只是将元素移除,后面的元素会依次往前移动,但容器的元素个数不会改变。
remove()与remove_if()
_FwdIt remove(_FwdIt _First, const _FwdIt _Last, const _Ty& _Val)
_FwdIt remove_if(_FwdIt _First, const _FwdIt _Last, _Pr _Pred)

int main() {
	vector<int> vec = { 1, 2, 3, 2, 1, 2, 3, 2, 1 };
	vector<int> vec1 = vec;
	print_container(vec);
	print_container(vec1);
	//remove
	remove(vec.begin(), vec.end(), 2);
	//remove_if
	remove_if(vec.begin(), vec.end(),
		[](int elem) {return elem >= 2; });
	print_container(vec);
	print_container(vec1);
	return 0;
}
Element: 1 2 3 2 1 2 3 2 1
Element: 1 2 3 2 1 2 3 2 1
Element: 1 1 1 1 1 2 3 2 1
Element: 1 2 3 2 1 2 3 2 1

从结果可以看出,remove算法并不改变容器的元素个数,只是将元素向前移动,后面仍然是remove之前的值。

remove()和remove_if()的返回值是移除元素后的新逻辑终点,因此可以用该函数的返回值结合erase方法来将后面的元素删除。

int main() {
	vector<int> vec = { 1, 2, 3, 2, 1, 2, 3, 2, 1 };
	print_container(vec);
	//erase
	vec.erase(remove(vec.begin(), vec.end(), 2),vec.end());
	print_container(vec);
	return 0;
}
Element: 1 2 3 2 1 2 3 2 1
Element: 1 3 1 3 1

从结果可以看出,erase方法将容器的元素个数改变了。

unique()
_FwdIt unique(_FwdIt _First, _FwdIt _Last)

int main() {
	vector<int> vec = { 2, 4, 2, 4, 1, 3, 3, 3, 4};
	print_container(vec);
	//unique
	vec.erase(unique(vec.begin(), vec.end()),vec.end());
	print_container(vec);
	//unique
	vec.erase(unique(vec.begin(), vec.end(), greater<int>()), vec.end());
	print_container(vec);
	return 0;
}
Element: 2 4 2 4 1 3 3 3 4
Element: 2 4 2 4 1 3 4
Element: 2 4 4 4

其中,第一次调用unique移除了毗邻的相等元素,第二次调用unique移除了比前一个元素小的元素。

(3)变序型算法
变序型算法会改变元素的次序,但是不改变元素值。
下面列出变序型算法:

算法 作用
reverse() 将元素的次序反转
reverse_copy() 复制的同时将元素的次序反转
rotate() 旋转元素次序
rotate_copy() 复制的同时旋转元素的次序
next_permutation() 得到元素的下一个排列次序
prev_permutation() 得到元素的上一个排列次序
shuffle() 将元素的次序随机打乱(始于C++11)
random_shuffle() 将元素的次序随机打乱
partition() 改变元素次序,使“符合某准则"者移到前面
stable_partition() 与patition()类似,但保持”与准则相符“和与准则不符”之各个元素之间的相对位置
partition_copy() 改变元素次序,使“符合某准则”者移到前面,过程中并复制元素

以下是排序有关算法:

算法 作用
sort() 对所有元素排序
stable_sort() 对所有元素排序,并保持相等元素之间的相对次序
partial_sort() 部分排序
partial_sort_copy() 部分排序并复制
nth_element() 根据某个位置进行排序
partition() 改变元素次序,使“符合某准则"者移到前面
stable_partition() 与patition()类似,但保持”与准则相符“和与准则不符”之各个元素之间的相对位置
partition_copy() 改变元素次序,使“符合某准则”者移到前面,过程中并复制元素
make_heap() 将某区间转换为一个heap
push_heap() 将元素加入一个heap
pop_heap() 从heap移除一个yuansu
sort_heap() 对heap进行排序(排序完成之后不是heap了)

反转元素次序
void reverse(const _BidIt _First, const _BidIt _Last)
_OutIt reverse_copy(_BidIt _First, _BidIt _Last,_OutIt _Dest)
reverse算法将区间内元素全部反转次序。
list也也提供了reverse方法,该方法将指针反向而非直接将元素进行反转,因此具有更高的性能。

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9};
	print_container(vec);
	//reverse
	reverse(vec.begin(), vec.end());
	print_container(vec);
	//reverse_copy
	reverse_copy(vec.begin(), vec.end(), ostream_iterator<int>(cout, " "));
	return 0;
}
Element: 1 2 3 4 5 6 7 8 9
Element: 9 8 7 6 5 4 3 2 1
1 2 3 4 5 6 7 8 9

旋转元素
_FwdIt rotate(_FwdIt _First, _FwdIt _Mid, _FwdIt _Last)
_OutIt rotate_copy(_FwdIt _First, _FwdIt _Mid, _FwdIt _Last, _OutIt _Dest)
rotate算法将区间内所有元素进行旋转(相当于循环左/右移),结果是_Mid所指元素成为第一个元素。
如下:

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9};
	print_container(vec);
	//rotate
	rotate(vec.begin(),vec.begin()+3, vec.end());
	print_container(vec);
	//rotate_copy
	rotate_copy(vec.begin(), vec.begin()+3, vec.end(), ostream_iterator<int>(cout, " "));
	return 0;
}
Element: 1 2 3 4 5 6 7 8 9
Element: 4 5 6 7 8 9 1 2 3
7 8 9 1 2 3 4 5 6

排列
bool next_permutation(_BidIt _First, _BidIt _Last)
bool next_permutation(_BidIt _First, _BidIt _Last, _Pr _Pred)
bool prev_permutation(_BidIt _First, _BidIt _Last)
bool prev_permutation(_BidIt _First, _BidIt _Last, _Pr _Pred)
这几个算法是用来计算排列的,通过不断调用某个算法可以得到元素的全排列。下例:

int main() {
	vector<int> vec = { 1,2,3};
	//next_permutation
	do {
		print_container(vec);
	}while(next_permutation(vec.begin(),vec.end()));
	return 0;
}
Element: 1 2 3
Element: 1 3 2
Element: 2 1 3
Element: 2 3 1
Element: 3 1 2
Element: 3 2 1

从结果看出,1、2、3总共有六种排列的结果。

打乱顺序
void shuffle(_RanIt _First, _RanIt _Last, _Urng&& _Func)
void random_shuffle(_RanIt _First, _RanIt _Last)
void random_shuffle(_RanIt _First, _RanIt _Last, _RngFn&& _RngFunc)
shuffle使用给定的随机数引擎将区间内的元素打乱;前一个random_shuffle使用一个均匀分布随机数产生器来打乱区间内的元素;后一个random_shuffle使用op来打乱区间内的元素。

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9};
	print_container(vec);
	//next_permutation
	random_shuffle(vec.begin(), vec.end());
	print_container(vec);
	sort(vec.begin(), vec.end());
	print_container(vec);
	shuffle(vec.begin(), vec.end(), default_random_engine(1));
	print_container(vec);
	return 0;
	}

元素前移
_FwdIt partition(_FwdIt _First, const _FwdIt _Last, _Pr _Pred)
_BidIt stable_partition(_BidIt _First, _BidIt _Last, _Pr _Pred)
该算法将满足某准则的元素前移。如下:

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9};
	vector<int> vec1 = vec;
	print_container(vec);
	cout << endl;
	//partition
	partition(vec.begin(), vec.end(),
		[](int elem) {return elem % 2 == 0; });
	print_container(vec);
	//stable_partition
	stable_partition(vec1.begin(), vec1.end(),
		[](int elem) {return elem % 2 == 0; });
	print_container(vec1);
	return 0;
	}
Element: 1 2 3 4 5 6 7 8 9

Element: 8 2 6 4 5 3 7 1 9
Element: 2 4 6 8 1 3 5 7 9

从结果可以看出,partition将所有的偶数都移动到了前面,越靠后的元素位置越靠前;而stable_partition同样将所有的偶数都移动到了前面,但偶数元素之间仍然保持了之前的先后性。

排序
void sort(const _RanIt _First, const _RanIt _Last)
void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred)
void stable_sort(_BidIt _First, _BidIt _Last)
void stable_sort(_BidIt _First, _BidIt _Last, _Pr _Pred)
sort为不稳定排序,stable_sort为不稳定排序。区别在于相同元素经过排序后是否仍然保持之前的先后顺序。

int main() {
		vector<int> vec = { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5};
		print_container(vec);
		sort(vec.begin(), vec.end());
		print_container(vec);
		sort(vec.begin(), vec.end(), greater<int>());
		print_container(vec);
		return 0;
	}
Element: 1 2 3 4 5 1 2 3 4 5
Element: 1 1 2 2 3 3 4 4 5 5
Element: 5 5 4 4 3 3 2 2 1 1

对于自定义类型,可通过三参数或者重载operator"<"来实现排序:
第三参数

struct Point {
	int x, y;
	Point(int a, int b) :x(a), y(b) {};
};
int main() {
	vector<Point> vec;
	vec.push_back(Point(2, 3));
	vec.push_back(Point(4, 4));
	vec.push_back(Point(-6, 3));
	print_container(vec);
	//by x
	sort(vec.begin(), vec.end(),
		[](Point p1, Point p2) {return p1.x<p2.x; });
	print_container(vec);
	//by x*x+y*y
	sort(vec.begin(), vec.end(),
		[](Point p1, Point p2) {return (p1.x*p1.x+p1.y*p1.y) < (p2.x*p2.x + p2.y*p2.y); });
	print_container(vec);
	return 0;
}
Element: 2,3 4,4 -6,3
Element: -6,3 2,3 4,4
Element: 2,3 4,4 -6,3

重载操作符"<"

struct Point {
	int x, y;
	Point(int a, int b) :x(a), y(b) {};
};
bool operator<(Point p1, Point p2) {//重载<
	return p1.x<p2.x;
}
int main() {
	vector<Point> vec;
	vec.push_back(Point(2, 3));
	vec.push_back(Point(4, 4));
	vec.push_back(Point(-6, 3));
	print_container(vec);
	sort(vec.begin(), vec.end());
	print_container(vec);
	return 0;
}
Element: 2,3 4,4 -6,3
Element: -6,3 2,3 4,4

局部排序
void partial_sort(_RanIt _First, _RanIt _Mid, _RanIt _Last)
void partial_sort(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred)
局部排序不会将所有元素进行排序,当已排出前n个最大/最小元素时排序结束。下例:

int main() {
	vector<int> vec = {2, 6, 3, 7, 8, 2, 1, 6, 4, 6, 2 , 8};
	print_container(vec);
	partial_sort(vec.begin(), vec.begin() + 5, vec.end());
	print_container(vec);
	vec.erase(vec.begin() + 5, vec.end());
	print_container(vec);
	return 0;
}
Element: 2 6 3 7 8 2 1 6 4 6 2 8
Element: 1 2 2 2 3 8 7 6 6 6 4 8
Element: 1 2 2 2 3

上例通过partial排序得到前5个值,然后通过erase将后面的元素删除。

根据某个元素排序
void nth_element(_RanIt _First, _RanIt _Nth, _RanIt _Last)
void nth_element(_RanIt _First, _RanIt _Nth, _RanIt _Last, _Pr _Pred)
这里测试出了问题!

Heap算法
Heap可以视为以一定顺序形成的二叉树,它有如下两个性质:
1.首元素总是最大(最小)的。
2.增加/移除数据速度很快(O(lnN))。
STL提供了四个算法用以处理Heap:
void make_heap(_RanIt _First, _RanIt _Last)
void make_heap(_RanIt _First, _RanIt _Last, _Pr _Pred)
void push_heap(_RanIt _First, _RanIt _Last)
void push_heap(_RanIt _First, _RanIt _Last, _Pr _Pred)
void pop_heap(_RanIt _First, _RanIt _Last)
void pop_heap(_RanIt _First, _RanIt _Last, _Pr _Pred)
void sort_heap(_RanIt _First, _RanIt _Last)
void sort_heap(_RanIt _First, _RanIt _Last, _Pr _Pred)
make_heap将区间内的元素转换为heap;push_heap将end之后的最后一个元素加入原本就是heap的区间内,使整个区间形成一个heap;pop_heap将首元素移到最后一个位置,并使剩下的元素形成一个新的heap;sort_heap将一个heap转换为一个有序序列。

int main() {
	deque<int> deq = {3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1 , 2, 3, 4, 5};
	print_container(deq);
	make_heap(deq.begin(), deq.end());//make_heap
	print_container(deq);
	pop_heap(deq.begin(), deq.end());//pop_heap
	deq.pop_back();//pop_back
	print_container(deq);
	deq.push_back(17);//push_back
	push_heap(deq.begin(), deq.end());//push_heap
	print_container(deq);
	sort_heap(deq.begin(), deq.end());//sort_heap
	print_container(deq);
	return 0;
}
Element: 3 4 5 6 7 2 3 4 5 6 1 2 3 4 5
Element: 7 6 5 6 4 3 5 4 5 3 1 2 2 4 3
Element: 6 6 5 5 4 3 5 4 3 3 1 2 2 4
Element: 17 6 6 5 4 3 5 4 3 3 1 2 2 4 5
Element: 1 2 2 3 3 3 4 4 4 5 5 5 6 6 17

上例首先将deq转换为heap,然后通过pop_heap将首元素移到最后的位置,并使用pop_back将该元素删除。之后在使用push_back在末尾添加一个17,并使用push_heap使deq形成一个heap,最后通过sort_heap将该heap转换为一个有序序列输出。

已排序区间算法
以下算法的适用范围为有序区间。

算法 作用
binary_search() 二分查找元素
includes() 判断一个区间中的元素是否包含另一个区间所有元素
lower_bound() 查找第一个大于等于给定值的元素
upper_bound() 查找第一个大于给定值的元素
equal_range() 返回等于某个值的的元素构成的区间
merge() 将两个区间进行合并
set_union() 求两个区间的并集
set_intersection() 求两个区间的交集
set_difference() 返回位于第一个区间而不位于第二个区间的元素构成的有序区间
set_symmetric_differeence() 返回两个区间的并集减去交集的元素构成的区间
inplace_merge() 将两个连贯的已排序区间进行合并
partition_point() 将区间以一定准则进行分割

元素查找
bool binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val)
bool binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred)
binary_search用于检查单个元素是否存在。

int main() {
	deque<int> deq = {3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1 , 2, 3, 4, 5};
	print_container(deq);
	sort(deq.begin(), deq.end());
	print_container(deq);
	auto flag = binary_search(deq.begin(), deq.end(), 4);
	if (flag) cout << "4 exists in data!" << endl;
	else cout << "4 does not exist in data!" << endl;
	return 0;
}
Element: 3 4 5 6 7 2 3 4 5 6 1 2 3 4 5
Element: 1 2 2 3 3 3 4 4 4 5 5 5 6 6 7
4 exists in data!

bool includes(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2)
bool includes(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _Pr _Pred)
includes算法可用于查找多个元素是否存在于某个区间。

int main() {
	deque<int> deq = {3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1 , 2, 3, 4, 5};
	vector<int> vec = { 5, 4, 3, 2, 1 };
	print_container(deq);
	print_container(vec);
	sort(deq.begin(), deq.end());
	sort(vec.begin(), vec.end());
	print_container(deq);
	print_container(vec);
	auto flag = includes(deq.begin(), deq.end(),vec.begin(), vec.end());
	if (flag) cout << "Exists!" << endl;
	else cout << "Does not exist!" << endl;
	return 0;
}
Element: 3 4 5 6 7 2 3 4 5 6 1 2 3 4 5
Element: 5 4 3 2 1
Element: 1 2 2 3 3 3 4 4 4 5 5 5 6 6 7
Element: 1 2 3 4 5
Exists!

这里需要注意的是两个区间必须都是已排序序列!

上述binary_search和includes算法都是返回的查找结果,如果需要得到被查找元素的位置可以通过下面两个算法:
_FwdIt lower_bound(_FwdIt _First, _FwdIt _Last, const _Ty& _Val)
_FwdIt lower_bound(_FwdIt _First, const _FwdIt _Last,const _Ty& _Val, _Pr _Pred)
_FwdIt upper_bound(_FwdIt _First, _FwdIt _Last, const _Ty& _Val)
_FwdIt upper_bound(_FwdIt _First, _FwdIt _Last,const _Ty& _Val, _Pr _Pred)
其中,lower_bound()返回指向与查找元素相等的第一个元素的迭代器,而upper_bound()则返回与查找元素相等的最后一个元素后一位置的迭代器。下例:

int main() {
	list<int> lis = {1, 2, 3, 4, 4, 4, 5, 6, 7, 8, 9};
	auto it = lower_bound(lis.begin(), lis.end(), 4);
	for (; it != lis.end(); it++)
		cout << *it << " ";
	cout << endl;
	it = upper_bound(lis.begin(), lis.end(), 4);
	for (; it != lis.end(); it++)
		cout << *it << " ";
	return 0;
}
4 4 4 5 6 7 8 9
5 6 7 8 9

从结果就可以清晰的看到lower_bound()和upper_bound()的效果有什么不同。

如果要同时获取lower_bound()upper_bound()的结果可以利用如下函数来查找与给定元素相等的区间。
pair<_FwdIt, _FwdIt> equal_range(_FwdIt _First, _FwdIt _Last, const _Ty& _Val)
pair<_FwdIt, _FwdIt> equal_range(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred)
注意这里的返回结果为pair<iterator, iterator>

int main() {
	list<int> lis = {1, 2, 3, 4, 4, 4, 5, 6, 7, 8, 9};
	print_container(lis);
	auto it = equal_range(lis.begin(), lis.end(), 4);
	lis.erase(it.first, it.second);
	print_container(lis);
	return 0;
}
Element: 1 2 3 4 4 4 5 6 7 8 9
Element: 1 2 3 5 6 7 8 9

这里通过equal_range()查找到等于4的元素的区间,然后通过list容器的erase方法将它们删除。

元素合并
_DestTy * merge(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2,_DestTy (&_Dest)[_DestSize])
_DestTy * merge(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2,_DestTy (&_Dest)[_DestSize], _Pr _Pred)
merge算法将两个有序序列进行合并,合并后的序列仍然保持有序。

int main() {
	list<int> lis = {1, 2, 3, 4, 5};
	vector<int> vec = { 3, 4, 5, 6, 7 };
	deque<int> deq;
	print_container(lis);
	print_container(vec);
	merge(lis.begin(), lis.end(), vec.begin(), vec.end(), back_inserter<deque<int>>(deq));
	print_container(deq);
	return 0;
}
Element: 1 2 3 4 5
Element: 3 4 5 6 7
Element: 1 2 3 3 4 4 5 5 6 7

void inplace_merge(_BidIt _First, _BidIt _Mid, _BidIt _Last)
void inplace_merge(_BidIt _First, _BidIt _Mid, _BidIt _Last, _Pr _Pred)
inplace_merge也是将两个有序序列进行合并,不过该两个区间首尾相连。在归并排序中我们会经常进行这种操作,用于归并相邻的区间。

int main() {
	vector<int> vec = {1, 3, 5, 6, 9, 2, 4, 7, 8, 10};
	print_container(vec);
	inplace_merge(vec.begin(), vec.begin() + 5, vec.end());
	print_container(vec);
	return 0;
}
Element: 1 3 5 6 9 2 4 7 8 10
Element: 1 2 3 4 5 6 7 8 9 10

交集、并集与差集
_OutIt set_union(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2, _OutIt _Dest)
_OutIt set_union(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2, _OutIt _Dest, _Pr _Pred)
_OutIt set_intersection(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2, _OutIt _Dest)
_OutIt set_intersection(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2, _OutIt _Dest, _Pr _Pred)
set_uinon算法用于求两个有序序列的并集,set_intersection算法用于求两个有序序列的交集,它们的用法与merge类似。

int main() {
	list<int> lis = {1, 2, 3, 4, 5};
	vector<int> vec = { 3, 4, 5, 6, 7 };
	deque<int> deq,deq1;
	print_container(lis);
	print_container(vec);
	//并集
	set_union(lis.begin(), lis.end(), vec.begin(), vec.end(), back_inserter<deque<int>>(deq));
	print_container(deq);
	//交集
	set_intersection(lis.begin(), lis.end(), vec.begin(), vec.end(), back_inserter<deque<int>>(deq1));
	print_container(deq1);
	return 0;
}
Element: 1 2 3 4 5
Element: 3 4 5 6 7
Element: 1 2 3 4 5 6 7
Element: 3 4 5

_OutIt set_difference(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2,_OutIt _Dest)
_OutIt set_difference(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2,_OutIt _Dest, _Pr _Pred)
_OutIt set_symmetric_difference(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2,_OutIt _Dest)
_OutIt set_symmetric_difference(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _InIt2 _Last2,_OutIt _Dest, _Pr _Pred)
set_difference算法用于求前一个序列中存在而后一个序列中不存在的元素的集合,而set_symmetric_difference算法用于求两个序列非交集元素的集合。
注意,STL中分别提供了上述算法的8个重载,这里仅列出了两个常用的形式。

int main() {
	list<int> lis = {1, 2, 3, 4, 5};
	vector<int> vec = { 3, 4, 5, 6, 7 };
	deque<int> deq,deq1;
	print_container(lis);
	print_container(vec);
	//set_difference
	set_difference(lis.begin(), lis.end(), vec.begin(), vec.end(), back_inserter<deque<int>>(deq));
	print_container(deq);
	//set_symmetric_difference
	set_symmetric_difference(lis.begin(), lis.end(), vec.begin(), vec.end(), back_inserter<deque<int>>(deq1));
	print_container(deq1);
	return 0;
}
Element: 1 2 3 4 5
Element: 3 4 5 6 7
Element: 1 2
Element: 1 2 6 7

数值算法
STL中的数值算法用于数值处理,但也不仅用于数值处理。
下面列出数值处理有关的算法:

算法 作用
accumulate() 对所有元素进行求和、乘积等运算
inner_product() 求两个区间内元素内积
adjacent_difference() 将每个元素与前一个元素进行运算
partial_sum() 将每个元素与之前所有元素进行运算

_Ty accumulate(const _InIt _First, const _InIt _Last, _Ty _Val)
_Ty accumulate(const _InIt _First, const _InIt _Last, _Ty _Val, _Fn _Reduce_op)
上述第一种形式默认对所有元素进行求和,而第二种形式可以指定运算法则。

int main() {
	vector<int> vec = {1, 3, 5, 6, 9, 2, 4, 7, 8, 10};
	print_container(vec);
	//elem1+elem2+...
	auto ret = accumulate(vec.begin(), vec.end(), 0);
	cout << "Sum(elem) = " << ret << endl;
	//elem1*elem2*...
	ret = accumulate(vec.begin(), vec.end(), 1,multiplies<int>());
	cout << "Int(elem) = " << ret << endl;
	//elem1*elem1+elem2*elem2+...
	ret = accumulate(vec.begin(), vec.end(), 0,
		[](int val, int elem) {return val + elem * elem; });
	cout << "Sum(elem*elem) = " << ret << endl;
	return 0;
}
Element: 1 3 5 6 9 2 4 7 8 10
Sum(elem) = 55
Int(elem) = 3628800
Sum(elem*elem) = 385

_Ty inner_product(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _First2, _Ty _Val)
_Ty inner_product(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _Ty _Val, _BinOp1 _Reduce_op, _BinOp2 _Transform_op)
此算法用于对两个区间元素求内积,其第三个参数为初始值。

int main() {
	vector<int> vec1 = {1, 2, 3, 4 ,5};
	vector<int> vec2 = { 5, 4, 3, 2, 1 };
	cout << "vec1: "; print_container(vec1);
	cout << "vec2: "; print_container(vec2);
	//vec&vec
	auto ret = inner_product(vec1.begin(), vec1.end(), vec1.begin(), 0);
	cout << "--> (vec1|vec1) = " << ret << endl;
	//vec&vec1
	ret = inner_product(vec1.begin(), vec1.end(), vec2.begin(), 0);
	cout << "--> (vec1|vec2) = " << ret << endl;
	return 0;
}
vec1: Element: 1 2 3 4 5
vec2: Element: 5 4 3 2 1
--> (vec1|vec1) = 55
--> (vec1|vec2) = 35

_OutIt partial_sum(_InIt _First, _InIt _Last, _OutIt _Dest)
_OutIt partial_sum(const _InIt _First, const _InIt _Last, _OutIt _Dest, _BinOp _Reduce_op)
此算法第一种形式对当前元素之前的元素求部分和,第二形式可指定运算法则。

int main() {
	vector<int> vec1 = {1, 2, 3, 4 ,5};
	vector<int> vec2,vec3;
	//1, 1+2, 1+2+3, ...
	partial_sum(vec1.begin(), vec1.end(), back_inserter<vector<int>>(vec2));
	//1, 1*2, 1*2*3, ...
	partial_sum(vec1.begin(), vec1.end(), back_inserter<vector<int>>(vec3), multiplies<int>());
	print_container(vec1);
	print_container(vec2);
	print_container(vec3);
	return 0;
}
Element: 1 2 3 4 5
Element: 1 3 6 10 15
Element: 1 2 6 24 120

_OutIt adjacent_difference(const _InIt _First, const _InIt _Last, const _OutIt _Dest)
_OutIt adjacent_difference(const _InIt _First, const _InIt _Last, _OutIt _Dest, _BinOp _Func)
此算法的第一种形式用于计算相邻元素的差值;第二种形式可指定相邻元素的运算法则。

int main() {
	vector<int> vec1 = {1, 2, 3, 4 ,5};
	vector<int> vec2,vec3;
	//1, 2-1, 3-2, ...
	adjacent_difference(vec1.begin(), vec1.end(), back_inserter<vector<int>>(vec2));
	//1, 2*1, 3*2, ...
	adjacent_difference(vec1.begin(), vec1.end(), back_inserter<vector<int>>(vec3),
						multiplies<int>());
	print_container(vec1);
	print_container(vec2);
	print_container(vec3);
	return 0;
}
Element: 1 2 3 4 5
Element: 1 1 1 1 1
Element: 1 2 6 12 20

猜你喜欢

转载自blog.csdn.net/weixin_43374723/article/details/83548254
今日推荐