一天学完C++的标准模板库STL

这篇具有很好参考价值的文章主要介绍了一天学完C++的标准模板库STL。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

string字符串

提到字符串,我们会想起C语言中的char *,这是一个指针。而在STL中string也是用来声明字符串的,但是string是一个类,需要导入库#include<string>String封装了char *,管理这个字符串,是一个char*型的容器

string本质上是一个动态的char数组。

如何对string字符串的初始化(声明)?

使用String字符串的第一步,那就是定义、初始化、声明字符串。不要把最简单的东西不当回事,ACM模式下,很容易写不出来。下面介绍声明String字符串的若干方法。

背景定义:封装了一下打印字符串的函数

void printStr(string & str) {
	cout << str << endl;
}
  • 初始化一个空的字符串,这不叫初始化了,这叫定义一个空字符串。(打印为空)
string s1;
printStr(s1); //
  • const char*类型的字符串赋值给string对象,本质上发生的是string类内的运算符重载操作: string& operator=(const char* s)
//直接初始化一个string类型的字符串,但其实是:string = const char *,发生的是运算符重载
string s2 = "hello world";
printStr(s2);//hello world

string类内部还有两个运算符重载操作string& operator=(const string& s)string& operator=(const char c)也就是说,下面两个操作也可以发生:用char字符赋值string对象,用一个已经存在的string对象赋值新的string对象

string s2 = 'c';//string& operator=(const char c)
string s2_2 = s2;//string& operator=(const string& s)
  • 使用const char*类型的字符串调用string类内的有参构造函数string(const char* s),完成string的初始化
//使用const char *初始化string,发生的是调用有参构造函数
string s3("hello world");
printStr(s3);//hello world
  • 跟上面所达到的目的一样,也是调用有参构造函数string(const char* s),只不过把const char*类型的定义写在了外面,各有自己的应用场合
//用const char *类型的字符串初始化为string类型 
const char *c_s = "hello world";
string s4(c_s);
printStr(s4);//hello world
  • 使用string类型的字符串调用string类内的有参构造函数string(const string& str),完成string对象的初始化
//使用string类型的字符串拷贝构造一个string字符串
string s2 = "hello world";
string s5(s2);
printStr(s5);//hello world
  • 截取const char*字符串,调用函数string (const char* s, size_t nn)截取s的前n个字符
//用字符串的前n个字符构造string类
const char* url = "http://www.cplusplus.com/reference/string/string/string/";
string s6(url, 8);
printStr(s6);//http://w
  • 从指定位置开始截取,然后指定截取长度,调用string (const string& str, size_t pos, size_t len)。可以看到printStr(s7)打印的是lo w说明空格也算字符,通过这种索引截取字符串,也能证明string对象本质就是一个char字符数组。
string s2 = "hello world";
//初始化s7为s2的部分字符,从s2的第3个字符开始,往后数4个字符,然后截取下来作为s6的初始值 
string s7(s2, 3, 4); 
printStr(s7);//lo w
//如果截取的字符超过了s2,那就在s2的最后一个字符停下
string s8(s2, 9, 4); 
printStr(s8);//ld
  • nchar字符初始化string类字符串,本质调用的是构造函数string(int n, char c)。必须是单个字符,不能是字符串,多个字符组成的char字符,也只会取最后一个字符。
//用n个char字符构造,一定是字符,而不是字符串
string s9(10, 'x');  
printStr(s9);//xxxxxxxxxx
//如果不是单个字符,就直接读取最后一个字符
string s10(10, 'xs');
printStr(s10);//ssssssssss

必须是单引号字符,不可以是双引号字符串。

一天学完C++的标准模板库STL

可以看出,string类的对象本质上就是一个char字符数组,由一个一个字符组成的数组字符串。

如何遍历string对象进行访问?

我们了解到,string类的对象本质上就是一个char字符数组,由一个一个字符组成的数组字符串。所以,string是可以被遍历的,接下来介绍几种遍历string字符串并访问字符的方法:

  • 普通for循环遍历,使用下标访问。size()函数可以返回string字符串的有效长度。其实这个下标[]本质就是string类内部实现了运算符重载char& operator[](int n);
string s1 = "hello world";
//下标遍历访问法, 
for (int i = 0; i < s1.size(); i++) {
	cout << s1[i];
}
//hello world
  • 普通for循环遍历,然后使用string类内部的成员函数at()来访问对应索引的字符。
string s1 = "hello world";
//at()遍历访问法
for (int i = 0; i < s1.size(); i++) {
	cout << s1.at(i);
}
//hello world
  • 范围for循环遍历,直接拿到的就是string中的字符。通常用auto声明类型,省事。
string s1 = "hello world";
//范围for遍历
for (auto s_item : s1) {
	s_item = 's';
	cout << s_item;
}
//sssssssssss
printStr(s1);//hello world

还有一种情况是这样的:设置为引用类型s_item,这样的话就可以通过修改s_item 来修改外面的string了。不采用引用传递,就像上面一样,不会更改原本的s1的数据。

string s1 = "hello world";
//范围for遍历
for (auto &s_item : s1) {
	s_item = 's';
	cout << s_item;
}
//sssssssssss
printStr(s1);//sssssssssss
  • 正向迭代器遍历。迭代器通过string::iterator来定义,通过begin()end()成员函数来声明。其中it_begin指向string字符串的首字符,it_end指向的不是尾字符,而是尾字符的下一位。因为迭代器也遵循左闭右开。迭代器的本质就是指针,所以取出对应的元素,只需要*it_begin即可。

一天学完C++的标准模板库STL

string s1 = "hello world";
//迭代器遍历(正向),it_begin指向string的第一个字符,it_end指向的则是最后字符的下一个位置,因为迭代器遵循左闭右开原则
string::iterator it_begin = s1.begin();
string::iterator it_end = s1.end();
for (; it_begin != it_end; it_begin++) {
	cout << *it_begin;
}
//hello world
  • 反向迭代器遍历。反向迭代器跟正向迭代器不同,定义方式变为了string::reverse_iterator,声明方式则变成了rbegin()rend()。其中it_begin指向string字符串的尾字符,it_end指向的不是首字符,而是首字符的前一位。反向迭代器遵循左开右闭原则。最最重要的是:it_rbegin的索引仍然是从0开始,而不是从size-1开始。因此,it_rbegin遍历字符串仍要不断++,而不是--

一天学完C++的标准模板库STL

//迭代器遍历(反向),it_rbegin指向的是最后一个字符,it_rend指向的是第一个字符前面的位置,但是反向迭代器的索引位置是反着的,因此it_rbegin向前变量是++而不是--
string::reverse_iterator it_rbegin = s1.rbegin();
string::reverse_iterator it_rend = s1.rend();
for (; it_rbegin != it_rend; it_rbegin++) {
	cout << *it_rbegin << endl;
}
//dlrow olleh

从结果可以看到,这种反向迭代器获取元素的方式会翻转字符串

如何对string类型的字符串进行增删改查?

对string字符串增加一些字符

  • 在尾部增加单个字符,用string类的push_back()成员函数。连续添加,那就相当于添加字符串了。
string s1 = "hello ";
//在尾部插入字符.push_back只能插入单个字符
s1.push_back('w');
s1.push_back('o');
s1.push_back('r');
s1.push_back('l');
s1.push_back('d');
printStr(s1);//hello world
  • 还可以使用string类的成员函数append()直接在尾部添加字符串
