C++ STL 学习
由于自己之前并不常用 STL 库,而 STL 库在很多算法中能够节省很多自己手打数据结构的时间,故这里开一个笔记来重新学习一下,也方便自己之后能够复习。
# Vector 的常见用法详解
【简介】vector 翻译为向量,我觉得用‘变长数组’来解释他更为合适。利用 vector 可以避免超内存等情况,节省空间。也可以用邻接表的方式来存储图。 使用 vector 头文件是 vector 需要 include.
1,vector 定义
单独定义一个 vector:
1 | vector<typename> name; |
上面这个定义其实相当于定义了一个一维数组 name [SIZE],只不过其长度可以根据需要进行变化。 这里的 typename 可以是任何数据类型。
如果 typename 也是 vector
1 | vector<vector<typename>> name; |
这里很容易联想到二维数组的定义,我们可以认为其是两个维度都可变长的二维数组。
然后是 vector 数组 的方法:
1 | vector<typename> Arrayname[arraysize]; |
这样 Arrayname [0] ~ Arrayname [arraysize-1] 都是一个 vector 容器。 这里是一个维度固定的二维数组。
2,vector 容器内元素的访问
-
通过下标访问。
和访问普通数组一样,对于 vector<typename> vi; 直接通过 vi [index 访问即可。这里的下标是从 0 到 vi.size ()-1 访问这个范围外的元素可能会出错。 -
通过迭代器访问。
迭代器 (iterator) 可以理解是一种类似指针的东西,其定义是:1
vector<typename>::iterator it;
这样得到了迭代器 it, 并且可以通过 * it 来访问 vector 的元素。
例如,这里有一个 vector 容器:1
2
3
4vector<int> v1;
for(int i = 1;i <= 5; i++){
vi.push_back(i);
}可以通过下面的方式进行访问容器内的元素
1
2
3
4vector<int>::iterator it = vi.begin();
for(int i=0;i < 5; i++){
printf("%d ",*(it + i)); //输出 vi[i]
}从这里可以看出 vi [i] 和 *(vi.begin ()+ i ) 是等价的。
这里 begin () 是取 vector 头元素的地址,这里引出 end() 这里需要注意 end() 是取 vector 末尾元素地址的下一个地址,不存储任何元素。故这里有了另一种遍历 vector 的方法。1
2
3for(vector<int>::iterator it = vi.begin(); it != vi.end() ; it++){
printf("%d ", *it);
}最后需要指出,在常用的 STL 容器中,只有 vector 和 string 中,才允许使用 vi.begin ()+3 这种迭代器加上整数的写法。
3,vector 常用函数实例解析
(1)push_back
顾名思义,push_back (x) 就是在 vector 尾元素添加一个元素 x, 时间复杂度 O (1)。
(2)pop_back
即删除 vector 的尾元素。
(3)size()
用来获取 vector 中元素的个数。返回 unsigned 类型。
(4)clear()
用来清空 vector 中的所有元素。
(5)insert()
insert (it,x) 用来向 vector 的任意迭代器 it 处插入一个元素 x。
(6)erase()
两种用法:删除单个元素,删除一个区间的所有元素。
①删除单个元素。 erase (it) 即删除迭代器为 it 处的元素。
②删除一个区间所有的元素,erase (first,end), 删除 [first,last) 内的所有元素。
4,vector 的常见用途
(1)存储数据
(2)用邻接表存储图
# set 的常见用法详解
【简介】set 翻译为集合,是指一个内部自动有序且不含重复元素的容器。考试中,有可能出现需要去掉重复元素的情况,这时候就可以用 set 来保留元素本身不考虑其个数。使用 set 需要添加 <set>。
1,set 的定义
单独定义一个 set
1 | set<typename> name; |
这里的写法和 vector 基本一样,或者说大部分的 STL 都是这样定义的,typename 可以是任何基本类型。 这里不再介绍各个数组之类的定义方式,和 vector 基本一样。
2,set 容器内元素的访问
set 只能通过迭代器 (iterator) 访问。定义方式和 vector 的迭代器定义方式一样。由于除了 vector 和 stirng 之外的迭代器都不只除 *(it + i) 的访问方式,因此只能按照下面方式枚举。
1 | \ |
这里的输出结果为 2 3 5。
3,set 常用函数实例解析
(1) insert ()
insert (x) 可将 x 插入 set 容器中,并自动递增排序和去重。
(2)find()
find (value) 返回 set 中对应值为 value 的迭代器。
(3)erase()
erase 也有两种用法:删除单个元素,删除区间内元素
①删除单个,st.erase (it),it 为所要删除元素的迭代器。可以结合 find () 函数来表示
st.erase (value),value 为所要删除的元素的值。
②删除一个区间的所有元素。 st.erase (first,last) 可以删除一个区间所有的元素,first 和 last 为迭代器形式,注意删除为 [first,last) 左闭右开。
(4)size()
用来获得 set 内元素的个数。
(5)clear()
clear () 用来清空 set 中所有的元素。
3,set 的常见用途
主要作用是自动去重并按照升序进行排序。
# string 的常见用法详解
【简介】在 C 语言中,一般用字符串数组 char str [] 来存放字符串。使用 string 会更加的方便。如果要使用 string,需要包含 string 头文件,注意 string.h 和 string 是不一样的头文件。
1,string 的定义
定义 string 的方式和基本数据类型相同,只需要在 string 上跟上变量名(可进行初始化)即可:
1 | string str; |
2,string 中内容的访问
(1)通过下标访问
一般来说,可以直接像字符数组那样去访问 string:
1 | for(int i=0;i < str.length(); i++){ |
输出结果就是 abcd 。
如果要读入或者输出整个字符串,则只能用 cin 和 cout:(如果想用 printf 输出 string,需要利用 c_str () 函数将 string 类型转换为字符数组来进行输出)
1 | string str; |
(2)通过迭代器访问
因为有些函数如 insert () 和 erase () 要求迭代器为参数,因此还是需要学习一个 string 迭代器的用法。
由于 string 不像其他 STL 容器需要参数,故可以这样定义迭代器。
1 | string::iterator it; |
这样就得到了迭代器 it,并且可以通过 * 来访问 string 的每一位。
1 | string str = "abcd"; |
3,string 常用函数实例解析
string 的函数有很多,这里只调出几个主要的。
(1)operator+=
这是 string 的加法,可以直接将两个 string 拼接起来。
1 | string str1 = "abc", str2 = "xyz"; |
(2)compare operator
两个 string 类型可以用 ==,!= , <, <= ,> , >= 进行比较大小,比较规则是字典序。
(3)length()/size()
length () 返回 stirng 的长度,即存放的字符数。size () 和 length () 基本相同。
(4) insert()
string 的 insert () 函数有很多写法,这里给出几个常用的写法。
①insert (pos,string),在 pos 号位置中插入字符串 string。
②insert (it,it2,it3),it 为字符串的欲插入位置,it2 和 it3 为待插入字符串的首尾迭代器,用来表示串 [it2,it3) 将被插入在 it 的位置上。
(5)erase()
erase () 有两种用法:删除单个元素,删除一个区间的所有元素。
①删除单个元素:erase (it) 用于删除单个元素,it 为所需要删除的元素的迭代器。
②删除一个区间所有元素,erase (first,last),同理,删除 [first,last),first 和 last 为相应的迭代器。这里还有一种用法,str.erase (pos,length),pos 为需要开始删除的起始位置,length 为删除的字符个数。
(6)clear()
clear () 用于清空 string 的数据。
(7)substr()
substr (pos,len) 返回从 pos 号位开始,长度为 len 的字串
(8)string::npos
string::npos 是一个常数,其本身的值为 - 1,但是由于是 unsigned_int 类型,因此实际上也可以认为其是 unsigned_int 类型最大值,string::npos 用以作为 find 函数匹配失败的返回值。
(9)find()
str.find (str2),当 str2 是 str 的子串时,返回其在 str 中第一次出现的位置,如果 str2 不是 str 的子串,返回 string::npos。
str.find (str2,pos),从 str 的 pos 号位开始匹配 str2,返回值和上相同。
(10)replace()
str.replace (pos,len,str2) 把 str 从 pos 号位开始,长度为 len 的子串替换为 str2。
str.replace (it1,it2,str2) 把 str 的迭代器 [it1,it2) 范围的子串替换为 str2。
# map 的常用用法详解
【简介】 map 翻译为映射。在定义数组时 (int array [100]),其实是定义了一个从 int 型到 int 型的映射,比如 array [0] = 25,array [4] =36 就分别是将 0 映射到 25,将 4 映射到 36. 一个 double 型数组则是将 int 映射到 double 型,这里不再介绍。这样,当我们需要其他类型作为关键字来作映射,会显得不太方便。这时可以用到 map,因为 map 可以将任何基本类型(包括 STL 的容器)映射到任意基本类型。
1,map 的定义
单独定义一个 map
1 | map<typename1,typename2> mp; |
上述代码中,第一个是键的类型,第二个是值得类型。注意如果是字符串到整型得映射,必须用 string 而不能用 char 数组。
2,map 容器内元素得访问
map 有两种访问方式:下标和迭代器
- 通过下标访问
和普通数组一样,例如对于 map<char,int> mp 得 map 来说,可以使用 mp [‘c’] 来访问对应对应得整数。当建立映射时,可以用 mp [‘c’] = 20 这样和普通数组一样得方式。但是要注意的是,map 中得键是唯一的。 - 通过迭代器访问
map 得迭代器定义和其他得 STL 容器迭代器定义得方式一样typename1 和 typename2 就是定义 map 时填写得类型,这样就得到了迭代器 it。1
map<typename1,typename2>::iterator it;
map 迭代器得使用方式和其他 STL 容器得迭代器不同,因为 map 得每一对映射都有两个 typename,这决定了必须能通过一个 it 来同时访问键和值。事实上,map 可以使用 it->first 来访问键,使用 it->second 来访问值。结果:1
2
3
4
5
6
7map<char,int> mp;
mp['m'] = 20;
mp['r'] = 30;
mp['a'] = 40;
for(map<char,int>::iterator it = mp.begin(); it != mp.end(); it++){
printf("%c %d\n", it->first, it->second);
}这里似乎有一个很神奇的现象:map 会以键从小到大的顺序自动排序,这是由于 map 内部使用红黑树实现的 (set 也是),在建立映射会自动实现从小到大的排序功能。1
2
3a 40
m 20
r 30
3,map 常用函数实例解析
(1)find ()
find (key) 返回键为 key 的映射的迭代器。
(2)erase()
有两种用法:删除单个元素,删除一个区间额你的所有元素。
①删除单个元素:map.erase (it),it 为需要删除的元素的迭代器。
示例如下:
1 | map<char,int> mp; |
第二种方式,mp.erase (key),key 为欲删除的映射的键。
②删除一个区间的所有元素。mp.erase (first,last),其中 first 为需要删除的区间的起始迭代器,而 last 则为需要删除的区间的末尾迭代器的一个地址,也为左闭右开的区间 [first,last)。
(3)size()
size () 用来获取 map 中映射的对数。
(4)clear()
clear () 用来清空 map 中的所有元素。
4,map 的常见用途
感觉 map 能用到的地方应该算是比较多的。
- 需要建立字符和整数之间映射的题目。
- 判断大整数或其他数据是否存在的题目,吧 map 当 bool 数组使用。
- 字符串和字符串的映射也可能会遇到。
** 扩展:mao 的键和值是唯一的,如果一个键需要对应多个值,只能用 multimap。另外,C++11 还加了 unordered_map,以散列代替 map 内部的红黑树实现,使其可以用来处理只映射而不按 key 排序的需求,速度会很快。
# queue 的常见用法详解
【定义】queue 翻译为队列,是一种很常见的数据结构。STL 实现了一个先进先出的容器。使用时需要添加 queue 的头文件。
1,queue 的定义
1 | queue<typename> name; |
2,queue 容器内元素的访问
由于队列本身是一种先进先出的限制性数据结构,因此再 STL 中只能用 front () 来访问队首元素,或者是 back () 来访问队尾元素。
3,queue 常用函数实例解析
(1)push ()
push (x) 将 x 进行入队。
(2)front(),back()
front 和 back 分别获取队首元素和队尾元素。
(3)pop()
pop () 令队首元素出队。
(4)empty()
empty () 检测 queue 是否为空。
(5)size()
size () 返回 queue 内元素的个数。
4,queue 的常见用途
当需要进行 BFS 时,可以直接用 queue 进行代替
有一点可能需要注意,再使用 front () 和 back () 之前,先用 empty () 判断队列是否为空,否则可能会出现错误。
# priority_queue 的常见用法详解
【简介】 priority_queue 又称为优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最高的哪一个。如队列有如下元素,且定义好了优先级:
1 | 桃子 (优先级3) |
那么出队顺序为梨子 -> 桃子 -> 苹果。
当然,可以在任何时候往优先队列加入元素,而优先队列底层的数据结构堆 (heap) 会随时调整结构,使得每次的队首元素都是优先级最大的。
1,priority_queue
要使用优先队列,应该添加头文件 queue。 其定义写法也和其他 STL 容器相同。
1 | priority_queue<typename> name; |
2,priority_queue 容器内元素的访问
和队列不一样的是,优先对立而没有了 front () 和 back () 函数,而只能通过 top () 函数来访问队首元素,也就是优先级最高的元素。
3,priority_queue 常用函数实例解析
(1)push ()
push (X) 将 X 入队,时间复杂度 O (logN),其中 N 为当前优先队列中的元素个数。
(2)top()
获取队首(堆顶)的元素。
(3)pop()
使队首(堆顶)的元素出队。
(4)empty()
检测优先队列是否为空。
(5)size()
size () 返回优先队列元素的个数。
4,priority_queue 内元素优先级的设置
下面来介绍下优先级的设置方法。
(1)基本数据类型的优先级设置
此处指的是 int,double,char 等可以直接使用的数据类型,对他们的游戏那寄设置一般使数字大的优先级越高,因此队首元素就是优先队列元素中最大的那个 (char 类型则是字典序最大的)。对基本数据类型来说,下面两种定义是等价的。
1 | priority_queue<int> q; |
可以发现,第二种定义方式的尖括号多出了两个参数:一个是 vector<int>,另一个是 less<int>。其中第二个参数填写的是承载底层数据结构堆的容器,如果第一个参数是 double 或 char 型,则此处只需要填写 vector<double > 或 vector<char>; 而第三个参数 less<int > 则是对第一个参数的比较类,less<int > 表示数字大的优先级大,而 greater<int > 表示数字小的优先级大。
(2)结构体的优先级设置
这里以开头举得水果的例子
1 | struct fruit{ |
现在如果希望水果的价格高作为优先级高,就需要重载小于号 "<"。重载是指对已有的运算符重新定义。见上代码块。可以看到在结构体增加了一个友元函数。 这里可以记一下, 优先对了的这个函数和 sort 的 cmp 函数的效果是相反的。
5,priority_qeue 的常见用途
可以解决一些贪心问题,也可以对 Dijkstra 算法进行优化。
# stack 的常见用法解析
【简介】stack 翻译为栈,是 STL 种实现一个后进先出的容器。
1,stack 的定义
需要添加头文件 stack,用法依然和其他 STL 容器一样。
1 | stack<typename> name; |
2,stack 容器内元素的访问
由于 stack 是一种后进先出的数据结构,在 STL 的 stack 只能通过 top () 来访问栈顶元素。
3,stack 常用函数实例解析
(1)push ()
将 x 入栈。
(2)top()
获得栈顶元素。
(3)pop()
弹出栈顶元素。
(4)empty()
判断 stack 是否为空。
(5)size()
返回 stack 内元素的个数。
4,stack 的常见用途
stack 一般用来模拟实现一些递归,防止程序堆栈内存的限制而导致程序运行出错。一般来说,程序的栈内存空间很小。
# pair 的常见用法解析
【简介】pair 是一个很实用的 "小玩意",当想要将两个元素绑在一起作为一个合成元素,又不想定义结构体时,可以用 pair。其可以看作时一个内部有两个元素的结构体。
1 | struct pair{ |
1,pair 的定义
要使用 pair,需要添加头文件 utility。注意:由于 map 实现过程中设计 pair,因此添加 map 头文件会自动添加 utility 头文件,故添加 map 头文件即可。
1 | pair<typename1,typeName2> name; |
如果想初始化,只需要加个括号里面添加初始化的内容即可。如果想要临时构建一个 pair,有下面两种方法。
1 | pair<string,int>("haha",5) |
2,pair 中元素的访问
pair 中只有两个元素,分别是 first 和 second,只需要按照正常结构体方式去访问即可。
3,pair 的使用函数解析
这里只需注意比较操作数,是先比较 first,first 相等时采取判别 second 的大小。
4,pair 的常见用途
- 用来代替二元结构体和其构造函数,可以节省编码时间。
- 作为 map 的键值来进行插入。