常用STL(Standard Template Library 标准模板库)入门(C++)
- 不定长数组:
vector
- 集合:
set
set
和结构体- 映射:
map
- 二维
map
动态数组 vector
-
引用库
#include <vector>
-
构造动态数组
- C++中直接构造一个
vector
的语句为:vector<T> vec
。这样我们定义了一个名为vec
的储存T
类型数据的动态数组;其中T
是数组要储存的数据类型,可以使int、floot、double、
或者其他自定义的数据类型等。 - 初始的时候
vec
是空的。如vector<int> a
定义了一个储存整数的动态数组a
- C++中直接构造一个
-
插入数组
- C++ 中通过
push_back()
方法在数组最后面插入一个新的元素
- C++ 中通过
-
获取长度并访问元素
- C++ 中通过
size()
方法获取vector
的长度,通过[]
操作直接访问vector
中的元素,这一点与数组一样
- C++ 中通过
-
修改元素
- C++ 中修改
vector
中某个元素只需用=
赋值
- C++ 中修改
-
删除元素
- 和插入一样,删除元素也只能在动态数组的末端进行操作
- C++ 中通过
pop_back()
方法删动态数组的最后一个元素
-
清空
- C++ 中都只需要调用
clear()
方法就可清空vector
- C++ 中
vector
的clear()
只是清空vector
,并不会清空开的内存。用一种方法可以清空vector
的内存:
- C++ 中都只需要调用
// 清空vector的内存
// vector<int> v;
vector<int>().swap(v);
C++ vector
方法总结
方法 | 功能 |
---|---|
push_back | 在末尾加入一个元素 |
pop_back | 在末尾弹出一个元素 |
size | 获取长度 |
clear | 清空 |
#include<cstdio>
#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]
vec.pop_back(); // [1,3]
vec.pop_back(); // [1]
for (int i = 0; i < vec.size(); i++) {
cout << vec[i] << endl;
}
vec.clear();
return 0;
}
vector
的一些用法
- 用动态数组存储自定义数据
- 动态数组不仅仅可以存储基本的数据类型,还能存储自定义数据类型,如结构体
struct Student {
string name; // 名字
int age; // 年龄
};
int main() {
vector<Student> class1; // 班级
Student stu1, stu2; // 学生1 学生2
stu1.name = "xiaoming";
stu1.age = 12;
stu2.name = "daxiong";
stu2.age = 18;
class1.push_back(stu1);
class1.push_back(stu2);
return 0;
}
- 构造函数
如果我们需要一个长度为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> > vec2
这样就定义了一个二维的动态数组。注意,<int> >
中间有一个空格,这个空格一定要加上,否则在一些老版本的编译器上将不能通过编译。- 通过上面的方法定义的二维数组,每一维都是空的,我们必须要一维一维的赋值。比如我们规定第一维的大小为n
#include<vector>
#include<cstdio>
#include<iostream>
using namespace std;
int main() {
int n = 5;
vector<vector<int> > vec2;
for (int i = 0; i < n; i++) {
vector<int> x(i+1, 1);
vec2.push_back(x);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < vec2[i].size(); j++) {
cout << vec2[i][j] << " ";
}
cout << endl;
}
return 0;
}
// 输出结果
1
1 1
1 1 1
1 1 1 1
1 1 1 1 1
- 二维动态数组的每一维的长度都可以不一样,可以是任意形状的
借助构造函数,我们可以快速构造一个n行m的动态数组,每个元素的初始值是0:vector<vector<int> > vec2(n, vector<int>(m, 0));
动态数组非常容易写错的写法:
vector<int> vec;
for (int i = 0; i < 10; i++) {
cout << vec[i] << endl;
}
// 没有 push_back 任何元素,会访问非法内存
vector<vector<int> > vec2;
for (int i = 0; i < 10; i++) {
vec2[i].push_back(i);
}
// 该代码当我们尝试访问vec2[i] 时,会访问非法内存,因为第一维的大小为0
// 下面有三种已修复正确的代码
vector<vector<int> > vec2(10, vector<int>(5)); // 第一维长度为5 全部为0
for (int i = 0; i < 10; ++i) {
vec2[i].push_back(i);
}
vector<vector<int> > vec2(10, vector<int>());
for (int i = 0; i < 10; ++i) {
vec2[i].push_back(i);
}
vector<vector<int> > vec2(10);
for (int i = 0; i < 10; ++i) {
vec2[i].push_back(i);
}
vector
的维度可以像数组一样更多,但超过两维以后操作起来麻烦,所以一般用vector
都只用到两维
//定义一个长度为n,全是0的动态数组x的三种方法:
vector<int> x(n);
vector<int> x(n, 0);
vector<int> x;
for (int i = 0; i < n; i++) {
x.push_back(0);
}
//动态输入 输出乘法口诀表
#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
int main() {
int n;
cin >> n;
vector<vector<int> > v2d;
for (int i = 0; i < n; i++) {
v2d.push_back(vector<int>()); // 给第一维赋值 大小为n 每一个元素是一个一维的vector
}
for (int i = 0; i < v2d.size(); i++) {
for (int j = 0; j <= i; j++) {
v2d[i].push_back((i+1)*(j+1)); // 用二维的vector来记录一个1到n的乘法表
}
}
for (int i = 0; i < v2d.size(); i++) {
for (int j = 0; j < v2d[i].size(); j++) {
cout << j + 1 << " * " << i + 1 << " = " << v2d[i][j] << "\t";
}
cout << endl;
}
return 0;
}
集合 set
-
引用库
#include <set>
-
构造一个集合
- C++ 中直接构造一个
set
语句为:set<T> s
这样我们定义了一个名为s
、存储T
类型数据的集合,其中T
是集合要存储的数据类型。初始的时候s
是空集合。如set<int> aa, set<string> bbb
等等
- C++ 中直接构造一个
-
插入元素
- C++中用
insert()
函数向集合中插入一个新的元素 - 如果集合中已经存在了某个元素,再次插入不会产生任何效果,集合中不会出现重复元素
- C++中用
-
删除元素
- C++ 中通过
erase()
函数删除集合中的一个元素,如果集合中不存在这个元素,不进行任何操作
- C++ 中通过
-
判断元素是否存在
- C++ 中如果你想知道某个元素是否在集合中出现,你可以直接用
count()
函数。 - 如果集合中存在我们要找的元素,返回1,否则会返回0.
- C++ 中如果你想知道某个元素是否在集合中出现,你可以直接用
-
遍历元素
- C++ 通过迭代器可以访问集合中的每个元素,迭代器就好像一根手指指向
set
中的某个元素。通过操作这个手指,我们可以改变它指的元素。 通过*
(解引用运算符,不是乘号的意思) 操作可以获取迭代器指向的元素。通过++
操作让迭代器指向下一个元素,同理--
操作让迭代器指向上一个元素。 - 迭代器的写法比较固定:
set<T>::iterator it
就定义了一个指向set<T>
这种集合的迭代器it
,T
是任意的数据类型。其中::iterator
是固定的写法 begin
函数返回容器中起始元素的迭代器,end
函数返回容器的尾后迭代器
- C++ 通过迭代器可以访问集合中的每个元素,迭代器就好像一根手指指向
注意,在C++中遍历set
是从小到大遍历的, 也就是说set
会帮我们排好序。
- 清空
- C++ 中调用
clear()
函数就可清空set
,同时会清空set
占用的内存
- C++ 中调用
C++ set
函数总结
在set
中插入、删除、查找某个元素的时间复杂度都是O(log n),并且内部元素是有序的
函数 | 功能 | 时间复杂度 |
---|---|---|
insert | 插入一个元素 | O(log n) |
erase | 删除一个元素 | O(log n) |
count | 统计集合中某个元素的个数 | O(log n) |
size | 获取元素个数 | O(1) |
clear | 清空 | O(n) |
#include<cstring>
#include<set>
#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"}
country.erase("America"); // {"China", "France"}
country.erase("England"); // {"China", "France"}
if (country.count("China")) {
cout << "China belong to country" << endl;
}
for (set<string>::iterator it = country.begin(); it != country.end(); it++) {
cout << *it << endl; // {"China", "France"}
}
country.clear();
return 0;
}
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
中了。
->
运算符 it->x
和 (*it).x
的效果是一样的,就是获取迭代器 it
指向结构体里 x
变量的值
/* 用set来存储一个二维坐标系上点的集合
输入
6
5 6
1 2
2 1
3 4
1 2
1 1
输出
1 1
1 2
2 1
3 4
5 6
*/
#include<set>
#include<cstdio>
#include<iostream>
using namespace std;
struct Point {
int x, y;
bool operator<(const Point &rhs) const { // 给结构体添加 < 的运算符重置
if(x == rhs.x) {
return y < rhs.y;
} else {
return x < rhs.x;
}
}
};
int main() {
int n;
set<Point> v;
cin >> n;
for (int i = 0; i < n; i++) {
Point temp; // 定义一个储存点的容器
cin >> temp.x >> temp.y;
v.insert(temp);
}
for (set<Point>::iterator it = v.begin(); it != v.end(); it++) {
cout << it->x << " " << it->y << endl;
}
return 0;
}
映射 map
关键字集合(key)–> 值集合(value)
-
引用库
#include<map>
-
构造一个映射
- 在C++中,构造一个
map
的语句为:map<T1, T2> m
. 这样我们定义了一个名为m
的从T1
类型到T2
类型的映射。初始的时候m
是空映射.- 如
map<string, int> m
构建了一个字符串到整数的映射,这样我们可以把一个字符串和一个整数关联起来
- 如
- 在C++中,构造一个
-
插入一对映射
- 在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
替代原来的value
,也就是这次插入是无效的
- 在C++ 中通过
-
访问映射
- 在C++中访问映射和数组一样,直接用
[]
就能访问。比如dict["Tom"]
就可以获取"Tom"
的班级。而如果没有对"Tom"
做过映射的话,此时你访问dict["Tom"]
,系统将会自动为"Tom"
生成一个映射,其value
为对应类型的默认值(比如int
的默认值是0,string
的默认值是空字符串) - 并且我们可以之后再给映射赋予新的值,比如
dict["Tom"] = 3
,这样为我们提供了另一种方便的插入手段。实际上常用通过下标访问的方式来插入映射,而不是通过用insert
插入一个pair
来实现。
- 在C++中访问映射和数组一样,直接用
-
判断关键字是否存在
- 如果你想知道某个关键字是否被映射过,可以使用
count()
函数。如果关键字存在,返回1,否则返回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
成员的值
注意,在C++中遍历map
是按照关键字从小到大遍历的, 这一点与set
有些共性
- 清空
- 调用
clear()
函数就可清空map
和其占用的内存
- 调用
C++中map
常用函数总结
函数 | 功能 | 时间复杂度 |
---|---|---|
insert | 插入一对映射 | O(log n) |
count | 判断关键字是否存在 | O(log n) |
size | 获取映射对个数 | O(1) |
clear | 清空 | O(n) |
#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}
if(dict.count("Mary")) {
cout << "Mary is in class "<< dict["Mary"] << endl;
} else {
cout << "Mary has no class" << endl;
}
for (map<string, int>::iterator it = dict.begin(); it != dict.end(); it++) {
cout << it->first << " -> " << it->second << endl; // first 是关键字,second 是对应值
}
return 0;
}
二维map
-
map
套用set
map<int, set<string> > s
就定义上面描述的数据结构,和二维vector
一样,两个> >
中间的空格不能少了- 可理解为给全校的班级进行编号,对每一个班级建立一个
set
,但这样只能分辨不同班级的同名同学,不能分辨同班同名同学。 - 如果对2班的小明进行插入查询删除操作,插入
s[2].insert("xiaoming")
,查询s[2].count("xiaoming")
,删除s[2].erase("xiaoming")
- 可理解为给全校的班级进行编号,对每一个班级建立一个
-
map
套用map
map<int, map<string, int> > s
. 可统计同班同名的同学,2班有一个小明,s[2]["xiaoming"]++
, 2班共有个s[2]["xiaoming"]
小明。
/* 统计各班级各名字个数
输入
6
1 zgh
2 yuhaoran
2 yuhaoran
1 party
100 xxx
50 xxx
输出
There are 1 people named party in class 1
There are 1 people named zgh in class 1
There are 2 people named yuhaoran in class 2
There are 1 people named xxx in class 50
There are 1 people named xxx in class 100
*/
#include<map>
#include<iostream>
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++) {
//it1->first 是一个 int,表示班级编号,it1->second 是一个 map<string, int> 容器
//遍历内层迭代器,即遍历某个班级里每个名字个数的 map
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;
}