string s1 = "hello ";
//在尾部追加字符串,可以是多个字符组成的字符串
s1.append("world");
printStr(s1);//hello world
  • 也可以使用一种最简单暴力的手段,使用运算符重载之后的'+='直接进行append操作。这也能起到在尾部添加字符串的目的。(这个是最常用的
string s1 = "hello ";
//这种也是追加字符串,跟append效果一样
s1 += "world";
printStr(s1);//hello world
  • 使用string类内部的函数string& insert(int pos, const char* s),在pos位置插入const char*风格字符数组。想要实现在尾部插入,那pos就设置为s1.size()。(不推荐使用,因为这个函数本质跟数组插入元素一样,可能会复杂度变高
string s1 = "hello ";
//insert是在任意位置插入元素,只不过我这里选择的是尾部,尽量少用,因为本质是数组插入
s1.insert(s1.size(), "world");
printStr(s1);//hello world

insert函数有三种重载形式:

string& insert(int pos, const char* s); // 在pos位置插入C风格字符数组
string& insert(int pos, const string& str); // 在pos位置插入字符串str
string& insert(int pos, int n, char c); // 在pos位置插入n个字符c

既可以接收const char*,也可以接收const string&

//可以接受两种类型的字符串
const char *ss = "world";
//string ss = "world";
s1.insert(3, ss);
printStr(s1);//helworldlo

还可以传入两个参数,表示插入n个字符c

string s1 = "hello ";
//表示在3位置插入4个'x'
s1.insert(3, 4,'x');
printStr(s1);//helxxxxlo

对string字符串删除一些字符

  • 从某个位置一直删除到字符末尾:string& erase(int pos, int n = npos); // 删除从pos位置开始的n个字符,如果第二个参数缺省,则默认删除到末尾。
string s1 = "hello ";
s1.erase(1);
printStr(s1);//h
  • 删除某一段字符,那就不能缺省第二个参数
string s1 = "hello ";
s1.erase(0, 2);//从第0位开始,删除2个
printStr(s1);//llo
  • 如果删除的个数大于剩余元素的个数,那么就直接删除到尾部。(明显100大于从1开始到末尾的元素个数,)
string s1 = "hello ";
s1.erase(1, 100);//从第1位开始,删除100个
printStr(s1);//h

对string字符串改动一些字符

string的修改比较简单,string类中对[ ]运算符进行了重载,我们可以像数组一样一样直接使用[ ]加下表的方式对string值进行修改。

string s1 = "hello ";
s1[2] = 'p';
printStr(s1);//heplo

在string字符串中查找一些字符

首先介绍一个取出string字符串子串的内置函数:string substr(int pos, int n),获取pos位置开始,n个字符的子串。跟使用构造函数string (const string& str, size_t pos, size_t len)时效果一样,都是截取子串。

string s1 = "hello ";
string sub_str = s1.substr(2, 3);//string substr(int pos, int n),获取pos位置开始,n个字符的子串
printStr(sub_str);//llo

查找字符串中的字符,有一个非常重要的内置成员函数:find()

int find(const string& str, int pos = 0) const; 
// 查找str在当前字符串中第一次出现的位置,从pos开始查找,pos默认为0
int find(const char* s, int pos = 0) const; 
// 查找C风格字符串s在当前字符串中第一次出现的位置,从pos开始查找,pos默认为0
int find(const char* s, int pos, int n) const; 
// 从pos位置查找s的前n个字符在当前字符串中第一次出现的位置
int find(const char c, int pos = 0) const; 
// 查找字符c第一次出现的位置,从pos开始查找,pos默认为0

下面介绍其用法:

  • 由string对象调用,必须手动传入的参数是需要在原字符串s1中查找的子串,即sub_str。find函数的功能就是查找子串在原字符串中的位置,返回第一次出现位置的索引。
string s1 = "hello world";
string sub_str = "llo";
cout << s1.find(sub_str) << endl;//2

当我查找"llo"时,就会首先确定字符串中有没有连续的子串"llo",如果有,再定位第一次出现时子串第一个字符在原字符串中的索引,并返回。

一天学完C++的标准模板库STL

接下来我多多举例,更加清晰地了解find函数的作用。

  • 设置:子串不仅可以是string类型的,也可以是const char*类型的。原字符串中有两处子串,find函数只会找到首次出现的,不会理会第二次出现的

一天学完C++的标准模板库STL

string s1 = "hello hello";
const char* sub_str = "llo";
cout << s1.find(sub_str, 0) << endl;//2
  • 设置:如果从索引为3处开始找,那么就不会找到前面的子串了。在s1[3:]的范围内,索引8出现的子串才是第一次被发现的子串,因此返回8。(注意这里返回的8是看原字符串说的,而不是看查找区间

一天学完C++的标准模板库STL

string s1 = "hello hello";
const char* sub_str = "llo";
cout << s1.find(sub_str, 3) << endl;//8
  • 设置:不仅可以查找子串,还可以查找单个字符(const char sub_str = ‘l’;)。也是根据查找范围,找到第一个出现的索引。

一天学完C++的标准模板库STL

string s1 = "hello hello";
const char sub_str = 'l';
cout << s1.find(sub_str,3) << endl;//3
  • 我们有一个应用场景,需要判断find函数没找到的情况,string内置了一个成员变量string::npos,用来标志find函数的返回值没找到,要记住这个内置的成员变量,以后会有用的。为什么要记住这个变量呢,因为如果find没找到,就会返回4294967295这一串很乱的数字,不利于我们确认标志位。
string s1 = "hello hello";
const char* sub_str = "xxx";	
if (s1.find(sub_str, 3) == string::npos) {
	cout << "没找到" << endl;//没找到
}
else
{
	cout << s1.find(sub_str, 3) << endl;//3
}
  • 小练习:从网址中https://github.com/XHB-ZMM/BTrans-with-LC-for-UnbiasedSGG/tree/master找出域名github.com
string s1 = "https://github.com/XHB-ZMM/BTrans-with-LC-for-UnbiasedSGG/tree/master";
//首先定位://的位置
int pos = s1.find("://");
//然后向后移动三位
int start = pos + 3;
//从://后开始找,直到找到第一个'/'停下
int end = s1.find('/', start);
//start就是域名的开始索引,end-start就是域名所占字符的个数,调用substr函数即可截取域名
string sub_str = s1.substr(start, end-start);
printStr(sub_str);
  • 上述都是从前往后查找,string其实还提供了从后往前查找的函数int rfind(const string& str, int pos = npos) const;默认是从最后一位向前查找,因此第二个参数可以不写。功能就是从pos开始向前查找,找到首次出现的字符str,就返回索引。

一天学完C++的标准模板库STL

string s1 = "hello hello";
const char* sub_str = "llo";
cout << s1.rfind(sub_str) << endl;//8

string类的一些功能性API

对string字符串内的某一段子串进行替换

string& replace(int pos, int n, const string& str); 
// 替换从pos开始n个字符为字符串s
string& replace(int pos, int n, const char* s);
// 替换从pos开始的n个字符为字符串s

一天学完C++的标准模板库STL

string s1 = "hello hello";
printStr(s1.replace(0, 5, "world"));//world hello
  • 假如我要替换的字符串,和前面的数量不吻合,会发生什么?

一天学完C++的标准模板库STL

string s1 = "hello hello";
printStr(s1.replace(0, 7, "world"));//worldello

replace函数的原理是先通过前两个参数定位要被替换的字符串,然后把它们从内存中抹去,然后把第三个参数补在这块区域上,额外多出来的区域自动消失。

两个字符串进行比较,判断是否相等

int compare(const string& s) const; // 与字符串s比较
int compare(const char* s) const; // 与C风格字符数组比较
  • compare函数用来比较两个字符串是否相等。如果相等则会返回0,如果不相等则会返回-1或1。至于是1还是-1,是根据字典序进行比较的,具体详情看这篇博客。
string s1 = "hello hello";
cout << s1.compare("hello hello") << endl;//0

或者使用重载后的’=='也可以判断是否相等。

string s1 = "hello hello";
const char * s2 = "hello hello";
cout << (s1 == s2) << endl;//1

string字符串的大小size和容量capacity

size指的是当前字符串的长度,而capacity指的是内存中存储这个字符串的内存容量。一般不相等,容量会稍微大一些。

string s1 = "hello hello";
cout << s1.size() << endl;//11
cout << s1.capacity() << endl;//15

为什么这么说呢?因为我清空字符串后,容量不变,size正常的清空了。

string s1 = "hello hello";
cout << s1.size() << endl;//11
cout << s1.capacity() << endl;//15
s1.clear();
cout << s1.size() << endl;//0
cout << s1.capacity() << endl;//15

string 和 C-Style 字符串的转换

  • stringconst char*,用的是string内置的c_str()函数
string str = "demo";
const char* cstr = str.c_str();
  • const char*string,直接string(const char* )就行,本质是string有参的构造函数,只不过可以接受const char* 类型的参数
const char* cstr = "demo";
string str(cstr); // 本质上其实是一个有参构造

字符串和数字之间的转换

  • 数字转化为字符串:调用to_string函数。
#include <string>
using namespace std;

/** 带符号整数转换成字符串 */
string to_string(int val);
string to_string(long val);
string to_string(long long val);

/** 无符号整数转换成字符串 */
string to_string(unsigned val);
string to_string(unsigned long val);
string to_string(unsigned long long val);

/** 实数转换成字符串 */
string to_string(float val);
string to_string(double val);
string to_string(long double val);

只需要调用to_string函数即可,就可以将数字转换为字符串,然后就可以按照字符串的要求进行操作了。

int a = 10;
string s1 = to_string(a);
printStr(s1);//10
double a = 10;
string s1 = to_string(a);
printStr(s1);//10.00000
  • 字符串变为数字。如果想要转换为int类型,那就用stoi()函数;如果想要转换为float类型,那就用stof()函数;如果想要转换为double类型,那就用stod()函数。
string s1 = "10";
int a = stoi(s1);
cout << a + 1 << endl;

string s2 = "10.0";
float b = stof(s2);
cout << b + 1 << endl;

string s3 = "10.00000";
double c = stod(s3);
cout << c + 1 << endl;

vector向量

如何声明vector数组,vector数组可以存储哪些类型元素?

vector是STL的动态数组,可以在运行中根据需要改变数组的大小。也就是我们熟知的动态扩容技术

因为它以数组的形式储存,所以它的内存空间是连续的。

vector的头文件为#include<vector>,由于vector仍是在std命名空间下,所以还要using namespace std

背景知识:实现一个接口打印vector里的东西,这里要用泛型函数模板,因为vector<数据类型> 变量名,就是泛型。

template<typename T>
void printVec(vector<T>& v) {
	for (T c : v) {
		cout << c << ' ';
	}
	cout << endl;
}

下面介绍如何声明vector数组:

  • 声明一个空的vector<int>数组,里面啥也没有的那种。根据打印结果,可以看出来vector数组里是什么情况。声明一个空的vector里面什么也没有,所以size等于0,且打印出来的vector为空。
vector<int> v1;
cout << v1.size() << endl;//0
printVec(v1);//
  • 声明一个已知内容的vector<int>数组,里面初始化为10个4,
vector<int> v2 = {4,4,4,4,4,4,4,4,4,4};
cout << v2.size() << endl;//10
printVec(v2);//4 4 4 4 4 4 4 4 4 4
  • 声明一个大小为10的vector<int>数组,第二个参数没写,默认填充为0。
vector<int> v3(10);
cout << v3.size() << endl;//10
printVec(v3);//0 0 0 0 0 0 0 0 0 0
  • 声明一个大小为10的vector<int>数组,第二个参数写了4,那就把10个数全部填充为4
vector<int> v4(10,4);
cout << v4.size() << endl;//10
printVec(v4);//4 4 4 4 4 4 4 4 4 4
  • 声明一个大小为10的vector<float>数组,第二个参数写了4.1,那就把10个数全部填充为4.1
vector<float> v5(10, 4.1);
cout << v5.size() << endl;//10
printVec(v5);//4.1 4.1 4.1 4.1 4.1 4.1 4.1 4.1 4.1 4.1
  • 声明一个大小为10的vector<string>数组,第二个参数写了具体的字符串,那就把10个字符串部填充为 “null”
vector<string> v6(10, "null");
cout << v6.size() << endl;//10
printVec(v6);//null null null null null null null null null null
  • 声明一个大小为10的vector<string>数组,第二个参数写了具体的字符串,那就把10个字符串部填充为 “hello”
vector<string> v7(10, "hello");
cout << v7.size() << endl;//10
printVec(v7);//hello hello hello hello hello hello hello hello hello hello
  • 声明一个大小为10的vector<string>数组,并采用构造函数的方式初始化,本质是用v7从头到尾的内容初始化v8,也就是发生了拷贝构造的操作
vector<string> v8(v7.begin(),v7.end());
cout << v8.size() << endl;//10
printVec(v8);//hello hello hello hello hello hello hello hello hello hello

上述方法都是在声明时就初始化了,如果有这样的需求:声明时不初始化,用到的时候再分配数据给vector,这又该如何实现?好在vector提供了一个内置函数assign(),用来在任意位置分配数据给vector的。 有三种使用形式:

  • 直接像构造函数一样,分配10个整型变量4到v1中
vector<int> v1;
v1.assign(10, 4);
printVec(v1);//4 4 4 4 4 4 4 4 4 4
  • 利用已存在的v1分配v2,assign需要接收两个迭代器,一个是begin,一个是end
vector<int> v1;
v1.assign(10, 4);
printVec(v1);//4 4 4 4 4 4 4 4 4 4

vector<int> v2;
v2.assign(v1.begin(), v1.end());
printVec(v2);//4 4 4 4 4 4 4 4 4 4

当然,也可以使用反向迭代器,还能达到翻转vector的效果呢!

vector<int> v1 = { 1,2,3,4,5,6,7,8,9 };
printVec(v1);//1 2 3 4 5 6 7 8 9

vector<int> v2;
v2.assign(v1.rbegin(), v1.rend());
printVec(v2);//9 8 7 6 5 4 3 2 1
  • 利用普通数组,分配数据给vector数组。此时的assign输入参数本质上还是指针,跟迭代器一样。
int arr[] = { 1,2,3,4,5,6,7,8,9 };
vector<int> v2;
v2.assign(arr, arr + sizeof(arr)/sizeof(int));
printVec(v2);//1 2 3 4 5 6 7 8 9
  • vector还可以存储自定义类型。存结构体或者类 vector<结构体名或者类名> v;比如vector<Person> v
class Person
{
public:
	int m_age;
	string m_name;
	Person(int age, string name) {
		m_age = age;
		m_name = name;
	}
};

vector<Person> v0(5,Person(18,"zmm"));
cout << v0.size() << endl;//10

可以通过迭代器,遍历访问Person对象里的成员变量:

vector<Person>::iterator it_begin = v1.begin();
vector<Person>::iterator it_end = v1.end();

for (; it_begin != it_end; it_begin++) {
	cout << "年龄:" << it_begin->m_age << " 姓名:" << it_begin->m_name << endl;
}
//年龄:18 姓名:zmm
//年龄:18 姓名:zmm
//年龄:18 姓名:zmm
//年龄:18 姓名:zmm
//年龄:18 姓名:zmm

如何遍历vector对象?

迭代器遍历是最专业的遍历方式,分为正向迭代器和反向迭代器。正向迭代器从前向后遍历,反向迭代器从后向前遍历。都遵循左闭右开,即[begin,end)。下面首先介绍这两种迭代器:

  • 正向迭代器,正向迭代器的声明都是容器类型::iterator,由begin()end()方法生成。遍历的话就是从it_begin开始,一直到it_begin == it_end结束,每次it_begin++
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
vector<int>::iterator it_begin = v1.begin();
vector<int>::iterator it_end = v1.end();

for (; it_begin != it_end; it_begin++) {
	cout << *it_begin <<' ';
}
//0,1,2,3,4,5,6,7,8,9
  • 反向迭代器,正向迭代器的声明都是容器类型::iterator,由rbegin()rend()方法生成。使用while循环也是遍历的好方法。遍历的话就是从it_rbegin开始,一直到it_rbegin == it_rend结束,每次it_rbegin++注意:反向迭代器的it_rbegin从末尾开始,但是it_rbegin++是向前走,而不是向后走。
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
vector<int>::reverse_iterator it_rbegin = v1.rbegin();
vector<int>::reverse_iterator it_rend = v1.rend();

while (it_rbegin != it_rend)
{
	cout << *it_rbegin << ' ';
	it_rbegin++;
}
//9 8 7 6 5 4 3 2 1 0

如果给出一幅图可以看懂正向迭代器和反向迭代器,下面的图很合适:

一天学完C++的标准模板库STL

  • 除了使用专业的迭代器遍历,还有更简单的遍历方式,也就是for范围遍历:
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
for (auto item : v1) {
	cout << item << ' ';
}
//0,1,2,3,4,5,6,7,8,9

如何访问vector内的元素

  • at()方法访问
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};

for (int i = 0; i < 10; i++) {
	cout << v1.at(i) << " ";
}
//9 8 7 6 5 4 3 2 1 0
  • 下标[]访问
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
for (int i = 0; i < 10; i++) {
	cout << v1[i] << " ";
}
//9 8 7 6 5 4 3 2 1 0
  • 迭代器指针间接访问
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
	cout << *it << " ";
}
//9 8 7 6 5 4 3 2 1 0
  • 取出vector中的首元素或者尾元素
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
cout << v1.front() << endl;//0
cout << v1.back() << endl;//9

vector容器的增删改查

向vector添加元素

  • push_back()是最经典的向vector尾部添加数据的方法,但是C++11引入了emplace_back()方法,跟push_back()的功能一样,也是向尾部添加。底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
vector<int> v1;

for (int i = 0; i < 10; i++) {
	v1.emplace_back(i);
}
for (int i = 9; i >= 0; i--) {
	v1.push_back(i);
}
printVec(v1);//0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0

无论是push_back还是emplace_back都是向vector的尾部插入元素。最灵活的添加元素方法,还是insert(),任意位置插入任意元素。vector内部重载了三种insert用法,下面一一介绍其用法:

insert(const_iterator pos, T elem); // 在pos位置处插入元素elem
insert(const_iterator pos, int n, T elem); // 在pos位置插入n个元素elem
insert(const_iterator pos, const_iterator beg, const_iterator end); // 将[beg, end)区间内的元素插到位置pos

注意insert()的第一个参数必须是迭代器表示的位置,不能是普通的int变量。

  • 在指定pos位置,插入元素eleminsert(const_iterator pos, T elem)。本例是在pos=v1.begin()也就是第0位插入元素elem=0
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
v1.insert(v1.begin(),0);
printVec(v1);//0 0 1 2 3 4 5 6 7 8 9
  • 在指定pos位置,插入n个元素eleminsert(const_iterator pos, int n, T elem)。本例是在pos=v1.begin() + 3,也就是第3位插入n=3个元素elem=2
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
v1.insert(v1.begin() + 3, 3, 2);
printVec(v1);//0 1 2 [2 2 2] 3 4 5 6 7 8 9
  • 在指定pos位置,插入另一个vector的某一段区间(可能是从头到尾,也可能是截取一部分): insert(const_iterator pos, const_iterator beg, const_iterator end)。本例是在v1pos=v1.end()也就是尾部插入v2[begin, end)区间。
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
vector<int> v2(5, 10);
v1.insert(v1.end(), v2.begin(),v2.end());
printVec(v1);//0 1 2 3 4 5 6 7 8 9 [10 10 10 10 10]

删除vector中的元素

  • 最经典的弹出最后一个元素(也就是删除尾部元素,与push_back对应),所用的方法是:pop_back(),不需要传参。
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
v1.pop_back();//0 1 2 3 4 5 6 7 8
v1.pop_back();//0 1 2 3 4 5 6 7 
v1.pop_back();//0 1 2 3 4 5 6 

还是那个问题,pop_back()只能删除一个尾部元素,而我想要更加灵活的删除方式。vector内部提供了内置函数erase(),可以删除任意位置的任意元素(与insert对应)。

erase(const_iterator start, const_iterator end); // 删除区间[start, end)内的元素
erase(const_iterator pos); // 删除位置pos的元素
  • 删除vector某个区间的批量元素,本例删除的是[v1.begin() + 1, v1.end() - 1)的所有元素,也就只剩下首元素和尾元素了。
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
v1.erase(v1.begin() + 1, v1.end() - 1);
printVec(v1);//0 9
  • 删除pos位置的某一个元素,这个pos也不能是普通的int变量,必须是迭代器指针。本例我删除的是pos=v1.begin(),也就是第0位元素。
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
v1.erase(v1.begin());
printVec(v1);//1 2 3 4 5 6 7 8 9
  • 终极删除法:清空,函数是clear()
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
v1.clear();
printVec(v1);//

改动vector中的元素

  • 跟string类似,直接通过赋值操作,就能覆盖vector原来的值。以string类型的vector为例,可以更改某一处的字符串
vector<string> vs(10,"hello");
vs[1] = "world";
printVec(vs);//hello world hello hello hello hello hello hello hello hello
  • 交换两个vector的内容,使用的是内置函数swap()。本质就是交换指向vector内存空间的指针。
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
vector<int> v2(5, 0);
printVec(v1);//0 1 2 3 4 5 6 7 8 9
printVec(v2);//0 0 0 0 0
v1.swap(v2);
printVec(v1);//0 0 0 0 0
printVec(v2);//0 1 2 3 4 5 6 7 8 9

查找vector中的元素(可以判断是否在vector中以及所在位置)

依然是find()函数。find()函数不仅可以查找string类,还可以用在vector类上。stringfind()函数会直接返回int类型,而用于vector类的find()函数返回的是对应的迭代器类型。然后我们可以通过it - v1.begin()获取索引,单独的it不行,会出错。

  • 本例我们查找的范围是[v1.begin(), v1.end()),也就是整个vector容器,查找的元素是4。返回的是迭代器指针,通过*it可以获取查找到的元素,通过it - v1.begin()可以获取该元素在vector中的索引。
vector<int> v1 = {0,1,2,3,4,5,6,7,8,9};
vector<int>::iterator it = find(v1.begin(), v1.end(), 4);
if (it != v1.end()) {
	cout << "查到的元素是:" << *it << endl;
	cout << "对应的首次出现的索引是:" << it - v1.begin() << endl;
}else
{
	cout << "没找到" << endl;
}
  • 查找string类型的vector也一样。可以查找到是否有字符串在vector中,并定位其索引位置。
vector<string> vs = {"hello", "world" ,"hello" ,"world"};
vector<string>::iterator it = find(vs.begin(), vs.end(), "hello");

if (it != vs.end()) {
	cout << "查到的元素是:" << *it << endl; //hello
	cout << "对应的首次出现的索引是:" << it - vs.begin() << endl; //0
}else
{
	cout << "没找到" << endl;
}

有关vector内存大小的一些操作

查询vector容器中元素的个数

size()函数可以返回当前vector里元素的个数

vector<string> vs = {"hello", "world" ,"hello" ,"world"};
printVec(vs);//hello world hello world
cout << vs.size() << endl;//4

判断vector容器是否为空

empty()可以返回当前vector是否为空,不为空就返回0,为空就返回1

vector<string> vs = {"hello", "world" ,"hello" ,"world"};
printVec(vs);//hello world hello world
cout << vs.empty() << endl;//0,也就是FALSE

重新指定vector容器的长度

vector提供了两个重载函数来实现这个事。总结就是:若容器变长,则填充指定的元素到新位置;若容器变短,则删除超出容器新长度的元素。

void resize(int num); 
// 重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
// 若容器变短,则末尾超出容器长度的元素被删除
void resize(int num, T elem); 
// 重新指定容器的长度为num,若容器变长,则以elem填充新位置。
// 若容器变短,则末尾超出容器长度的元素被删除
  • void resize(int num); 如果扩大容器,默认填充空元素到尾部,容器的size会变化,但是打印容器看不出变化。之前是hello world hello worldresize之后就变成了hello world hello world - -,我向尾部push一个hello,就打印hello world hello world - - hello。如果此时我缩小,那么就会删除多出来的
vector<string> vs = {"hello", "world" ,"hello" ,"world"};
printVec(vs);//hello world hello world
vs.resize(6);
cout << vs.size() << endl;//6
printVec(vs);//hello world hello world 

vs.push_back("hello");
printVec(vs);//hello world hello world    hello

vs.resize(3);
printVec(vs);//hello world hello
  • void resize(int num, T elem); 扩大容器的话,可以向后面填充指定的元素。本例填充的是xxx。可以看到,resize之后,size会变,且多出来的部分被xxx填充。
vector<string> vs = {"hello", "world" ,"hello" ,"world"};
printVec(vs);//hello world hello world
cout << vs.size() << endl;//4
cout << vs.empty() << endl;//0,也就是FALSE

vs.resize(6,"xxx");
cout << vs.size() << endl;//6
cout << vs.capacity() << endl;//6
printVec(vs);//hello world hello world xxx xxx
vs.push_back("hello");
printVec(vs);//hello world hello world xxx xxx hello
vs.resize(3);
printVec(vs);//hello world hello

查看vector容器的容量

依然是capacity函数,多数情况下capacity会略大于size,这个就看内部设置了。

vector<string> vs = {"hello", "world" ,"hello" ,"world"};
cout << vs.size() << endl;//4
cout << vs.capacity() << endl;//4

手动扩容

vector本身是一个动态数组,在size大于等于capacity时会自动扩容。当我明知要添加很多数据时,如果任由动态数组自动扩容,那势必会扩容很多次,造成性能浪费。这时候我们可以手动扩容一个大的容量,避免多次扩容。vector给出了一个合理的方法,使用reserve()函数。

  • 假设初始的vector设置为10个数,然后push100个数进去,看看扩容情况。
vector<int> v1(10, 5);

cout << v1.capacity() << endl;//10
auto current_cap = v1.capacity();

for (int i = 0; i < 100; i++) {
	v1.push_back(i);

	if (v1.capacity() != current_cap) {
		//说明扩容了
		cout << "由 " << current_cap << " 扩容为:" << v1.capacity() << endl;
	
	}
	current_cap = v1.capacity();
}
//由 10 扩容为:15
//由 15 扩容为:22
//由 22 扩容为:33
//由 33 扩容为:49
//由 49 扩容为:73
//由 73 扩容为:109
//由 109 扩容为:163

可以看到,扩容了很多次。这势必会造成资源的浪费。当我把reserve操作放在push大量数据之前,那就会减少扩容的次数,进而提升性能。

vector<int> v1(10, 5);

cout << v1.capacity() << endl;
v1.reserve(100);
auto current_cap = v1.capacity();

for (int i = 0; i < 100; i++) {
	v1.push_back(i);

	if (v1.capacity() != current_cap) {
		//说明扩容了
		cout << "由 " << current_cap << " 扩容为:" << v1.capacity() << endl;
	
	}
	current_cap = v1.capacity();
}
//由 100 扩容为:150

这种操作只有在需要给vector添加大量数据的时候才显得有意义,因为这将避免内存多次容量扩充操作(当vector的容量不足时会自动扩容,当然这必然降低性能)

二维vector的一些碎碎念

二维vector如何初始化呢?vector<vector<int> 空格>即两个vector,里面的vector必须后面跟一个空格,如果没有空格,比如:vector<vector<int>>这会报错。vv是二维向量的名字,row是二维向量的行数,col是二维向量的列数。意思就是初始化row个长度为col的vector,那相当于初始化了一个矩阵一样。

int row, col;
vector<vector<int> > vv(row, vector<int>(col));

比如初始化一个3行4列的二维vector,即矩阵。

vector<vector<int> > vv(3,vector<int>(4));

还可以初始化的时候,全部设置初始化值。比如下面:初始化时设置矩阵里面的值都为elem,例子中初始化一个全为1的3行4列矩阵。

int row, col;
int elem;
vector<vector<int> > vv(row, vector<int>(col,elem));

vector<vector<int> > vv(3,vector<int>(4,1));

当然还可以直接初始化一个自定义的矩阵:都要有大括号包围着。

vector<vector<int> > vv{ {1,2,3,4}, {2,3,4,5}, {3,4,5,6} };

如何遍历二维vector呢?

  • 下标遍历法:二维vectorsize方法返回的是行数,每一行的vectorsize方法返回的是该行中元素的个数。知道了这个原则,就可以使用下标遍历法,两个for循环即可遍历。注意一个细节:用vector初始化的二维数组,不要求每一行的vector长度一致,比如第三行只有三个数。
vector<vector<int> > vv{ {1,2,3,4}, {2,3,4,5}, {3,4,5} };

for (int i = 0; i < vv.size(); i++)
{
	for (int j = 0; j < vv[i].size(); j++)
		cout << vv[i][j] << " ";
	cout << endl;
}
//1 2 3 4
//2 3 4 5
//3 4 5
  • 迭代器遍历法:vv.size返回的是行的个数,说明二维vector的属性都是为行服务的。因此vv.begin返回的其实是指向行的指针,也就是行迭代器。那么取出行的内容,就是所在行的vector。然后再.begin在是遍历当前行中的每一个元素。

一天学完C++的标准模板库STL

vector<vector<int> > vv{ {1,2,3,4}, {2,3,4,5}, {3,4,5} };

for (vector<vector<int>>::iterator row_begin = vv.begin(); row_begin != vv.end(); row_begin++) 
{
	for (vector<int>::iterator it = (*row_begin).begin(); it != (*row_begin).end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

最最最最最重要的一点:二维vector的所有属性都是每一行说的。size返回的是行数,begin返回的是行指针等等

queue队列

queue的基本概念

queue 是一种先进先出(First In First Out, FIFO)的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素只允许从尾部添加元素不允许从尾部弹出元素,只允许从头部弹出元素,不允许从头部添加元素。其示意图如下:

一天学完C++的标准模板库STL

队列queue只有头元素才有可能被外界使用,因此queue不提供遍历功能,也不提供迭代器(不允许遍历,因为队列的目的就是让外界只能从队头访问元素

队列queue的初始化

初始化有两种方式,要么直接初始化一个空的,要么利用已有的拷贝构造。

queue<int> plist;
queue<int> qlist(plist);

队列是不允许赋值初始化的:

一天学完C++的标准模板库STL

向队列中添加元素

向队列中添加元素只能从队尾向队列里添加,采用的是push方法,不是push_back

queue<int> plist;

for (int i = 0; i < 10; i++) {
	plist.push(i);
}

访问队尾元素

队尾元素访问,使用的是back()函数。也只是访问,不能删除。

queue<int> plist;
for (int i = 0; i < 10; i++) {
	plist.push(i);
}
cout << plist.back() << endl;//9

访问队头元素

看明白标题,这里说的是访问,仅仅是访问,不影响原队列。用front()函数front()函数是有返回值的,不能单独书写。

queue<int> plist;

for (int i = 0; i < 10; i++) {
	plist.push(i);
}
cout << plist.front() << endl;//0

删除队头元素(弹出队头)

弹出队头元素,意味着从队列中删除该元素,那么原来老二就变成了新的队头。用的是pop()函数,无返回值,可以单独执行。可以看到下面的例子,弹出前和弹出后的front变了。

queue<int> plist;
for (int i = 0; i < 10; i++) {
	plist.push(i);
}
cout << plist.front() << endl;//0
plist.pop();
cout << plist.front() << endl;//1

查看队列的长度

还是用的size函数,可以看出弹出队头之后,size就减一了。

queue<int> plist;
for (int i = 0; i < 10; i++) {
	plist.push(i);
}
cout << plist.size() << endl;//10
plist.pop();
cout << plist.size() << endl;//9

判断队列是否为空

用的是empty()函数0表示不空,1表示空了。

queue<int> plist;
for (int i = 0; i < 10; i++) {
	plist.push(i);
}
cout << plist.empty() << endl;//0

打印队列

我知道队列不能遍历,也就意味着,不能正常打印。那我就想知道队列中有什么元素怎么办?

queue<int> plist;
for (int i = 0; i < 10; i++) {
	plist.push(i);
}
cout << plist.empty() << endl;//0
while (!plist.empty()) {
	cout << plist.front() << " ";
	plist.pop();
}
// 0 1 2 3 4 5 6 7 8 9
cout << plist.empty() << endl;//1

先获取当前队头元素,然后把队头弹出,继续获取下一个队头元素,然后继续弹出…知道队列为空,所有队头都被挨个打印出来了,那不就完成了打印队列的目的吗(虽然队列最终被清空了)

stack栈

stack的基本概念

栈是一种先进后出(First In Last Out, FILO)的数据结构,它只有一个出口,那就是栈顶。栈底是不允许访问的,想要访问栈的元素,必须一个一个访问栈顶元素top。使用前需要包含stack库:#include<stack>

一天学完C++的标准模板库STL

如何声明一个栈stack

  • 直接声明stack<string> stkS,或者利用已存在的初始化stack<string> stkS_(stkS)。不能赋值初始化stack<string> stkS = {“hello”,“world”}会报错。
stack<string> stkS;
cout << stkS.size() << endl;//0

for (int i = 0; i < 10; i++) {
	stkS.push("zmm");
}

stack<string> stkS_(stkS);
cout << stkS_.size() << endl;//10

向栈中添加元素

queue一样,也是push方法,可以看到push之后,size变大了

stack<string> stkS;
cout << stkS.size() << endl;//0

for (int i = 0; i < 10; i++) {
	stkS.push("zmm");
}
cout << stkS.size() << endl;//10

弹出栈顶元素(也叫从栈中删除栈顶元素)

用的是pop()函数,

stack<string> stkS;

cout << stkS.size() << endl;//0

for (int i = 0; i < 10; i++) {
	stkS.push("zmm");
}
cout << stkS.size() << endl;//10
stkS.pop();
cout << stkS.size() << endl;//9
stkS.pop();
cout << stkS.size() << endl;//8
stkS.pop();
cout << stkS.size() << endl;//7

访问栈顶元素(仅仅是访问,不删除)

用的是top函数,有返回值,不可独立书写。本例,我一次将0~9入栈,然后返回栈顶元素9,弹出栈顶元素后再次访问新的栈顶元素,是8

stack<int> stkI;
for (int i = 0; i < 10; i++) {
	stkI.push(i);
}
cout << stkI.top() << endl;//9
stkI.pop();
cout << stkI.top() << endl;//8

判断栈是否为空

用的就是empty()函数

stack<int> stkI;
cout << stkI.empty() << endl;//1
for (int i = 0; i < 10; i++) {
	stkI.push(i);
}
cout << stkI.empty() << endl;//0

打印stack元素

只能访问栈顶元素,那就访问一个,弹出一个,这就打印出来了。可以看出,打印的是9 8 7 6 5 4 3 2 1 0,这就叫后入先出。

stack<int> stkI;

for (int i = 0; i < 10; i++) {
	stkI.push(i);
}

for (int i = 0; i < 10; i++) {
	cout << stkI.top() << " ";
	stkI.pop();
}
//9 8 7 6 5 4 3 2 1 0

我就想从栈底打印到栈顶,怎么办?也就是说打印出0 1 2 3 4 5 6 7 8 9,那就得准备两个栈,另一栈接收原来栈的元素。

stack<int> stkI;
stack<int> stkTemp;

for (int i = 0; i < 10; i++) {
	stkI.push(i);
}

for (int i = 0; i < 10; i++) {
	stkTemp.push(stkI.top());//另一个栈不断地入栈,栈顶元素,达到把原来的栈反序的目的。
	stkI.pop();
}

for (int i = 0; i < 10; i++) {
	cout << stkTemp.top() << " ";
	stkTemp.pop();
}
//0 1 2 3 4 5 6 7 8 9

deque双向队列

deque的基本概念

普通队列queue只能在尾部插入和在头部删除。deque相对于queue来说,可以在两端进行添加和删除操作。具体如下图所示:

一天学完C++的标准模板库STL

相对于vector来说,deque最大的优势就是在头部插入元素时,效率依然是O(1)。因为deque在空间中并不是连续内存,而是由一个中控器管理,貌似是连续存储元素,其实不是。

deque的初始化(声明、定义)

deque内部重载了多种构造函数,因此依然有很多种初始化方式

deque<T> deqT; // 默认构造函数
deque(beg, end); // 构造函数将[beg, end)区间中的元素拷贝给本身
deque(int n, T elem); // 构造函数将n个elem拷贝给本身
deque(const deque& deq); // 拷贝构造函数

这里就不一一介绍了,因为这种初始化方式跟vector几乎一样,自己看吧:

deque<int> deq1;
printDeq(deq1);//

deque<int> deq2(10, 0);
printDeq(deq2);//0 0 0 0 0 0 0 0 0

deque<int> deq3 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq3);//1 2 3 4 5 6 7 8 9

deque<int> deq4(deq3.begin(), deq3.end());
printDeq(deq4);//1 2 3 4 5 6 7 8 9

deque<int> deq5(deq4);
printDeq(deq5);//1 2 3 4 5 6 7 8 9

队列的赋值操作

deque也有assign()函数,用于分配现有的数据给新队列。

deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//1 2 3 4 5 6 7 8 9

deque<int> deq2;
deq2.assign(deq1.begin()+1, deq1.end()-1);
printDeq(deq2);//1 2 3 4 5 6 7 8

deque也有交换函数,swap()函数,可以交换

deq1.swap(deq2);
printDeq(deq1);//1 2 3 4 5 6 7 8
printDeq(deq2);//1 2 3 4 5 6 7 8 9

队列的内存大小API

查看队列中的元素个数,依然用size()函数

deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//1 2 3 4 5 6 7 8 9

cout << deq1.size() << endl;//10

判断队列是否为空,依然用empty()函数

deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//1 2 3 4 5 6 7 8 9

cout << deq1.empty() << endl;//0

重新指定队列的size,多的填充,少的去掉,用的是resize函数,跟vector一样。

void resize(int num); 
// 重新指定容器的长度为num,若容器变长,则以默认值填充新位置,
// 如果容器变短,则末尾超出容器长度的元素被删除
void resize(int num, T elem);
// 重新指定容器的长度为num,若容器变长,则以elem填充新位置,
// 如果容器变短,则末尾超出容器长度的元素被删除

注意:deque没有容量的概念,deque都是动态的生成存储空间,没有容量的概念,因此不能使用capacity函数。

遍历双向队列deque

迭代器遍历是最经典的:

deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//1 2 3 4 5 6 7 8 9

for (deque<int>::iterator it = deq1.begin(); it != deq1.end(); it++) {
	cout << *it << " ";
}
//1 2 3 4 5 6 7 8 9

虽然deque容器也提供了Random Access Iterator,但是它的迭代器并不是普通的指针,其复杂度和vector不是一个量级,这当然影响各个运算的层面。因此,在需要遍历容器的时候,除非有必要,我们应该尽可能的使用vector,而不是 deque。

例如:对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque

deque的双端插入与删除

这是deque最重要的特性,可以在双端进行删除与插入。这也是deque唯一的优点。

  • 插入元素,push操作,如果在尾部插入就是push_back(),如果在头部插入那就是push_front()
deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//1 2 3 4 5 6 7 8 9

deq1.push_back(-1);
deq1.push_front(10);
printDeq(deq1);//10 0 1 2 3 4 5 6 7 8 9 -1

可以看到,前面的才叫头部,后面的才叫尾部,顺序要搞懂。

  • 删除(弹出)元素,用的是pop操作,如果是弹出尾部元素就是pop_back(),如果是弹出队头元素那就是pop_front()
deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

deq1.push_back(-1);
deq1.push_front(10);
printDeq(deq1);//10 0 1 2 3 4 5 6 7 8 9 -1

deq1.pop_back();
deq1.pop_front();
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

弹出元素并不是返回元素,因此没有返回值,需要单独书写。

访问双向队列的队头和队尾元素

只是简单的访问:访问头元素用的是front()函数,访问尾元素用的是back()

deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

cout << deq1.front() << endl;//0
cout << deq1.back() << endl;//9

别跟栈的弄混了,顶元素用的是top函数,队列的队头用的是front函数。

在双向队列deque的任意位置插入元素

用的也是insert函数,内置了三种重载函数。

const_iterator insert(const_iterator pos, T elem); 
// 在pos位置处插入元素elem的拷贝,返回新数据的位置
void insert(const_iterator pos, int n, T elem); 
// 在pos位置插入n个元素elem,无返回值
void insert(pos, beg, end);
// 将[beg, end)区间内的元素插到位置pos,无返回值
  • 第一个,插入之后有返回值的,返回的是迭代器,想要打印出来迭代器的位置,需要it - deq1.begin(),我们之前讲过。
deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

deque<int>::iterator it = deq1.insert(deq1.begin()+1, 100);
cout << it - deq1.begin() << endl;//1
printDeq(deq1);//0 100 1 2 3 4 5 6 7 8 9

可以看到,把100插入到队列的pos=deq1.begin()+1,也就是第1位。

  • 第二个,无返回值,直接插入2100在第1
deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

deq1.insert(deq1.begin()+1, 2, 100);
printDeq(deq1);//0 100 100 1 2 3 4 5 6 7 8 9
  • 第三个,把另一个容器的某一块区间插入到pos位置。本例是队列pos=deq1.begin()+1的位置插入vector的数据。
deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

vector<int> v1 = { 2,3,2,3 };
deq1.insert(deq1.begin()+1, v1.begin(),v1.end());
printDeq(deq1);//0 2 3 2 3 1 2 3 4 5 6 7 8 9

deque的删除操作

清空的话,还是用clear函数

deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

cout << deq1.empty() << endl;//0
deq1.clear();
cout << deq1.empty() << endl;//1

删除指定pos位置的元素,用的还是erase函数。提供了两个重载函数。

iterator erase(iterator beg, iterator end);
// 删除区间[beg, end)的数据,返回下一个数据的位置
iterator erase(iterator pos)// 删除pos位置的数据,返回下一个数据的位置
  • 删除队列某一个区间的所有元素,然后返回删除之后队列的下一个数据的位置(返回的是迭代器)。比如本例:删除的是 2 3 4 5 6 7,那么下一个数据就是8,然而在更新之后的队列中,8所在的索引位置是2,所以返回的是2,而不是原来队列8的索引。
deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

deque<int>::iterator it = deq1.erase(deq1.begin()+2, deq1.end()-2);
cout << it - deq1.begin() << endl; // 2
printDeq(deq1);// 0 1 8 9
  • 删除某一个元素,返回的就是下一个元素在新队列中的索引。
deque<int> deq1 = { 0,1,2,3,4,5,6,7,8,9 };
printDeq(deq1);//0 1 2 3 4 5 6 7 8 9

deque<int>::iterator it = deq1.erase(deq1.begin() + 1);
cout << it - deq1.begin() << endl; // 1
printDeq(deq1);// 0 2 3 4 5 6 7 8 9

list链表

STL的list双向链表(要明白一个点啊:节点内部的prevnext我们是看不见的,也访问不了。STL中的list就是一个线性表。只不过在设计链表的时候,要用到prevnext。我们今后在使用链表的时候,只需要了解到链表的一些API即可。)需要包含头文件#inclined<list>

一天学完C++的标准模板库STL

下面介绍一下,双向链表list的各种使用范式:

list的初始化(定义&声明)

背景知识,定义了一个打印list内容的函数:

template<class T>
void printList(list<T> &deq) {
	for (T item : deq) {
		cout << item << " ";
	}
	cout << endl;
}

list也是容器的一种,也支持泛型模板,本例我们存储string字符串,初始化方式有以下几种,跟vector差不多:

list<string> list0;
printList(list0);//0
cout << list0.size() << endl;//

list<string> list1 = {"hello","world","ZMM"};
printList(list1);//hello world ZMM

list<string> list2(list1.begin(), list1.end());
printList(list2);//hello world ZMM

list<string> list3(list1);
printList(list3);//hello world ZMM

list<string> list4(5, "hello");
printList(list4);//hello hello hello hello hello

访问链表头元素和链表尾元素

跟双向队列一样,还是front函数和back函数。

list<int> list1 = { 0,1,2,2,2,2,2,7,8,9 };
cout << list1.front() << endl;//0
cout << list1.back() << endl;//9

list元素的插入和删除操作

在链表list的头尾进行插入和删除

双向链表的头尾都可以操作,跟双向队列的API名字一样。往尾部添加元素用push_back,往头部添加元素用push_front,删除尾部元素用pop_back,删除头部元素用pop_front。通过本例的打印,可以看出每一个API的使用效果。

list<string> list1 = {"hello","world"};
printList(list1);//hello world

list1.push_back("ZMM");
printList(list1);//hello world ZMM
list1.push_back("XHB");
printList(list1);//hello world ZMM XHB

list1.push_front("C++");
printList(list1);//C++ hello world ZMM XHB

list1.pop_back();
printList(list1);//C++ hello world ZMM
list1.pop_back();
printList(list1);//C++ hello world

list1.pop_front();
printList(list1);//hello world

在链表list的任意位置插入或者删除

插入函数,提供了三种重载方法。

insert(iterator pos, elem); // 在pos位置插入elem元素的拷贝,返回新数据的位置
insert(iterator pos, n, elem); // 在pos位置插入n个elem元素的拷贝,无返回值
insert(iterator pos, beg, end); // 在pos位置插入[beg, end)区间内的数据,无返回值
  • 插入仍是用的insert函数,本例中我们先查到到2的位置,然后插入元素10。
list<int> list1 = { 0,1,2,3,4,5,6,7,8,9 };

list<int>::iterator pos = find(list1.begin(),list1.end(),2);
list1.insert(pos, 10);
printList(list1);//0 1 10 2 3 4 5 6 7 8 9
  • 插入5个10
list<int> list1 = { 0,1,2,3,4,5,6,7,8,9 };

list<int>::iterator pos = find(list1.begin(),list1.end(),2);
list1.insert(pos, 5,10);
printList(list1);//0 1 10 10 10 10 10 2 3 4 5 6 7 8 9
  • 插入另一个数据结构的若干元素
list<int> list1 = { 0,1,2,3,4,5,6,7,8,9 };
list<int> list2 = { 8,8,8,8 };

list<int>::iterator pos = find(list1.begin(),list1.end(),2);
list1.insert(pos, list2.begin(),list2.end());
printList(list1);//0 1 8 8 8 8 2 3 4 5 6 7 8 9

删除函数依然用的是erase函数,提供两个重载函数。

erase(beg, end); // 删除[beg, end)区间内的所有数据,返回下一个数据的位置
erase(pos); // 删除pos位置的数据,返回下一个数据的位置

链表的迭代器没有重载+运算符,因此不能手动移动任意位置了,只能删除链表中已知存在的元素。

一天学完C++的标准模板库STL

  • 从头删除到尾
list<int> list1 = { 0,1,2,3,4,5,6,7,8,9 };

list1.erase(list1.begin(), list1.end());
printList(list1);//
  • 删除指定元素:先通过find函数查找到pos,然后删除。
list<int> list1 = { 0,1,2,3,4,5,6,7,8,9 };
list<int>::iterator pos = find(list1.begin(),list1.end(), 2);
list1.erase(pos);
printList(list1);//0 1 3 4 5 6 7 8 9

删除链表中所有等于elem的元素

用的是一个新函数:remove(),可以删除链表中等于elem的所有元素。

list<int> list1 = { 0,1,2,2,2,2,2,7,8,9 };
list1.remove(2);
printList(list1);//0 1 7 8 9

清空list元素,并判断是否为空

通过这个例子了解到,链表是否为空还是用的empty函数,判断里面的元素个数还是用的size函数,清空还是用的clear函数。

list<int> list1 = { 0,1,2,3,4,5,6,7,8,9 };
cout << list1.empty() << endl;//0
list1.clear();
cout << list1.empty() << endl;//1
cout << list1.size() << endl;//0
printList(list1);//

翻转链表

用的是reverse函数,直接调用也行list1.reverse(),传参数进去也行reverse(list1.begin(),list1.end())

list<int> list1 = { 0,1,2,3,4,5,6,7,8,9 };
list1.reverse();
//reverse(list1.begin(),list1.end());
printList(list1);//9 8 7 6 5 4 3 2 1 0

排序链表

用的是sort函数,直接调用也行list1.sort(),传参数进去也行sort(list1.begin(),list1.end())

list<int> list1 = { 0,1,2,5,4,3,6,8,7,9 };
list1.sort();
//sort(list1.begin(),list1.end());
printList(list1);//0 1 2 3 4 5 6 7 8 9

set集合

set 容器基本概念

set翻译为集合,是一个内部自动有序且不含重复元素的容器。set这个容器最大的特点就是不允许有重复元素,且会自动排序,即使初始化的时候是一个乱序的。需要包含头文件#inclined<set>

set<int> set1 = { 0,1,2,3,4,5,6,9,8,7 };
printSet(set1);//0 1 2 3 4 5 6 7 8 9

本例中可以看到,我初始化的时候是乱序的,但是打印出来就是顺序的。

set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };
printSet(set1);//0 1 3 7 8

本例中可以看到,我初始化的时候有一一堆3,但是打印的时候被去重了。

这两个例子充分展现了集合set的特点!

set的初始化操作

跟其它容器差不多,也是下面的一些操作,这里就不赘述了

set<int> set0;
set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };
set<int> set2(set1);
set<int> set3(set1.begin(), set1.end());
printSet(set3);//0 1 3 7 8

set的遍历访问

set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };

