提到字符串,我们会想起C语言中的char *
,这是一个指针。而在STL中string
也是用来声明字符串的,但是string
是一个类,需要导入库#include
。 String
封装了char *
,管理这个字符串,是一个char*
型的容器。
string
本质上是一个动态的char
数组。
使用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
n
个char
字符初始化string
类字符串,本质调用的是构造函数string(int n, char c)
。必须是单个字符,不能是字符串,多个字符组成的char字符,也只会取最后一个字符。//用n个char字符构造,一定是字符,而不是字符串
string s9(10, 'x');
printStr(s9);//xxxxxxxxxx
//如果不是单个字符,就直接读取最后一个字符
string s10(10, 'xs');
printStr(s10);//ssssssssss
必须是单引号字符,不可以是双引号字符串。
可以看出,string类的对象本质上就是一个char字符数组,由一个一个字符组成的数组字符串。
我们了解到,string类的对象本质上就是一个char字符数组,由一个一个字符组成的数组字符串。所以,string是可以被遍历的,接下来介绍几种遍历string字符串并访问字符的方法:
size()
函数可以返回string字符串的有效长度。其实这个下标[]
本质就是string类内部实现了运算符重载char& operator[](int n);
string s1 = "hello world";
//下标遍历访问法,
for (int i = 0; i < s1.size(); i++) {
cout << s1[i];
}
//hello world
at()
来访问对应索引的字符。string s1 = "hello world";
//at()遍历访问法
for (int i = 0; i < s1.size(); i++) {
cout << s1.at(i);
}
//hello world
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
即可。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
遍历字符串仍要不断++
,而不是--
。//迭代器遍历(反向),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
类的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& 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
string s1 = "hello ";
s1.erase(1, 100);//从第1位开始,删除100个
printStr(s1);//h
string的修改比较简单,string类中对[ ]运算符进行了重载,我们可以像数组一样一样直接使用[ ]加下表的方式对string值进行修改。
string s1 = "hello ";
s1[2] = 'p';
printStr(s1);//heplo
首先介绍一个取出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 = "hello world";
string sub_str = "llo";
cout << s1.find(sub_str) << endl;//2
当我查找"llo"
时,就会首先确定字符串中有没有连续的子串"llo"
,如果有,再定位第一次出现时子串第一个字符在原字符串中的索引,并返回。
接下来我多多举例,更加清晰地了解find函数
的作用。
string
类型的,也可以是const char*
类型的。原字符串中有两处子串,find函数
只会找到首次出现的,不会理会第二次出现的string s1 = "hello hello";
const char* sub_str = "llo";
cout << s1.find(sub_str, 0) << endl;//2
string s1 = "hello hello";
const char* sub_str = "llo";
cout << s1.find(sub_str, 3) << endl;//8
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);
int rfind(const string& str, int pos = npos) const
;默认是从最后一位向前查找,因此第二个参数可以不写。功能就是从pos
开始向前查找,找到首次出现的字符str
,就返回索引。string s1 = "hello hello";
const char* sub_str = "llo";
cout << s1.rfind(sub_str) << endl;//8
string& replace(int pos, int n, const string& str);
// 替换从pos开始n个字符为字符串s
string& replace(int pos, int n, const char* s);
// 替换从pos开始的n个字符为字符串s
string s1 = "hello hello";
printStr(s1.replace(0, 5, "world"));//world hello
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风格字符数组比较
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
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
转 const 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
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
是STL的动态数组,可以在运行中根据需要改变数组的大小。也就是我们熟知的动态扩容技术。
因为它以数组的形式储存,所以它的内存空间是连续的。
vector的头文件为#include
,由于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
数组,里面啥也没有的那种。根据打印结果,可以看出来vector数组里是什么情况。声明一个空的vector里面什么也没有,所以size等于0,且打印出来的vector为空。vector<int> v1;
cout << v1.size() << endl;//0
printVec(v1);//
vector
数组,里面初始化为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
vector
数组,第二个参数没写,默认填充为0。vector<int> v3(10);
cout << v3.size() << endl;//10
printVec(v3);//0 0 0 0 0 0 0 0 0 0
vector
数组,第二个参数写了4,那就把10个数全部填充为4vector<int> v4(10,4);
cout << v4.size() << endl;//10
printVec(v4);//4 4 4 4 4 4 4 4 4 4
vector
数组,第二个参数写了4.1,那就把10个数全部填充为4.1vector<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
vector
数组,第二个参数写了具体的字符串,那就把10个字符串部填充为 “null”vector<string> v6(10, "null");
cout << v6.size() << endl;//10
printVec(v6);//null null null null null null null null null null
vector
数组,第二个参数写了具体的字符串,那就把10个字符串部填充为 “hello”vector<string> v7(10, "hello");
cout << v7.size() << endl;//10
printVec(v7);//hello hello hello hello hello hello hello hello hello hello
vector
数组,并采用构造函数的方式初始化,本质是用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的。 有三种使用形式:
vector<int> v1;
v1.assign(10, 4);
printVec(v1);//4 4 4 4 4 4 4 4 4 4
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
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 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
迭代器遍历是最专业的遍历方式,分为正向迭代器和反向迭代器。正向迭代器从前向后遍历,反向迭代器从后向前遍历。都遵循左闭右开,即[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
如果给出一幅图可以看懂正向迭代器和反向迭代器,下面的图很合适:
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
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<int> v1 = {0,1,2,3,4,5,6,7,8,9};
cout << v1.front() << endl;//0
cout << v1.back() << endl;//9
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
位置,插入元素elem
:insert(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
个元素elem
:insert(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)
。本例是在v1
的pos=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]
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> vs(10,"hello");
vs[1] = "world";
printVec(vs);//hello world hello hello hello hello hello hello hello hello
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
依然是find()
函数。find()
函数不仅可以查找string
类,还可以用在vector
类上。string
的find()
函数会直接返回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;
}
size()函数可以返回当前vector里元素的个数
vector<string> vs = {"hello", "world" ,"hello" ,"world"};
printVec(vs);//hello world hello world
cout << vs.size() << endl;//4
empty()可以返回当前vector是否为空,不为空就返回0,为空就返回1
vector<string> vs = {"hello", "world" ,"hello" ,"world"};
printVec(vs);//hello world hello world
cout << vs.empty() << endl;//0,也就是FALSE
vector提供了两个重载函数来实现这个事。总结就是:若容器变长,则填充指定的元素到新位置;若容器变短,则删除超出容器新长度的元素。
void resize(int num);
// 重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
// 若容器变短,则末尾超出容器长度的元素被删除
void resize(int num, T elem);
// 重新指定容器的长度为num,若容器变长,则以elem填充新位置。
// 若容器变短,则末尾超出容器长度的元素被删除
void resize(int num)
; 如果扩大容器,默认填充空元素到尾部,容器的size会变化,但是打印容器看不出变化。之前是hello world hello world
,resize
之后就变成了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
依然是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<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必须后面跟一个空格,如果没有空格,比如:vector
这会报错。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呢?
vector
的size
方法返回的是行数,每一行的vector
的size
方法返回的是该行中元素的个数。知道了这个原则,就可以使用下标遍历法,两个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
在是遍历当前行中的每一个元素。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
是一种先进先出(First In First Out, FIFO)
的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素只允许从尾部添加元素不允许从尾部弹出元素,只允许从头部弹出元素,不允许从头部添加元素。其示意图如下:
队列queue
只有头元素才有可能被外界使用,因此queue
不提供遍历功能,也不提供迭代器(不允许遍历,因为队列的目的就是让外界只能从队头访问元素)
初始化有两种方式,要么直接初始化一个空的,要么利用已有的拷贝构造。
queue<int> plist;
queue<int> qlist(plist);
队列是不允许赋值初始化的:
向队列中添加元素只能从队尾向队列里添加,采用的是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
先获取当前队头元素,然后把队头弹出,继续获取下一个队头元素,然后继续弹出…知道队列为空,所有队头都被挨个打印出来了,那不就完成了打印队列的目的吗(虽然队列最终被清空了)
栈是一种先进后出(First In Last Out, FILO)
的数据结构,它只有一个出口,那就是栈顶。栈底是不允许访问的,想要访问栈的元素,必须一个一个访问栈顶元素top
。使用前需要包含stack库:#include
stack stkS
,或者利用已存在的初始化stack stkS_(stkS)
。不能赋值初始化stack 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
只能访问栈顶元素,那就访问一个,弹出一个,这就打印出来了。可以看出,打印的是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
普通队列queue
只能在尾部插入和在头部删除。deque
相对于queue
来说,可以在两端进行添加和删除操作。具体如下图所示:
相对于vector
来说,deque
最大的优势就是在头部插入元素时,效率依然是O(1)。因为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
查看队列中的元素个数,依然用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<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
唯一的优点。
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
函数。
用的也是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
位。
2
个100
在第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
清空的话,还是用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
STL的list
是双向链表(要明白一个点啊:节点内部的prev
和next
我们是看不见的,也访问不了。STL中的list
就是一个线性表。只不过在设计链表的时候,要用到prev
和next
。我们今后在使用链表的时候,只需要了解到链表的一些API即可。)需要包含头文件#inclined
下面介绍一下,双向链表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
双向链表的头尾都可以操作,跟双向队列的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
插入函数,提供了三种重载方法。
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
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位置的数据,返回下一个数据的位置
链表的迭代器没有重载+运算符,因此不能手动移动任意位置了,只能删除链表中已知存在的元素。
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
用的是一个新函数: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
通过这个例子了解到,链表是否为空还是用的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
这个容器最大的特点就是不允许有重复元素,且会自动排序,即使初始化的时候是一个乱序的。需要包含头文件#inclined
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<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<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
)
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
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
set
中elem
的个数,因为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
se
t中大于等于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
se
t中大于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
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
也是集合,只不过允许集合中有重复的元素,仍然是会自动排序。
所以说,multiset
的应用场景比set更广泛。跟set
一样,只需要#inclined
,这两个共有一个头文件。
通过下面的例子可以看出: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
,模板类第二个参数也是可以传参的。这样就可以办到递减排序。
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
是具有唯一键值对的容器,通常使用红黑树实现。
map
中的键值对是 key
value
的形式,都是pair
结构,并且所有的元素都会根据元素的键值自动排序;
map
不允许两个元素有相同的键值;但是multimap
可以拥有相同的键值。使用时需要包含头文件:#include
定义形式如下所示:必须遵循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
使用迭代器遍历,想要打印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
顺序可能不一致。
key
和value
都是啥)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));
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
注意:修改数据仅能修改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
来获取key
和value
值。至于为什么打印出来顺序不一样,那是因为map会自动根据key值进行排序。字符串的排序规则可能就是按照A~Z
的顺序。
只能通过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)
的意思是:查找map
中key
值大于等于18
的map元素,且是第一个出现的,因此就找到了key = 18 value = a
这一map元素;
upper_bound(18)
的意思是:查找map
中key
值严格大于18
的map元素,且是第一个出现的,因此就找到了key = 19 value = b
这一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
使用的是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<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效率是最高的。
用于需要快速定位(访问)任意位置上的元素以及主要在元素序列的尾部增加/删除元素的场合。
用于经常在元素序列中任意位置上插入/删除元素的场合。
用于主要在元素序列的两端增加/删除元素以及需要快速定位(访问)任意位置上的元素的场合。
用于仅在元素序列的尾部增加/删除元素的场合。
用于仅在元素序列的尾部增加、头部删除元素的场合。
用于需要根据关键字来访问元素的场合。容器中每个元素是一个pair结构类型,该结构有两个成员:first和second,关键字对应first,值对应second,元素是根据其关键字排序的。对于map,不同元素的关键字不能相同;对于multimap,不同元素的关键字可以相同。它们在头文件map中定义,常常用某种二叉树来实现。
它们分别是map和multimap的特例,每个元素只有关键字而没有值,或者说,关键字与值合一了。在头文件set中定义。