一、vector
构造一个动态数组
C++中直接构造一个动态数组语句为: vector< T > vec,T可以是float,int,double或者其他自定义数据类型
示例:
vector<int> a//定义一个动态数组a
插入元素
C++中通过push_back()在最后面插入一个新的元素。
示例:
#include<vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> vec;
vec.push_back(1);//[1]
vec.push_back(2);//[1,2]
vec.push_back(3);//[1,2,3]
return 0;
}
获取长度并且访问元素
C++ 中通过size()方法获取vector的长度,通过[]操作直接访问vector中的元素,这一点和数组是一样的。
示例:
#include<vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> vec;
vec.push_back(1);//[1]
vec.push_back(2);//[1,2]
vec.push_back(3);//[1,2,3]
for (int i = 0; i < vec.size(); i++)//获取长度
{
cout << vec[i] << endl;//访问
}
return 0;
}
修改元素
C++ 中修改vector中某个元素很简单,只需要用=给它赋值就好了,比如vec[1] = 3。
示例:
#include<vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> vec;
vec.push_back(1);//[1]
vec.push_back(2);//[1,2]
vec.push_back(3);//[1,2,3]
vec[1] = 3;//[1,3,3]
vec[2] = 2;//[1,3,2]
for (int i = 0; i < vec.size(); i++)//获取长度
{
cout << vec[i] << endl;//访问
}
return 0;
}
删除元素
和插入一样,删除元素也只能在动态数组的末端进行操作。C++ 中通过pop_back()方法删动态数组的最后一个元素。
示例:
#include<vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> vec;
vec.push_back(1);//[1]
vec.push_back(2);//[1,2]
vec.push_back(3);//[1,2,3]
vec.pop_back();//[1,2]
vec.pop_back();//[1]
return 0;
}
清空
C++ 中都只需要调用clear()方法就可清空vector。
C++ 中vector的clear()只是清空vector,并不会清空开的内存。用一种方法可以清空vector的内存:
vector<int> v;
vector<int>().swap(v)
用动态数组储存自定义数据
动态数组不仅仅可以储存基本的数据类型,还能储存自定义数据类型,比如结构体。
struct Student {
string name; //名字
int age; //年龄
};
int main()
{
vector<Student> class1; //班级
Student stu1, stu2; //学生1,学生2
stul .name = "xiaohong";
stu1.age = 12;
stu2.name = "yuhaoran";
stu2.age = 25;
class1.push_back(stu1);
class1.push_back(stu2);
return 0;
构造函数
我们知道可以通过push_back()来向动态数组添加一个元素。如果我们需要一个长度为n的,全是1的动态数组。我们可以像下面这样写。
int n = 10;
vector<int> vec;
for (int i = 0; i < n; i++)
{
vec.push_back(1);
}
其实,我们可以通过一个构造函数快速构建这样的一个动态数组。所谓构造函数,就是我们在定义一个对象的时候可以给他赋予初始值。
int n = 10;
vector<int> vec(n, 1);
上面的代码,我们在定义一个vector的时候,调用构造函数,第一个参数表示初始的动态数组的长度,第二个参数表示初始的数组里面每个元素的值。如果不传入第二个参数,那么初始的值都是 0。
上面用构造函数的写法和用循环是等价的,通过合理的使用构造函数,可以减少代码量。
二维动态数组
格式:vector<vector<int> > vec
注意:<int> >
中间有一空格,这个空格一定要加上,否则在一些老版本的编译器上将不能通过编译。
示例:
int n = 5;
vector<vector<int> > vec;
for (int i = 0; i < n; i++)
{
vector<int> x(i + 1, 1);
vec.push_back(x);
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < vec[i].size(); i++)
{
cout << vec[i][j];
}
cout << endl;
}
上面代码定义的二维数组如下:
1
1 1
1 1 1
1 1 1 1
1 1 1 1 1
二维动态数组的每一维的长度都可以不一样,可以是任意形状的。借助构造函数,我们可以快速构造一个 n 行 m 的动态数组,每个元素的初始值是 0:vector<vector<int> > vec(n, vector<int>(m, 0))
示例1:
vector<vector<int> > vec(10, vector<int>());
for (int i = 0; i < 10; i++)
{
vec[i].push_back(i);
}
示例2:
vector<vector<int> > vec(10, vector<int>(5));
for (int i = 0; i < 10; i++)
{
vec[i].push_back(i);
}
示例3:
vector<vector<int> > vec(10);//第一维默认初始的值是一个空的一维的vector
for (int i = 0; i < 10; i++)
{
vec[i].push_back(i);
}
应用实例:
打印9*9乘法口诀
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<vector<int> > v2d;
for (int i = 0; i < 9; i++)
{
v2d.push_back(vector<int>());
}
for (int i = 0; i < v2d.size(); i++)
{
for (int j = 0; j <= i; j++)
{
v2d[i].push_back((i + 1) * (j + 1));
}
}
for (int i = 0; i < v2d.size(); i++)
{
for (int j = 0; j < v2d[i].size(); j++)
{
cout << i + 1 << "*" << j + 1 << "=" << v2d[i][j] << "\t";
}
cout << endl;
}
return 0;
}
C++ vector方法总结:
二、set
集合是数学中的一个基本概念,通俗地理解,集合是由一些不重复的数据组成的。比如{1,2,3}就是一个有 1, 2, 3 三个元素的集合。
构造一个集合
C++ 中直接构造一个set的语句为:set<T> s
。这样我们定义了一个名为s的、存储类型T类型数据的集合,其中T是集合要存储的数据类型。初始时s是空集合。比如set<int> aa
,set<string> bb
等等。
插入元素
C++ 中用insert()函数向集合中插入一个新的元素。注意如果集合中已经存在了某个元素,再次插入不会产生任何效果,集合中是不会出现重复元素的。
#include<set>
#incldue<string>
using namespace std;
int main()
{
set<string> country;//{}
country.insert("China");//{"China"}
country.insert("America");//{"China", "America"}
country.insert("France");//{"China", "America", "France"}
country.insert("China");//{"China", "America", "France"}
}
删除元素
C++ 中通过erase()函数删除集合中的一个元素,如果集合中不存在这个元素,不进行任何操作。
#include<set>
#incldue<string>
using namespace std;
int main()
{
set<string> country;//{}
country.insert("China");//{"China"}
country.insert("America");//{"China", "America"}
country.insert("France");//{"China", "America", "France"}
country.erase("America");//{"China", "France"}
country.erase("England");//{"China", "France"}
}
判断元素是否存在
C++ 中如果你想知道某个元素是否在集合中出现,你可以直接用count()函数。如果集合中存在我们要查找的元素,返回 1,否则会返回 0。
#include<set>
#incldue<string>
#include<iostream>
using namespace std;
int main()
{
set<string> country;//{}
country.insert("China");//{"China"}
country.insert("America");//{"China", "America"}
country.insert("France");//{"China", "America", "France"}
if (country.count("China"))
{
cout << "China belong to country" << endl;
}
return 0;
}
遍历元素
C++ 通过迭代器可以访问集合中的每个元素,迭代器就好像一根手指指向set中的某个元素。通过操作这个手指,我们可以改变它指向的元素。通过 * 解引用运算符,不是乘号的意思)操作可以获取迭代器指向的元素。通过 ++ 操作让迭代器指向下一个元素,同理 – 操作让迭代器指向上一个元素。
迭代器的写法比较固定,set<T>::iterator it
就定义了一个指向set<T>
这种集合的迭代器 it,T是任意的数据类型。其中::iterator
是固定的写法。
#include<set>
#incldue<string>
#include<iostream>
using namespace std;
int main()
{
set<string> country;//{}
country.insert("China");//{"China"}
country.insert("America");//{"China", "America"}
country.insert("France");//{"China", "America", "France"}
for (set<string>::iterator it = country.begin(); it != country.end(); it++)
{
cout << *it << endl;
}
return 0;
}
注意:在 C++ 中遍历set是从小到大遍历的,也就是说set会帮我们排序的。
清空
C++ 中调用clear()函数就可清空set,同时会清空set占用的内存。
C++ set 函数总结
set和结构体
set经常会配合结构体来使用,用set来储存结构体和vector有些区别。正如我们前面所说的那样,set是需要经过排序的。系统自带的数据类型有默认的比较大小的规则,而我们自定义的结构体,系统是不可能知道这个结构体比较大小的方式的。所以我们需要用一种方式来告诉系统怎么比较这个结构体的大小。其中一种方法叫做运算符重载,我们需要重新定义小于符号。
下面代码定义了一个重载了小于符号的结构体:
struct Node {
int x, y;
bool operator<(const Node &rhs) const {
if (x == rhs.x) {
return y < rhs.y;
} else {
return x < rhs.x;
}
}
};
operator<表示我们要重载运算符<,可以看成是一个函数名。rhs是“right hand side”的简称,有右操作数的意思,这里我们定义为一个const引用。因为该运算符重载定义在结构体内部,左操作数就当前调用operator<的对象。
特别要注意,不要漏掉最后的const。const函数表示不能对其数据成员进行修改操作,并且const对象不能调用非const成员函数,只允许调用const成员函数。
上面重载规定了排序方式为,优先按照x从小到大排序,如果x相同,那么再按照y从小到大排序。经过了<运算符重载的结构体,我们就可以比较两个Node对象的大小了,因此可以直接储存在set中了。
三、map
映射表
映射是指两个集合之间的元素的相互对应关系。通俗地说,就是一个元素对应另外一个元素。比如有一个姓名的集合{“Tom”, “Jone”, “Mary”},班级集合 {1,2}。姓名与班级之间可以有如下的映射关系:
class(“Tom”) = 1, class(“Jone”) = 2, class(“Mary”) = 1
我们称其中的姓名集合为 关键字集合(key),班级集合为 值集合(value)。
在 C++ 中我们常用的映射是map。
构造一个映射
在 C++ 中,我们构造一个map的语句为:map<T1, T2> m;这样我们定义了一个名为m的从T1类型到T2类型的映射。初始的时候m是空映射。比如map<string,int> m构建了一个字符串到整数的映射,这样我们可以把一个字符串和一个整数关联起来。
插入一对映射
在 C++ 中通过insert()函数向集合中插入一个新的映射,参数是一个pair。pair是一个标准库类型,定义在头文件utility中。可以看成是有两个成员变量first和second的结构体,并且重载了<运算符(先比较first大小,如果一样再比较second)。当我们创建一个pair时,必须提供两个类型。
我们可以像这样定义一个保存string和int的pair
pair<string, int> p;
make_pair(v1, v2)函数返回由v1和v2初始化的pair,类型可以从v1和v2的类型推断出来。我们向映射中加入新映射对的时候就是通过插入pair来实现的。如果插入的key之前已经存在了,将不会用插入的新的value,也就是这次插入是无效的。
#include<map>
#include<string>
#include<utility>
using namespace std;
int main() {
map<string, int> dict; // dict 是一个 string 到 int 的映射,存放每个名字对应的班级号,初始时为空
dict.insert(make_pair("Tom", 1)); // {"Tom"->1}
dict.insert(make_pair("Jone", 2)); // {"Tom"->1, "Jone"->2}
dict.insert(make_pair("Mary", 1)); // {"Tom"->1, "Jone"->2, "Mary"->1}
dict.insert(make_pair("Tom", 2)); // {"Tom"->1, "Jone"->2, "Mary"->1}
return 0;
}
访问映射
在 C++ 中访问映射和数组一样,直接用[]就能访问。比如dict[“Tom”]就可以获取"Tom"的班级了。而这里有一个比较神奇的地方,如果没有对"Tom"做过映射的话,此时你访问dict[“Tom”],系统将会自动为"Tom"生成一个映射,其value为对应类型的默认值(比如int的默认值是 00,string的默认值是空字符串)。
并且我们可以之后再给映射赋予新的值,比如dict[“Tom”] = 3,这样为我们提供了另一种方便的插入手段。实际上,我们常常通过下标访问的方式来插入映射,而不是通过用insert插入一个pair来实现。
当然有些时候,我们不希望系统自动为我们生成映射,这时候我们需要检测"Tom"是否已经有映射了,如果已经有映射再继续访问,需要用count()函数进行判断,在下面会详细介绍。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<string, int> dict; // dict 是一个 string 到 int
// 的映射,存放每个名字对应的班级号,初始时为空
dict["Tom"] = 1; // {"Tom"->1}
dict["Jone"] = 2; // {"Tom"->1, "Jone"->2}
dict["Mary"] = 1; // {"Tom"->1, "Jone"->2, "Mary"->1}
cout << "Mary is in class " << dict["Mary"] << endl;
cout << "Tom is in class " << dict["Tom"] << endl;
return 0;
}
判断关键字是否存在
在 C++ 中,如果你想知道某个关键字是否被映射过,你可以直接用count()函数。如果关键字存在,返回 1,否则会返回 0。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<string, int> dict; // {}
dict["Tom"] = 1; // {"Tom"->1}
dict["Jone"] = 2; // {"Tom"->1, "Jone"->2}
dict["Mary"] = 1; // {"Tom"->1, "Jone"->2, "Mary"->1}
if (dict.count("Mary"))
{
cout << "Mary is in class " << dict["Mary"] << endl;
} else {
cout << "Mary has no class" << endl;
}
return 0;
}
遍历映射
map的迭代器的定义和set差不多,map<T1, T2>::iterator it
就定义了一个迭代器,其中T1、T2分别是key和value的类型。
C++ 通过迭代器可以访问集合中的每个元素。这里迭代器指向的元素是一个pair,有first和second两个成员变量,分别代表一个映射的key和value。
我们用->运算符来获取值,it->first和(*it).first的效果是一样的,就是获取迭代器it指向的pair里first成员的值。
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
map<string, int> dict; // {}
dict["Tom"] = 1; // {"Tom"->1}
dict["Jone"] = 2; // {"Tom"->1, "Jone"->2}
dict["Mary"] = 1; // {"Tom"->1, "Jone"->2, "Mary"->1}
for (map<string, int>::iterator it = dict.begin(); it != dict.end(); it++)
{
cout << it->first << " -> " << it->second << endl; // first 是关键字, second 是对应的值
}
return 0;
}
注意,在 C++ 中遍历map是按照关键字从小到大遍历的,这一点和set有些共性。
清空
C++ 中只需要调用clear()函数就可清空map和其占用的内存。
C++ 中map常用函数总结
二维map
前面我们学习了二维的vector,实际上就是一个vector套一个vector。map当然也有二维的用法。而二维的map不仅仅能map套map,还能map套set。
①map套用set
为了帮助理解,我们举用一个生活中实际例子。全校有很多班级,每个班级每个人都会有中文名。现在我们需要用一种方式来记录全校的同学的名字。如果直接用一个set记录,对于重名的同学,那么就没办法分辨了。
我们可以把全校的班级进行编号,对每一个班级建立一个set,就是每个班级都映射成一个set,这样就能分辨不同班级的同名同学了。对于同班的同学来说,一般很少有重名的。
map<int, set<string> > s
就定义上面描述的数据结构,和二维vector一样,两个> >中间的空格不能少了。
这样我们就可以进行插入和查询了。比如对 22 班的yuhaoran同学,我们s[2].insert(“yuhaoran”)。然后查询yuhaoran是不是一个 22 班的人,s[2].count(“yuhaoran”)。然后还可以把他从 22 班删除,s[2].erase(“yuhaoran”)。
②map套用map
上面的结构没有办法解决同班同名的情况。实际上,如果同班同名,单单通过名字本身是无法分辨的,需要通过其他特征来分辨。所以为了简单起见,我们只需要记录每个班级同名的人的个数。
这时候,我们把里面的set改成map即可。map<int, map<string, int)> >
。2 班有一个yuhaoran,s[2][“yuhaoran”]++。2 班又转来了一个yuhaoran,s[2][“yuhaoran”]++。
现在 22 班一共有多少个yuhaoran?
cout << s[2]["yuhaoran"] << endl;
实例应用:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
map<int, map<string, int> > info;
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
int class_id;
string name;
cin >> class_id >> name;
info[class_id][name]++;
}
for (map<int, map<string, int> >::iterator it1 = info.begin(); it1 != info.end(); it1++)
{
for (map<string, int>::iterator it2 = it1->second.begin(); it2 != it1->second.end(); it2++)
{
cout << "There are " << it2->second <<" people named " << it2->first << " in class " << it1->first <<endl;
}
}
return 0;
}
四、stack
就像,vector、 map、 set一样,在C++的标准库中也已经有了stack的实现,功能齐全且易于使用。
标准库里面的stack在头文件里面,它的定义和map、set 、vector 都大同小异,如果你对前面的标准库已经使用得很熟练了,那么对于stack的使用你也会一目了 然。stack<T> s
定义了一个储存T类型数据的栈s。
标准库的栈除了支持push(),pop() 等基本操作以外,还支持top()来获取栈顶元素、empty() 判断栈是否为空、size() 计算栈中元素的个数。
下面通过一个示例了解stack:
#include <stack>
#include <iostream>
using namespace std;
int main()
{
stack<string> s;
s.push("123456");
s.push("aaaaa");
s.push("bbbbbb");
while (!s.empty())
{
cout << s.top() << endl;
s.pop();
}
return 0;
}
栈stack<T>
的方法总结如下:
五、queue
队列(queue) 是一种线性的数据结构,和栈一样是一种运算受限制的线性表。其限制只允许从表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。一般允许进行插入的一端我们称为队尾,允许删除的一端称为队首。队列的插入操作又叫入队,队列的删除操作又叫出队。
可以把队列想象成购物时排队结账的队伍,先排队的人会先结账,后排队的人会后结账,只能在队伍的末尾进行排队。这就是队列的特点,这就是队列的特点,具有 先进先出 的性质,而这一点和栈的 先进后出 正好相反。
队列的结构如下图所示:
队列的主要操作包括:
入队(push)
出队(pop)
判断队列是否为空(empty)
统计队列元素的个数(size)
访问队首元素(front)
构造一个队列
C++ 中直接构造一个queue的语句为:queue<T> q
。这样我们定义了一个名为q的储存T类型数据的队列。其中T是我们数组要储存的数据类型,可以是int、double、string或者其他自定义的数据类型等等。初始的时候q是空的。比如queue<int> q
定义了一个储存整数的队列q。
入队
通过push()方法在队尾插入一个新的元素。
#include <queue>
using namespace std;
int main()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
return 0;
}
获取队首元素
通过front()方法可以获取到当前的队首元素。
#include <queue>
#include <iostream>
using namespace std;
int main()
{
queue<int> q;
q.push(1);
cout << q.front() << endl;
q.push(2);
cout << q.front() << endl;
q.push(3);
cout << q.front() << endl;
return 0;
}
出队
通过pop()方法可以让队首元素出队。
#include <queue>
#include <iostream>
using namespace std;
int main()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
q.pop();
cout << q.front() << endl;
q.pop();
cout << q.front() << endl;
q.pop();
return 0;
}
判断队列是否为空
empty()方法可以判断队列是否为空,如果为空,方法返回true,否则返回false。这个方法的意义是,在我们每次调用front()和pop()之前,都要检查一下,保证队列不为空,否则去访问一个空队列的首部或者让一个空的队列出队就会发生错误。
#include <queue>
#include <iostream>
using namespace std;
int main()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
while (!q.empty()) {
// 如果队列不空,一直出队,用这样的方法清空一个队列,因为队列没有 clear 方法。
cout << q.front() << endl;
q.pop();
}
return 0;
}
清空
注意:队列没有 clear 方法
只能手动清空:
// 如果队列不空,用这样的方法清空一个队列
while (!q.empty())
{
q.pop();
}