for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
	cout << *it << " ";
}
//0 1 3 7 8

set只能通过迭代器访问!

set容器也不能迭代器+1,(能迭代器+1的只有vector,string,deque

一天学完C++的标准模板库STL

set的插入和删除操作

  • 插入函数还是用的insert
set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };

set1.insert(5);

for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
	cout << *it << " ";
}
//0 1 3 5 7 8

但是,这里还隐藏着一个新知识:insert是有返回值的,返回的是一个pair结构。其中第一个位置返回迭代器,第二个位置返回是否插入成功。pair这种数据结构,通过first方法和second方法访问不同位置的返回值。可以看到:取出迭代器的值就是我们插入的值,返回是否插入成功,输出的就是1。

set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };
pair<set<int>::iterator, bool> pair;
pair = set1.insert(5);
cout << *pair.first << endl;//5
cout << pair.second << endl;//1

for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
	cout << *it << " ";
}
//0 1 3 5 7 8
  • 清空set还是用的clear
set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };
set1.clear();
for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
	cout << *it << " ";
}
//
  • 删除指定pos的元素,由于set也不能迭代器+1,因此只能指定头尾的迭代器然后删除头尾元素,没法删除指定位置的中间元素
set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };
for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
	cout << *it << " ";
}
//0 1 3 7 8
set1.erase(set1.begin());
set1.erase(--set1.end());
printSet(set1);//1 3 7
  • 删除set容器中为elem的元素,这个是最常用的set删除操作。本例中从容器中删除的是3。
set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };
for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
	cout << *it << " ";
}
//0 1 3 7 8
set1.erase(3);
printSet(set1);//0 1 7 8

set的查找操作(可以说是set最重要API)

  • 查找elem是否在set中,如果在就返回对应的迭代器,如果不在就返回set.end()。依然用的是find方法
set<int> set1 = { 0,1,3,3,3,3,3,4,8,7 };
printSet(set1);//0 1 3 4 7 8
set<int>::iterator it = set1.find(3);
cout << *it << endl;//3
  • 统计setelem的个数,因为set是去重集合,因此count函数不是返回1就是0可以用来判断elem是否在set中
set<int> set1 = { 0,1,3,3,3,3,3,4,8,7 };
printSet(set1);//0 1 3 4 7 8
cout << set1.count(3) << endl;//1
  • 查找set中大于等于elem的第一次出现的迭代器。本例中查找的是4,因此返回大于等于4的第一次出现的迭代器
set<int> set1 = { 0,1,3,3,3,3,3,4,8,7 };
printSet(set1);//0 1 3 4 7 8
set<int>::iterator it_lower = set1.lower_bound(4);
cout << *it_lower << endl;//4
  • 查找set中大于elem的第一次出现的迭代器,本例中查找的是4,因此返回大于4的第一次出现的迭代器
set<int> set1 = { 0,1,3,3,3,3,3,4,8,7 };
printSet(set1);//0 1 3 4 7 8
set<int>::iterator it_upper = set1.upper_bound(4);
cout << *it_upper << endl;//7

pair数据结构的使用

set<int> set1 = { 0,1,3,3,3,3,3,3,8,7 };

set<int>::iterator k, bool v;
pair<set<int>::iterator, bool> p(k, v);

pair<set<int>::iterator, bool> p;
p.first, p.second;

multiset的使用(set的进化版,可以存在重复元素)

multiset也是集合,只不过允许集合中有重复的元素,仍然是会自动排序

所以说,multiset的应用场景比set更广泛。跟set一样,只需要#inclined<set>,这两个共有一个头文件。

通过下面的例子可以看出:multiset不会去掉重复的元素,但是仍然会给元素进行排序,默认就是递增排序。当我们统计3的个数,也能准确地给出5个。

multiset<int> set1 = { 0,1,3,3,3,3,3,4,8,7 };
printSet(set1);//0 1 3 3 3 3 3 4 7 8

multiset<int>::iterator it = set1.find(3);
cout << *it << endl;//3

for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
	cout << *it << " ";
}
//0 1 3 3 3 3 3 4 7 8

cout << set1.count(3) << endl;//5

想要改变排序的规则,可以自定义仿函数,然后在声明set的时候传入multiset<int, MyCompare> set1,模板类第二个参数也是可以传参的。这样就可以办到递减排序。

class MyCompare
{
public:
	bool operator()(int v1, int v2)
	{
		return v1 > v2;
	}
};

multiset<int, MyCompare> set1 = { 0,1,3,3,3,3,3,4,8,7 };
for (multiset<int>::iterator it = set1.begin(); it != set1.end(); it++) {
	cout << *it << " ";
}
//8 7 4 3 3 3 3 3 1 0

map映射(类似于python中的字典)

map基本概念

map是具有唯一键值对的容器,通常使用红黑树实现。

map中的键值对是 key value 的形式,都是pair结构,并且所有的元素都会根据元素的键值自动排序;

map不允许两个元素有相同的键值;但是multimap可以拥有相同的键值。使用时需要包含头文件:#include <map>

map的声明初始化操作

定义形式如下所示:必须遵循pair结构,第一个是key的数据类型,第二个是value的数据类型。

map<key_type, value_type>变量名

初始的方式有两种,一种是默认的构造函数,一种是拷贝构造函数

map<T1, T2> mapTT; // map默认构造函数
map(const map& mp); // 拷贝构造函数

下面的例子展现了初始化以及赋值的一些操作:

map<string, int> map1;
map1["ZMM"] = 18;
map1["XHB"] = 18;
map<string, int> map2(map1);
cout << map2["ZMM"] << endl;//18

遍历map里的元素

使用迭代器遍历,想要打印key那就打印it->first,想要打印value那就打印it->second

map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));

for (map<string, int>::iterator it = map1.begin(); it != map1.end(); it++)
{
	cout << "key = " << it->first << " value = " << it->second << endl;
}
//key = XHB - EGA value = 18
//key = XHB - MONEY value = 0
//key = ZMM - EGA value = 18
//key = ZMM - MONEY value = 0

map会自动的按照key进行排序,所以,实际的map跟初始化的map顺序可能不一致。

map插入数据的操作

  • 直接增通过下标法,插入数据(在这个过程中就表明了keyvalue都是啥)
map<string, int> map1;
map1["ZMM-EGA"] = 18;
map1["XHB-EGA"] = 18;
map1["ZMM-MONEY"] = 0;
map1["XHB-MONEY"] = 0;
  • 通过函数insert函数来插入,传的参数要是pair类型的,并在括号里表明key和value
map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));

map删除数据操作

  • 使用的还是erase函数,本例是直接根据key删除一个map元素
map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));
cout << "size = " << map1.size() << endl; //size = 4

map1.erase("ZMM-EGA");
cout << "size = " << map1.size() << endl; //size = 3
  • 本例是通过find函数找到key,然后返回迭代器,然后再删除
map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));
cout << "size = " << map1.size() << endl; //size = 4

map<string, int>::iterator it = map1.find("ZMM-EGA");
map1.erase(it);
cout << "size = " << map1.size() << endl; //size = 3
  • 清空map,用的还是clear函数
map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));
cout << "size = " << map1.size() << endl; //size = 4

map1.clear();
cout << "size = " << map1.size() << endl; //size = 0

修改map里的数据

注意:修改数据仅能修改value的值,key是不能修改的。如果非要修改key,只能通过增加和删除来实现修改key。本例中我们修改了key="ZMM-EGA"value

map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));

for (map<string, int>::iterator it = map1.begin(); it != map1.end(); it++)
{
	cout << "key = " << it->first << " value = " << it->second << endl;
}
//key = XHB - EGA value = 18
//key = XHB - MONEY value = 0
//key = ZMM - EGA value = 18
//key = ZMM - MONEY value = 0

map1["ZMM-EGA"] = 17;

for (map<string, int>::iterator it = map1.begin(); it != map1.end(); it++)
{
	cout << "key = " << it->first << " value = " << it->second << endl;
}
//key = XHB - EGA value = 18
//key = XHB - MONEY value = 0
//key = ZMM - EGA value = 17
//key = ZMM - MONEY value = 0

其中,我们遍历打印了map,通过迭代器指针it指向first和second来获取keyvalue值。至于为什么打印出来顺序不一样,那是因为map会自动根据key值进行排序。字符串的排序规则可能就是按照A~Z的顺序。

查找map中的数据

只能通过key来查找map元素,返回的是迭代器,如果没查到那就返回end迭代器

map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));

map<string, int>::iterator it_find = map1.find("XHB-EGA");
if (it_find != map1.end()) {
	cout << it_find->second << endl;//18
}

为了更加直观的感受按照key值排序,接下来的查找例子我们将key设置为int类型

  • 查找恰好大于等于的最小元素lower_bound(),恰好大于的最小元素upper_bound()
map<int, string> map2;
map2[18] = "a";
map2[19] = "b";
map2[20] = "c";
map2[21] = "d";
map2[22] = "e";

map<int, string>::iterator iter = map2.lower_bound(18);
cout << "key = " << iter->first << " value = " << iter->second << endl;//key = 18 value = a

iter = map2.upper_bound(18);
cout << "key = " << iter->first << " value = " << iter->second << endl;//key = 19 value = b

其中lower_bound(18)的意思是:查找mapkey值大于等于18的map元素,且是第一个出现的,因此就找到了key = 18 value = a这一map元素;

upper_bound(18)的意思是:查找mapkey严格大于18的map元素,且是第一个出现的,因此就找到了key = 19 value = b这一map元素;

交换两个map

用的还是swap函数,本质就是交换指向两个map的指针。

map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));

map<string, int> map2;
map2["ZMM"] = 1888;
map2["XHB"] = 1888;

map1.swap(map2);

for (map<string, int>::iterator it = map1.begin(); it != map1.end(); it++)
{
	cout << "key = " << it->first << " value = " << it->second << endl;
}
//key = XHB value = 1888
//key = ZMM value = 1888

返回当前map所能容纳的最大容量

使用的是max_size()函数

map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("XHB-EGA", 18));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));

//返回当前容器的可以容纳的最大元素个数,来看一个例子。
cout << "size = " << map1.max_size() << endl; //size = 89478485

map不允许两个相同的key出现

当插入map元素时,插入了相同key,就会默认去掉后来插入的。

map<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("ZMM-EGA", 17));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));

cout << "size = " << map1.size() << endl; //size = 3

for (map<string, int>::iterator it = map1.begin(); it != map1.end(); it++)
{
	cout << "key = " << it->first << " value = " << it->second << endl;
}
//key = XHB - MONEY value = 0
//key = ZMM - EGA value = 18
//key = ZMM - MONEY value = 0

但是multimap允许有相同的key

multimap<string, int> map1;
map1.insert(pair<string, int>("ZMM-EGA", 18));
map1.insert(pair<string, int>("ZMM-EGA", 17));
map1.insert(pair<string, int>("ZMM-MONEY", 0));
map1.insert(pair<string, int>("XHB-MONEY", 0));

cout << "size = " << map1.size() << endl; //size = 3

for (multimap<string, int>::iterator it = map1.begin(); it != map1.end(); it++)
{
	cout << "key = " << it->first << " value = " << it->second << endl;
}
//key = XHB - MONEY value = 0
//key = ZMM - EGA value = 18
//key = ZMM - EGA value = 17
//key = ZMM - MONEY value = 0

不同容器的使用场景(容器的总结)

vector deque list set map
内部实现 单端数组 双端数组 双向链表 二叉树 二叉树
元素搜索速度 对key而言快
元素增删速度 尾端快头部慢 头尾两端都快 任何位置都快

vector 可以涵盖其他所有容器的功能,只不过实现特殊功能时效率没有其他容器高。但如果只是简单存储,vector效率是最高的。

  • deque 相比于 vector 支持头端元素的快速增删,这也是deque的一大优势。
  • list 支持频繁的不确定位置元素的移除插入。
  • set 会自动排序。
  • map 是元素为键值对组并按键排序的set。

vector<元素类型>

用于需要快速定位(访问)任意位置上的元素以及主要在元素序列的尾部增加/删除元素的场合。

list<元素类型>

用于经常在元素序列中任意位置上插入/删除元素的场合。

deque<元素类型>

用于主要在元素序列的两端增加/删除元素以及需要快速定位(访问)任意位置上的元素的场合。

stack<元素类型>

用于仅在元素序列的尾部增加/删除元素的场合。

queue<元素类型>

用于仅在元素序列的尾部增加头部删除元素的场合。

map<关键字类型,值类型>

用于需要根据关键字来访问元素的场合。容器中每个元素是一个pair结构类型,该结构有两个成员:first和second,关键字对应first,值对应second,元素是根据其关键字排序的。对于map,不同元素的关键字不能相同;对于multimap,不同元素的关键字可以相同。它们在头文件map中定义,常常用某种二叉树来实现。

set<元素类型>

它们分别是map和multimap的特例,每个元素只有关键字而没有值,或者说,关键字与值合一了。在头文件set中定义。

算法(Algorithm)–容器的高级API

#inclined<algorithm> 是所有STL头文件中最大的一个,其中常用的功能涉及到比较、交换、查找、遍历、复制、修改、反转、排序、合并等。下面介绍其常用API

对容器中的每个元素应用一个函数:for_each()

用法:std::for_each(begin_iterator, end_iterator, function_object);
示例:对容器中每一个元素都加1,然后输出。

void print_elem(int elem) {
	std::cout << elem + 1 << " ";
}

std::vector<int> v{ 1, 2, 3, 4, 5 };
std::for_each(v.begin(), v.end(), print_elem); // 2 3 4 5 6

在容器中查找特定元素:find()

用法:std::find(begin_iterator, end_iterator, value);
示例:从vector中找到3,然后返回索引位置。(tips:迭代器的类型可以直接用auto,省事。)

std::vector<int> v{ 1, 2, 3, 4, 5 };
auto result = std::find(v.begin(), v.end(), 3); // 在vector中查找3
if (result != v.end()) {
	std::cout << "Found at position " << result - v.begin() << std::endl; //Found at position 2
}
else {
	std::cout << "Not found" << std::endl;
}

计算容器中特定元素的出现次数:count()

用法:std::count(begin_iterator, end_iterator, value);
示例:统计vector中数字3出现的次数

std::vector<int> v{ 1, 2, 3, 4, 5, 3 };
int count = std::count(v.begin(), v.end(), 3); // 计算3在vector中的出现次数
std::cout << "Found " << count << " times" << std::endl; // Found 2 times

用新值替换容器中的所有旧值:replace()

用法:std::replace(begin_iterator, end_iterator, old_value, new_value);用旧值替换新值,所以旧值是第三个参数,新值是第四个参数。
示例:用6替换vector所有的3。(没有返回值,底层使用的引用传递,因此直接更新原来的容器。)

std::vector<int> v{ 1, 2, 3, 4, 5, 3 };
std::replace(v.begin(), v.end(), 3, 6); // 用6替换vector中所有的3
for (auto i : v) {
	std::cout << i << " ";//1 2 6 4 5 6
}

对容器中的元素进行排序:sort()

用法:std::sort(begin_iterator, end_iterator);
示例:对vector递增排序

std::vector<int> v{ 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 };
std::sort(v.begin(), v.end()); // 对vector进行排序
for (auto i : v) {
	std::cout << i << " ";
}
//1 1 2 3 3 4 5 5 5 6 9

sort函数其实还可以接受一个函数对象std::sort(begin_iterator, end_iterator, function_object);可以实现递减排序:

int sort_re(int x, int y)
{
	return x > y;
}

std::vector<int> v{ 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 };
std::sort(v.begin(), v.end(), sort_re); // 对vector进行排序
for (auto i : v) {
	std::cout << i << " ";
}
//9 6 5 5 5 4 3 3 2 1 1

将容器中的元素反转:reverse()

用法:std::reverse(begin_iterator, end_iterator);
示例:把vector反转一下

std::vector<int> v{ 1, 2, 3, 4, 5 };
std::reverse(v.begin(), v.end()); // 反转vector
for (auto i : v) {
	std::cout << i << " ";
}
//5 4 3 2 1

将容器中的重复元素去重:unique()

该函数的作用是“去除”容器或者数组中相邻元素的重复出现的元素,注意
(1)这里的去除并非真正意义的erase,而是将重复的元素放到容器的末尾,返回值是去重之后的尾地址。
(2)unique针对的是相邻元素,所以对于顺序顺序错乱的数组成员,或者容器成员,需要先进行排序,可以调用std::sort()函数

一天学完C++的标准模板库STL

用法:std::unique(begin_iterator, end_iterator);
示例:对一个无序容器进行去重

std::vector<int> v{ 1, 5, 4, 3, 6, 4, 5, 2 };
std::sort(v.begin(), v.end());
auto it_last = std::unique(v.begin(), v.end()); // 去掉vector中的重复元素
v.erase(it_last, v.end());
for (auto i : v) {
	std::cout << i << " ";
}
//1 2 3 4 5 6

其中erase函数,删除从it_last到v.end()的元素,搭配unique函数,就能达到去重的目的。

在已排序的容器中查找特定元素是否存在:binary_search()

用法:std::binary_search(begin_iterator, end_iterator, value);
示例:这就是个二分查找,返回值是bool,也就是返回元素是否存在容器中。(必须是已排序,要不然有bug)

std::vector<int> v{ 1, 2, 3, 4, 5 };
bool found = std::binary_search(v.begin(), v.end(), 3); // 在已排序的vector中查找3
if (found) {
	std::cout << "Found" << std::endl;//Found
}
else {
	std::cout << "Not found" << std::endl;
}

将一个容器的元素复制到另一个容器中:copy()

用法:std::copy(begin_iterator, end_iterator, destination_iterator)destination_iterator是另一个容器的起始迭代器
示例:把v1的五个元素,复制到v2的后五个元素;下一个样例是把v1的五个元素直接复制到v2

std::vector<int> v1{ 1, 2, 3, 4, 5 };
std::vector<int> v2(6,0); // 定义一个长度为6的空vector
std::copy(v1.begin(), v1.end(), v2.begin()+1); // 复制v1到v2
for (auto i : v2) {
	std::cout << i << " ";
}
//0 1 2 3 4 5

std::vector<int> v1{ 1, 2, 3, 4, 5 };
std::vector<int> v2(5,0); // 定义一个长度为5的空vector
std::copy(v1.begin(), v1.end(), v2.begin()); // 复制v1到v2
for (auto i : v2) {
	std::cout << i << " ";
}
//1 2 3 4 5

找到容器中最大的元素:max_element()

用法:std::max_element(begin_iterator, end_iterator);
示例:找出vector中最大的元素,可以不用排序。

std::vector<int> v{ 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 };
auto max = std::max_element(v.begin(), v.end()); // 找到vector中最大的元素
std::cout << "Max: " << *max << std::endl;//Max: 9

找到容器中最小的元素:min_element()

用法:std::min_element(begin_iterator, end_iterator);
示例:找出vector中最小的元素,可以不用排序。

std::vector<int> v{ 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 };
auto min = std::min_element(v.begin(), v.end()); // 找到vector中最小的元素
std::cout << "Min: " << *min << std::endl;//Min: 1

对容器中的元素进行旋转:rotate()

用法:std::rotate(begin_iterator, middle_iterator, end_iterator);
示例:以middle_iterator为开始,对容器进行重新排序。以middle_iterator为开头,然后首尾连接。

一天学完C++的标准模板库STL

std::vector<int> v{ 1, 2, 3, 4, 5 };
std::rotate(v.begin(), v.begin() + 2, v.end()); // 对vector进行旋转,旋转两个位置
for (auto i : v) {
	std::cout << i << " ";
}
//3 4 5 1 2

std::vector<int> v{ 1, 2, 3, 4, 5 };
std::rotate(v.begin(), v.begin() + 3, v.end()); // 对vector进行旋转,旋转两个位置
for (auto i : v) {
	std::cout << i << " ";
}
//4 5 1 2 3

在容器中查找第一个满足条件的元素:find_if()

用法:std::find_if(begin_iterator, end_iterator, function_obj);
示例:找出满足容器元素满足某一条件的第一个出现的值的迭代器位置。

std::vector<int> v{ 1, 2, 3, 4, 5 };
auto it = std::find_if(v.begin(), v.end(), [](int i) { return i > 3; }); // 查找vector中第一个大于3的元素
if (it != v.end()) {
	std::cout << "Found: " << *it << std::endl; //Found: 4
}
else {
	std::cout << "Not found!" << std::endl;
}

对容器指定范围内的元素随机调整次序:random_shuffle()

用法:std::random_shuffle(iterator beg, iterator end);
示例:对vector随机打乱顺序

std::vector<int> v{ 1, 2, 3, 4, 5 };
std::random_shuffle(v.begin(), v.end());

for (auto i : v) {
	cout << i << " ";
}
//5 2 4 3 1
//5 2 4 3 1
//5 2 4 3 1

但是,这种方式每次打乱的顺序都是相同的,如果想要每次打乱的顺序不同,那就要设置随机数种子srand(要用到time方法,需要引入#include<time.h>

#include<time.h>

std::vector<int> v{ 1, 2, 3, 4, 5 };
srand((unsigned int)time(NULL));
std::random_shuffle(v.begin(), v.end());

for (auto i : v) {
	cout << i << " ";
}
//2 5 4 3 1
//5 2 3 4 1
//2 4 1 3 5

计算容器指定区间元素累计总和:accumulate()

用法:accumulate(iterator beg, iterator end, init_value); 需要引入计算库#include<numeric>,而不是算法库了
实例:求vector里元素的总和,以0作为总和的初始值,也就是说vector元素总和+0等于最终的输出结果

std::vector<int> v{ 1, 2, 3, 4, 5 };
int sum = std::accumulate(v.begin(), v.end(), 0);
std::cout << "Sum of vector elements is: " << sum << std::endl;//Sum of vector elements is: 15

在上面的代码中,我们使用 std::accumulate 计算了整数向量v中所有元素的和,并将结果存储在变量 sum 中。在这个示例中,我们使用了一个初始值 0,但可以根据需要使用任何初始值。

填充容器指定区间为某个值:fill()

用法:fill(iterator beg, iterator end, value);
示例:对vectorv.begin()+1填充到v.end()-1,全部填充为0

std::vector<int> v{ 1, 2, 3, 4, 5 };
std::fill(v.begin()+1, v.end()-1, 0);

for (auto i : v) {
	cout << i << " ";
}
//1 0 0 0 5

将两个有着相同序的容器合并:merge()

用法:merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
示例:将都是增序的v1v2合并,放到v3里。v3会自动排序合并之后的,但是一定要保证v1v2都是一样的顺序

std::vector<int> v1{ 1, 2, 3, 4, 5 };
std::vector<int> v2{ 3, 4, 5, 6, 7 };
std::vector<int> v3(10);

merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
for (auto i : v3) {
	cout << i << " ";
}
//1 2 3 3 4 4 5 5 6 7

假如,我把v2换成降序vector,或者换成无序vector

std::vector<int> v1{ 1, 2, 3, 4, 5 };
std::vector<int> v2{ 5, 4, 3, 2, 1 };
//std::vector<int> v2{ 1, 2, 3, 4, 1 };
std::vector<int> v3(10);

merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
for (auto i : v3) {
	cout << i << " ";
}

程序就会抛出异常,说序列没排好序
一天学完C++的标准模板库STL

求两个容器元素的交集:set_intersection()

用法:set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
示例:求两个vector的交集,首先必须要先排序,这是set_intersection函数的要求。需要有第三个容器作为输出容器,存放交集元素,且第三个容器的初始大小必须能放得下交集元素,因此最好的办法就是设置为其中一个vectorsize

std::vector<int> v1 = { 1,2,3,4,5 };
std::vector<int> v2 = { 3,4,5,6,7 };

sort(v1.begin(), v1.end());
sort(v2.begin(), v2.end());
std::vector<int> v3(v1.size());
vector<int>::iterator it_end = std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());

for (auto it = v3.begin(); it != it_end;it++) {
	cout << *it << " ";
}
//3 4 5

for (auto i : v3) {
	cout << i << " ";
}
//3 4 5 0 0

如果遍历整个v3后面其实还有0呢,如果想要只输出交集元素,那就要使用迭代器遍历。因为set_intersection函数返回的就是交集元素的末尾迭代器指针。

求两个容器元素的并集:set_union()

用法:set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
示例:求两个vector的并集,首先必须要先排序,这是set_union函数的要求。需要有第三个容器作为输出容器,存放并集元素,且第三个容器的初始大小必须能放得下并集元素,因此最好的办法就是设置为两个vectorsize之和。

std::vector<int> v1 = { 1,2,3,4,5 };
std::vector<int> v2 = { 3,4,5,6,7 };

sort(v1.begin(), v1.end());
sort(v2.begin(), v2.end());
std::vector<int> v3(v1.size()+ v2.size());
vector<int>::iterator it_end = std::set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());

for (auto it = v3.begin(); it != it_end;it++) {
	cout << *it << " ";
}
// 1 2 3 4 5 6 7

for (auto i : v3) {
	cout << i << " ";
}
//1 2 3 4 5 6 7 0 0

求两个容器元素的差集:set_difference()

用法:set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
示例:求两个vector的差集,首先必须要先排序,这是set_union函数的要求。需要有第三个容器作为输出容器,存放差集元素,且第三个容器的初始大小必须能放得下差集元素,因此最好的办法就是设置为两个vectorsize之和。

std::vector<int> v1 = { 1,2,3,4,5 };
std::vector<int> v2 = { 3,4,5,6,7 };

sort(v1.begin(), v1.end());
sort(v2.begin(), v2.end());
std::vector<int> v3(v1.size()+ v2.size());
vector<int>::iterator it_end = std::set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());

for (auto it = v3.begin(); it != it_end;it++) {
	cout << *it << " ";
}
// 1 2 

for (auto i : v3) {
	cout << i << " ";
}
//1 2 0 0 0 0 0 0 0 0

差集指的是,v1v2没有的元素,那就是1,2。如果把set_difference函数的前两个参数换成v2的迭代器,那就是查看v2v1没有的元素,不同顺序效果不一样,如下所示:文章来源地址https://www.toymoban.com/news/detail-415805.html

std::vector<int> v1 = { 1,2,3,4,5 };
std::vector<int> v2 = { 3,4,5,6,7 };

sort(v1.begin(), v1.end());
sort(v2.begin(), v2.end());
std::vector<int> v3(v1.size()+ v2.size());
vector<int>::iterator it_end = std::set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), v3.begin());

for (auto it = v3.begin(); it != it_end;it++) {
	cout << *it << " ";
}
// 6 7 

for (auto i : v3) {
	cout << i << " ";
}
//6 7 0 0 0 0 0 0 0 0

到了这里,关于一天学完C++的标准模板库STL的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【c++】:STL模板中string的使用

        文章目录 STL简介 一.认识string 二.string中基本功能的使用 总结   STL(standard template libaray-标准模板库): 是 C++ 标准库的重要组成部分 ,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架 。 STL 的版本 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完

    2024年01月18日
    浏览(44)
  • 【c++】:模拟实现STL模板中的string

        文章目录 前言 一.string的模拟实现 总结   上一篇文章我们详细介绍了STL中的string的一些常用的接口,这一篇文章我们将从底层实现string类,当然我们只是实现一些重要的,经常使用的接口,并且不是完全按照STL中的string去走的。   首先我们为了防止我们写的string类与库

    2024年01月20日
    浏览(56)
  • C++ STL 标准模板库介绍与入门

    目录 1、概述 1.1、C++ 标准库 1.2、Boost库 2、STL 版本 2.1、HP 原始版本

    2024年02月05日
    浏览(63)
  • c++标准模板(STL)(std::array)(三)

    template     class T,     std::size_t N struct array; (C++11 起   std::array 是封装固定大小数组的容器。 此容器是一个聚合类型,其语义等同于保有一个 C 风格数组 T[N] 作为其唯一非静态数据成员的结构体。不同于 C 风格数组,它不会自动退化成 T* 。它能作为聚合类型聚合初始化,只要

    2024年02月02日
    浏览(48)
  • C++——STL标准模板库——容器详解——list

    list:双向链表。list是一种分布式存储的线性表,每个节点分为数据域和指针域,其中指针域中包含一个指向前驱节点的指针和一个指向后续节点的指针,基本模型如下: 1、双向链表:每个元素都有一个前驱和一个后继,这种结构允许在链表的任何位置实现快速的插入和删除

    2024年01月16日
    浏览(47)
  • c++ 11标准模板(STL) std::vector (二)

    template     class T,     class Allocator = std::allocatorT class vector; (1) namespace pmr {     template class T     using vector = std::vectorT, std::pmr::polymorphic_allocatorT; } (2) (C++17 起) 1) std::vector 是封装动态数组的顺序容器。 2) std::pmr::vector 是使用多态分配器的模板别名。 元素相继存储,这意味着不

    2024年02月02日
    浏览(56)
  • (AI创作实验)C++中的STL(标准模板库)

    STL概述 STL: (Standard Template Library) 标准模板库 包含一些常用的算法如排序查找,还有常用的数据结构如可变长数组、链表、字典等。 使用方便,效率较高 要使用其中的算法,需要#include C++中的STL(标准模板库)是一个非常强大的工具,为程序员提供了许多高效的数据结构和算

    2023年04月18日
    浏览(48)
  • C++标准模板(STL)- 类型支持 (数值极限,C 数值极限接口)

    参阅 std::numeric_limits 接口 定义于头文件 cstdint PTRDIFF_MIN (C++11) std::ptrdiff_t 类型对象的最小值 (宏常量) PTRDIFF_MAX (C++11) std::ptrdiff_t 类型对象的最大值 (宏常量) SIZE_MAX (C++11) std::size_t 类型对象的最大值 (宏常量) SIG_ATOMIC_MIN (C++11) std::sig_atomic_t 类型对象的最小值 (宏常量) SIG_ATOMIC_

    2024年02月07日
    浏览(41)
  • C++、STL标准模板库和泛型编程 ——适配器、补充(侯捷)

    侯捷 C++八部曲笔记汇总 - - - 持续更新 ! ! ! 一、C++ 面向对象高级开发 1、C++面向对象高级编程(上) 2、C++面向对象高级编程(下) 二、STL 标准库和泛型编程 1、分配器、序列式容器 2、关联式容器 3、迭代器、 算法、仿函数 4、适配器、补充 三、C++ 设计模式 四、C++ 新标准 五、

    2023年04月27日
    浏览(72)
  • C++、STL标准模板库和泛型编程 ——迭代器、 算法、仿函数(侯捷)

    侯捷 C++八部曲笔记汇总 - - - 持续更新 ! ! ! 一、C++ 面向对象高级开发 1、C++面向对象高级编程(上) 2、C++面向对象高级编程(下) 二、STL 标准库和泛型编程 1、分配器、序列式容器 2、关联式容器 3、迭代器、 算法、仿函数 4、适配器、补充 三、C++ 设计模式 四、C++ 新标准 五、

    2023年04月25日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包