C++ STL
参考文章:
1. vector
1.1 介绍
vector
为可变长数组(动态数组
),定义的vector
数组可以随时添加数值和删除元素。
注意:在局部区域中(比如局部函数里面)开vector数组,是在堆空间里面开的。
在局部区域开数组是在栈空间开的,而栈空间比较小,如果开了非常长的数组就会发生爆栈。
故局部区域不可以开大长度数组,但是可以开大长度vector。
头文件:
1 |
|
-
一维初始化
1
2
3vector<int> a; // 定义了一个名为a的一维数组,数组存储int类型数据
vector<double> b;// 定义了一个名为b的一维数组,数组存储double类型数据
vector<node> c;// 定义了一个名为c的一维数组,数组存储结构体类型数据,node是结构体类型指定长度和初始值的初始化
1
2
3vector<int> v(n);// 定义一个长度为n的数组,初始值默认为0,下标范围[0, n - 1]
vector<int> v(n, 1);// v[0] 到 v[n - 1]所有的元素初始值均为1
// 注意:指定数组长度之后(指定长度后的数组就相当于正常的数组了)初始化中有多个元素
1
vector<int> a{1, 2, 3, 4, 5};// 数组a中有五个元素,数组长度就为5
拷贝初始化
1
2
3vector<int> a(n + 1, 0);
vector<int> b(a); // 两个数组中的类型必须相同,a和b都是长度为n+1,初始值都为0的数组
vector<int> c = a; // 也是拷贝初始化,c和a是完全一样的数组 -
二维初始化
定义第一维固定长度为5,第二维可变化的二维数组1
2
3vector<int> v[5];// 定义可变长二维数组
// 注意:行不可变(只有5行), 而列可变,可以在指定行添加元素
// 第一维固定长度为5,第二维长度可以改变vector<int> v[5]
可以这样理解:长度为5的v数组,数组中存储的是vector<int>
数据类型,而该类型就是数组形式,故v
为二维数组。其中每个数组元素均为空,因为没有指定长度,所以第二维可变长。可以进行下述操作:1
2v[1].push_back(2);
v[2].push_back(3);行列均可变
1
2// 初始化二维均可变长数组
vector<vector<int>> v;// 定义一个行和列均可变的二维数组应用:可以在v数组里面装多个数组
1
2
3
4
5vector<int> t1{1, 2, 3, 4};
vector<int> t2{2, 3, 4, 5};
v.push_back(t1);
v.push_back(t2);
v.push_back({3, 4, 5, 6}) // {3, 4, 5, 6}可以作为vector的初始化,相当于一个无名vector行列长度均固定
n + 1
行m + 1
列初始值为01
vector<vector<int>> a(n + 1, vector<int>(m + 1, 0));
c++17或者c++20支持的形式(不常用),与上面相同的初始化
1
vector a(n + 1, vector(m + 1, 0));
1.2 方法函数
知道了如何定义初始化可变数组,下面就需要知道如何添加,删除,修改数据。
c指定为数组名称,含义中会注明算法复杂度。
代码 | 含义 | 时间复杂度 |
---|---|---|
c.front() |
返回第一个数据 | O(1) |
c.back() |
返回数组中的最后一个数据 | O(1) |
c.pop_back() |
删除最后一个数据 | O(1) |
c.push_back(element) |
在尾部加一个数据 | O(1) |
c.size() |
返回实际数据个数(unsigned类型) | O(1) |
c.clear() |
清除元素个数 | O(N) |
c.resize(n, v) |
改变数组大小为n,n个空间数值赋为v,如果没有默认赋值为0 | - |
c.insert(it, x) |
向任意迭代器it插入一个元素x,例如:c.insert(c.begin() + 2,-1) 将-1插入c[2]的位置 |
O(N) |
c.erase(first,last) |
删除[first,last)的所有元素 | O(N) |
c.begin() |
返回首元素的迭代器(通俗来说就是地址) | O(1) |
c.end() |
返回最后一个元素后一个位置的迭代器(地址) | O(1) |
c.empty() |
判断是否为空,为空返回真,反之返回假 | O(1) |
注意:
end()
返回的是最后一个元素的后一个位置的地址,不是最后一个元素的地址,所有STL容器均是如此使用
vi.resize(n, v)
函数时,若vi
之前指定过大小为pre
pre > n
:即数组大小变小了,数组会保存前n
个元素,前n
个元素值为原来的值,不是都为 vpre < n
:即数组大小变大了,数组会在后面插入n - pre
个值为v
的元素也就是说,这个初始值 v 只对新插入的元素生效。
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>
using namespace std;
void out(vector<int> &a) { for (auto &x: a) cout << x << " "; cout << "\n"; }
int main() {
vector<int> a(5, 1);
out(a); // 1 1 1 1 1
a.resize(10, 2);
out(a); // 1 1 1 1 1 2 2 2 2 2
a.resize(3, 3);
out(a); // 1 1 1
return 0;
}
排序
使用sort
排序要: sort(c.begin(), c.end())
;
sort()
为STL函数,请参考本文最后面STL函数系列。
对所有元素进行排序,如果要对指定区间进行排序,可以对sort()里面的参数进行加减改动。
1 |
|
1.3 访问
共三种方法:
-
下标法 : 和普通数组一样
注意:一维数组的下标是从 0 到 v.size()-1 ,访问之外的数会出现越界错误 -
迭代器法 : 类似指针一样的访问 ,首先需要声明迭代器变量,和声明指针变量一样,可以根据代码进行理解(附有注释)。
1
2vector<int> vi; // 定义一个vi数组
vector<int>::iterator it = vi.begin();// 声明一个迭代器指向vi的初始位置 -
使用auto :非常简便,但是会访问数组的所有元素(特别注意0位置元素也会访问到)
1.3.1 下标访问
直接和普通数组一样进行访问即可。
1 |
|
1.3.2 迭代器访问
类似指针,迭代器就是充当指针的作用。
1 |
|
-
方式一:
1
2
3
4vector<int>::iterator it = vi.begin();
for(int i = 0; i < 5; i++)
cout << *(it + i) << " ";
cout << "\n"; -
方式二
1
2
3
4
5
6
7
8
9
10
11vector<int>::iterator it;
for(it = vi.begin(); it != vi.end();it ++)
cout << *it << " ";
// vi.end()指向尾元素地址的下一个地址
// 或者
auto it = vi.begin();
while (it != vi.end()) {
cout << *it << "\n";
it++;
}
1.3.3 智能指针
只能遍历完数组,如果要指定的内容进行遍历,需要另选方法。
auto
能够自动识别并获取类型。
1 |
|
vector
注意:
vi[i]
和*(vi.begin() + i)
等价,与指针类似。
vector
和string
的STL容器支持*(it + i)
的元素访问,其它容器可能也可以支持这种方式访问,但用的不多,可自行尝试。
2. stack
2.1 介绍
栈为数据结构的一种,是STL中实现的一个先进后出,后进先出的容器。
//头文件需要添加
1 |
|
2.2 方法函数
代码 | 含义 | 时间复杂度 |
---|---|---|
s.push(ele) |
元素ele入栈,增加元素 | O(1) |
s.pop() |
移除栈顶元素 | O(1) |
s.top() |
取得栈顶元素(但不删除) | O(1) |
s.empty() |
检测栈内是否为空,空为真 | O(1) |
s.size() |
返回栈内元素的个数 | O(1) |
2.3 栈遍历
2.3.1 栈遍历
栈只能对栈顶元素进行操作,如果想要进行遍历,只能将栈中元素一个个取出来存在数组中
1 |
|
2.3.2 数组模拟栈进行遍历
通过一个数组对栈进行模拟,一个存放下标的变量top
模拟指向栈顶的指针。
一般来说单调栈和单调队列写法均可使用额外变量 tt 或 hh 来进行模拟
特点: 比STL的stack
速度更快,遍历元素方便
1 |
|
3. queue
3.1 介绍
队列是一种先进先出的数据结构。
1 |
|
3.2 方法函数
代码 | 含义 | 时间复杂度 |
---|---|---|
q.front() |
返回队首元素 | O(1) |
q.back() |
返回队尾元素 | O(1) |
q.push(element) |
尾部添加一个元素element进队 | O(1) |
q.pop() |
删除第一个元素,出队 | O(1) |
q.size() |
返回队列中元素个数,返回值类型unsigned int | O(1) |
q.empty() |
判断是否为空,队列为空,返回true | O(1) |
3.3 队列模拟
使用q[]
数组模拟队列
hh
表示队首元素的下标,初始值为0
tt
表示队尾元素的下标,初始值为-1
,表示刚开始队列为空
一般来说单调栈和单调队列写法均可使用额外变量 tt 或 hh 来进行模拟
1 |
|
4. deque
5. priority_queue
5.1 介绍
优先队列是在正常队列的基础上加了优先级,保证每次的队首元素都是优先级最大的。
可以实现每次从优先队列中取出的元素都是队列中优先级最大的一个。
它的底层是通过堆来实现的。
1 |
|
5.2 函数方法
代码 | 含义 | 时间复杂度 |
---|---|---|
q.top() |
访问队首元素 | O(1) |
q.push() |
入队 | O(logN) |
q.pop() |
堆顶(队首)元素出队 | O(logN) |
q.size() |
队列元素个数 | O(1) |
q.empty() |
是否为空 | O(1) |
注意没有clear()
! 不提供该方法
优先队列只能通过top()
访问队首元素(优先级最高的元素)
5.3 设置优先级
5.3.1 基本数据类型的优先级
1 |
|
参数解释:
-
第一个参数:就是优先队列中存储的数据类型
-
第二个参数:
vector<int>
是用来承载底层数据结构堆的容器,若优先队列中存放的是double
型数据,就要填vector< double >
总之存的是什么类型的数据,就相应的填写对应类型。同时也要改动第三个参数里面的对应类型。 -
第三个参数:
less<int>
表示数字大的优先级大,堆顶为最大的数字
greater<int>
表示数字小的优先级大,堆顶为最小的数字
int
代表的是数据类型,也要填优先队列中存储的数据类型
下面介绍基础数据类型优先级设置的写法:
-
基础写法(非常常用):
1
2
3
4priority_queue<int> q1; // 默认大根堆, 即每次取出的元素是队列中的最大值
priority_queue<int, vector<int>, less<int> > q2; // 大根堆, 每次取出的元素是队列中的最大值,同第一行
priority_queue<int, vector<int>, greater<int> > q3; // 小根堆, 每次取出的元素是队列中的最小值 -
自定义排序(不常见,主要是写着麻烦):
下面的代码比较长,基础类型优先级写着太麻烦,用第一种即可。1
2
3
4
5
6
7
8
9
10
11
12struct cmp1 {
bool operator()(int x, int y) {
return x > y;
}
};
struct cmp2 {
bool operator()(const int x, const int y) {
return x < y;
}
};
priority_queue<int, vector<int>, cmp1> q1; // 小根堆
priority_queue<int, vector<int>, cmp2> q2; // 大根堆
5.3.2 高级数据类型(结构体)优先级
即优先队列中存储结构体类型,必须要设置优先级,即结构体的比较运算(因为优先队列的堆中要比较大小,才能将对应最大或者最小元素移到堆顶)。
优先级设置可以定义在结构体内进行小于号重载,也可以定义在结构体外。
1 |
|
-
版本一:自定义全局比较规则
1
2
3
4
5
6
7
8
9
10//定义的比较结构体
//注意:cmp是个结构体
struct cmp {//自定义堆的排序规则
bool operator()(const Point& a,const Point& b) {
return a.x < b.x;
}
};
//初始化定义,
priority_queue<Point, vector<Point>, cmp> q; // x大的在堆顶 -
版本二:直接在结构体里面写
因为是在结构体内部自定义的规则,一旦需要比较结构体,自动调用结构体内部重载运算符规则。
结构体内部有两种方式:
优先队列的定义:
1
priority_queue<Point> q;
方式一 :
1
2
3
4
5
6struct node {
int x, y;
friend bool operator < (Point a, Point b) {//为两个结构体参数,结构体调用一定要写上friend
return a.x < b.x;//按x从小到大排,x大的在堆顶
}
};方式二 :(推荐此种)
1
2
3
4
5
6struct node {
int x, y;
bool operator < (const Point &a) const {//直接传入一个参数,不必要写friend
return x < a.x;//按x升序排列,x大的在堆顶
}
};注意: 优先队列自定义排序规则和
sort()
函数定义cmp
函数很相似,但是最后返回的情况是相反的。即相同的符号,最后定义的排列顺序是完全相反的。
所以只需要记住sort
的排序规则和优先队列的排序规则是相反的就可以了。当理解了堆的原理就会发现,堆调整时比较顺序是孩子和父亲节点进行比较,如果是
>
,那么孩子节点要大于父亲节点,堆顶自然是最小值。
5.4 存储特殊类型的优先级
5.4.1 存储pair类型
- 排序规则:
默认先对pair
的first
进行降序排序,然后再对second
降序排序
对first
先排序,大的排在前面,如果first
元素相同,再对second
元素排序,保持大的在前面。
1 |
|
查看输出
1 |
|
6. map
6.1 介绍
映射类似于函数的对应关系,每个x
对应一个y
,而map
是每个键对应一个值。会python的朋友学习后就会知道这和python的字典非常类似。
比如说:学习 对应 看书,学习 是键,看书 是值。
学习->看书
玩耍 对应 打游戏,玩耍 是键,打游戏 是值。
玩耍->打游戏
1 |
|
map特性:map会按照键的顺序从小到大自动排序,键的类型必须可以比较大小
6.2 函数方法
6.2.1 函数方法
代码 | 含义 |
---|---|
mp.find(key) |
返回键为key 的映射的迭代器 。注意:用find 函数来定位数据出现位置,它返回一个迭代器。当数据存在时,返回数据所在位置的迭代器,数据不存在时,返回mp.end() |
mp.erase(it) |
删除迭代器对应的键和值 |
mp.erase(key) |
根据映射的键删除键和值 |
mp.erase(first,last) |
删除左闭右开区间迭代器对应的键和值 |
mp.size() |
返回映射的对数 |
mp.clear() |
清空map中的所有元素 |
mp.insert() |
插入元素,插入时要构造键值对 |
mp.empty() |
如果map为空,返回true,否则返回false |
mp.begin() |
返回指向map第一个元素的迭代器(地址) |
mp.end() |
返回指向map尾部的迭代器(最后一个元素的下一个地址) |
mp.rbegin() |
返回指向map最后一个元素的迭代器(地址) |
mp.rend() |
返回指向map第一个元素前面(上一个)的逆向迭代器(地址) |
mp.count(key) |
查看元素是否存在,因为map中键是唯一的,所以存在返回1,不存在返回0 |
mp.lower_bound() |
返回一个迭代器,指向键值 >= key的第一个元素 |
mp.upper_bound() |
返回一个迭代器,指向键值 > key的第一个元素 |
空间复杂度
在C++的标准模板库(STL)中,map
是一种基于红黑树实现的关联容器,它存储键值对,其中每个键唯一,且键值对是按照键的顺序排序的。由于map
是基于红黑树的,我们可以分析其空间复杂度。
空间复杂度分析:
-
红黑树节点:每个节点包含存储的键值对、颜色标记(通常是一个布尔值表示红或黑),以及指向其父节点、左子节点和右子节点的指针。因此,每个节点的空间复杂度是常数
O(1)
。 -
整体空间复杂度:由于每个键值对在
map
中由一个红黑树节点表示,因此map
的总空间复杂度与其中存储的键值对数量成线性关系,即O(n)
,其中n
是键值对的数量。
附加因素:
-
内存分配器:C++ STL允许使用自定义内存分配器,这可能会影响实际使用的内存量,但不改变总体空间复杂度。
-
节点平衡信息:红黑树通过维持树的平衡来确保操作的效率,这需要额外的信息(即节点颜色),但同样不改变空间复杂度。
-
指针和对齐:实际内存使用量会受到系统指针大小和数据对齐需求的影响。例如,在64位系统上,指针通常占8字节。这意味着每个节点的实际内存使用可能高于简单估计,但这依然是线性关系。
综上所述,STL中map
的空间复杂度为O(n)
,其中n
是存储的键值对数量。这反映了在实际应用中使用map
时应考虑的内存使用情况。
6.2.2 注意点
下面说明部分函数方法的注意点
注意:
查找元素是否存在时,可以使用
①mp.find()
②mp.count()
③mp[key]
但是第三种情况,如果不存在对应的key时,会自动创建一个键值对(产生一个额外的键值对空间)
所以为了不增加额外的空间负担,最好使用前两种方法
6.2.3 迭代器进行正反向遍历
mp.begin()
和mp.end()
用法:
1 |
|
查看输出
1 |
|
mp.rbegin()
和mp.rend()
1 |
|
查看输出
1 |
|
6.3 添加元素
1 |
|
-
方式一:
1
2mp["学习"] = "看书";
mp["玩耍"] = "打游戏"; -
方式二:插入元素构造键值对
1
mp.insert(make_pair("vegetable","蔬菜"));
查看make_pair()
在 C++ 中,
make_pair
是一个非常有用的函数模板,它属于<utility>
头文件。make_pair
用于创建一个std::pair
对象,这种对象可以存储一对值,这对值可能属于相同的类型或者不同的类型。使用make_pair
可以方便地生成pair
对象,而不需要显式指定其中值的类型,因为这些类型可以通过函数参数自动推导得出。基本用法
make_pair
的基本语法如下:1
std::pair<T1, T2> make_pair(T1 value1, T2 value2);
其中
T1
和T2
是pair
第一个和第二个值的类型,分别对应value1
和value2
这两个参数。make_pair
返回一个新的pair
对象,该对象的第一个元素类型为T1
,第二个元素类型为T2
。示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#include <iostream>
#include <utility> // For std::make_pair
int main() {
// 创建一个 pair,存储一个 int 和一个 double
auto p1 = std::make_pair(42, 3.14);
// 创建一个 pair,存储两个 string
auto p2 = std::make_pair("Hello", "World");
std::cout << "p1: " << p1.first << ", " << p1.second << std::endl;
std::cout << "p2: " << p2.first << ", " << p2.second << std::endl;
return 0;
}在这个例子中,使用
make_pair
创建了两个pair
对象:p1
存储了一个int
和一个double
,p2
存储了两个string
。注意,我们使用了auto
关键字让编译器自动推断pair
对象的完整类型,这是使用make_pair
的常见做法,因为它简化了代码并使其更加清晰。优点
- 类型推导: 最大的优点是不需要手动指定类型,
make_pair
会自动推导参数的类型。 - 代码简洁: 使用
make_pair
可以使得代码更简洁、更易读。
使用场景
make_pair
在需要创建pair
对象时非常有用,特别是当用作其他容器(如map
、set
中的元素或者作为函数返回值)时。例如,在std::map
中插入元素时,经常会用到make_pair
来生成键值对。 - 类型推导: 最大的优点是不需要手动指定类型,
-
方式三:
1
mp.insert(pair<string,string>("fruit","水果"));
-
方式四:
1
mp.insert({"hahaha","wawawa"});
6.4 访问元素
6.4.1 下标访问
(大部分情况用于访问单个元素)
1 |
|
6.4.2 遍历访问
-
方式一:迭代器访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14map<string,string>::iterator it;
for(it = mp.begin(); it != mp.end(); it++) {
// 键 值
// it是结构体指针访问所以要用 -> 访问
cout << it->first << " " << it->second << "\n";
//*it是结构体变量 访问要用 . 访问
//cout<<(*it).first<<" "<<(*it).second;
}
- 方式二:智能指针访问
```c++
for(auto i : mp)
cout << i.first << " " << i.second << endl;//键,值 -
方式三:对指定单个元素访问
1
2map<char,int>::iterator it = mp.find('a');
cout << it -> first << " " << it->second << "\n"; -
方式四:c++17特性才具有
1
2
3for(auto [x, y] : mp)
cout << x << " " << y << "\n";
//x,y对应键和值
6.5 与unordered_map的比较
这里就不单开一个大目录讲unordered_map
了,直接在map
里面讲了。
6.5.1 内部实现原理
map
:内部用红黑树实现,具有自动排序(按键从小到大)功能。
unordered_map
:内部用哈希表实现,内部元素无序杂乱。
6.5.2 效率比较
map
:
优点:内部用红黑树实现,内部元素具有有序性,查询删除等操作复杂度为
缺点:占用空间,红黑树里每个节点需要保存父子节点和红黑性质等信息,空间占用较大。
unordered_map
:
优点:内部用哈希表实现,查找速度非常快(适用于大量的查询操作)。
缺点:建立哈希表比较耗时。
两者方法函数基本一样,差别不大。
注意:
随着内部元素越来越多,两种容器的插入删除查询操作的时间都会逐渐变大,效率逐渐变低。
使用[]查找元素时,如果元素不存在,两种容器都是创建一个空的元素;如果存在,会正常索引对应的值。所以如果查询过多的不存在的元素值,容器内部会创建大量的空的键值对,后续查询创建删除效率会大大降低。
查询容器内部元素的最优方法是:先判断存在与否,再索引对应值(适用于这两种容器)
1
2
3
4
5
// 以 map 为例
map<int, int> mp;
int x = 999999999;
if(mp.count(x)) // 此处判断是否存在x这个键
cout << mp[x] << "\n"; // 只有存在才会索引对应的值,避免不存在x时多余空元素的创建
另外:
还有一种映射:multimap
键可以重复,即一个键对应多个值,如要了解,可以自行搜索。
7. set
7.1 介绍
set容器中的元素不会重复,当插入集合中已有的元素时,并不会插入进去,而且set容器里的元素自动从小到大排序。
即:set里面的元素不重复 且有序
1 |
|
7.2 函数方法
代码 | 含义 |
---|---|
s.begin() |
返回set容器的第一个元素的地址(迭代器) |
s.end() |
返回set容器的最后一个元素的下一个地址(迭代器) |
s.rbegin() |
返回逆序迭代器,指向容器元素最后一个位置 |
s.rend() |
返回逆序迭代器,指向容器第一个元素前面的位置 |
s.clear() |
删除set容器中的所有的元素,返回unsigned int类型 |
s.empty() |
判断set容器是否为空 |
s.insert() |
插入一个元素 |
s.size() |
返回当前set容器中的元素个数 |
erase(iterator) |
删除定位器iterator指向的值 |
erase(first,second) |
删除定位器first和second之间的值 |
erase(key_value) |
删除键值key_value的值 |
查找 | |
s.find(element) |
查找set中的某一元素,有则返回该元素对应的迭代器,无则返回结束迭代器 |
s.count(element) |
查找set中的元素出现的个数,由于set中元素唯一,此函数相当于查询element是否出现 |
s.lower_bound(k) |
返回大于等于k的第一个元素的迭代器 |
s.upper_bound(k) |
返回大于k的第一个元素的迭代器 |
7.3 访问
- 迭代器访问
1 |
|
- 智能指针
1 |
|
- 访问最后一个元素
1 |
|
7.4 重载<运算符
- 基础数据类型
方式一:改变set排序规则,set中默认使用less比较器,即从小到大排序。(常用)
1 |
|
方式二:重载运算符。(很麻烦,不太常用,没必要)
1 |
|
方式三:初始化时使用匿名函数定义比较规则
1 |
|
- 高级数据类型(结构体)
直接重载结构体运算符即可,让结构体可以比较。
1 |
|
7.5 其它set
multiset
: 元素可以重复,且元素有序
unordered_set
:元素无序且只能出现一次
unordered_multiset
: 元素无序可以出现多次
8. pair
9. string
9.1 介绍
string是一个字符串类,和char
型字符串类似。
可以把string理解为一个字符串类型,像int一样可以定义
9.2 初始化及定义
1 |
|
简单使用
- 访问单个字符:
1 |
|
- string数组使用:
1 |
|
查看输出
1 |
|
9.3 string 特性
-
支持比较运算符
string字符串支持常见的比较操作符(>,>=,<,<=,==,!=
),支持string与C-string的比较(如str < "hello"
)。
在使用>,>=,<,<=
这些操作符的时候是根据“当前字符特性”将字符按 字典顺序 进行逐一得 比较。字典排序靠前的字符小, 比较的顺序是从前向后比较,遇到不相等的字符就按这个位置上的两个字符的比较结果确定两个字符串的大小(前面减后面)。 -
支持
+
运算符,代表拼接字符串
string字符串可以拼接,通过"+"运算符进行拼接。1
2
3
4string s1 = "123";
string s2 = "456";
string s = s1 + s2;
cout << s; //123456
9.4 读入详解
读入字符串,遇空格,回车结束
1 |
|
读入一行字符串(包括空格),遇回车结束
1 |
|
注意: getline(cin, s)
会获取前一个输入的换行符,需要在前面添加读取换行符的语句。如:getchar()
或 cin.get()
查看实例
cin
与cin.getline()
混用cin输入完后,回车,cin遇到回车结束输入,但回车还在输入流中,cin并不会清除,导致
getline()
读取回车,结束。
需要在cin后面加cin.ignore()
;主动删除输入流中的换行符。(不常用)
错误读取:
1 |
|
正确读取:
1 |
|
cin和cout解锁
代码(写在main函数开头):
1 |
|
为什么要进行cin和cout的解锁,原因是:
在一些题目中,读入的数据量很大,往往超过了1e5(105)的数据量,而cin和cout的读入输出的速度很慢(是因为cin和cout为了兼容C语言的读入输出在性能上做了妥协),远不如scanf和printf的速度,具体原因可以搜索相关的博客进行了解。
所以对cin和cout进行解锁使cin和cout的速度几乎接近scanf和printf,避免输入输出超时。
注意:cin cout
解锁使用时,不能与 scanf,getchar, printf,cin.getline()
混用,一定要注意,会出错。
string与C语言字符串(C-string)的区别
string
是C++的一个类,专门实现字符串的相关操作。具有丰富的操作方法,数据类型为string,字符串结尾没有\0字符
C-string
C语言中的字符串,用char数组实现,类型为const char *,字符串结尾以\0结尾
一般来说string向char数组转换会出现一些问题,所以为了能够实现转换,string有一个方法c_str()
实现string向char数组的转换。
1 |
|
9.5 函数方法
- 获取字符串长度
代码 | 含义 |
---|---|
s.size() 和s.length() |
返回string对象的字符个数,他们执行效果相同。 |
s.max_size() |
返回string对象最多包含的字符数,超出会抛出length_error 异常 |
s.capacity() |
重新分配内存之前,string对象能包含的最大字符数 |
- 插入
代码 | 含义 |
---|---|
s.push_back() |
在末尾插入 |
例:s.push_back('a') |
末尾插入一个字符a |
s.insert(pos,element) |
在pos 位置插入element |
例:s.insert(s.begin(),'1') |
在第一个位置插入1 字符 |
s.append(str) |
在s 字符串结尾添加str 字符串 |
例:s.append("abc") |
在s 字符串末尾添加字符串“abc” |
- 删除
代码 | 含义 |
---|---|
erase(iterator p) |
删除字符串中p 所指的字符 |
erase(iterator first, iterator last) |
删除字符串中迭代器区间[first,last) 上所有字符 |
erase(pos, len) |
删除字符串中从索引位置pos 开始的len 个字符 |
clear() |
删除字符串中所有字符 |
- 字符替换
代码 | 含义 |
---|---|
s.replace(pos,n,str) |
把当前字符串从索引pos 开始的n 个字符替换为str |
s.replace(pos,n,n1,c) |
把当前字符串从索引pos 开始的n 个字符替换为n1 个字符c |
s.replace(it1,it2,str) |
把当前字符串[it1,it2) 区间替换为str 。it1 , it2 为迭代器 |
- 大小写转换
法一:
代码 | 含义 |
---|---|
tolower(s[i]) |
转换s[i] 为小写 |
toupper(s[i]) |
转换s[i] 为大写 |
法二:
通过stl的transform
算法配合tolower
和toupper
实现。
有4个参数,前2个指定要转换的容器的起止范围,第3个参数是结果存放容器的起始位置,第4个参数是一元运算。
1 |
|
- 分割
代码 | 含义 |
---|---|
s.substr(pos,n) |
截取从pos 索引开始的n 个字符 |
- 查找
代码 | 含义 |
---|---|
s.find(str, pos) |
在当前字符串的pos 索引位置(默认为0)开始,查找子串str ,返回找到的位置索引,-1 表示查找不到子串 |
s.find(c, pos) |
在当前字符串的pos 索引位置(默认为0)开始,查找字符c ,返回找到的位置索引,-1 表示查找不到字符 |
s.rfind(str, pos) |
在当前字符串的pos 索引位置开始,反向查找子串s ,返回找到的位置索引,-1 表示查找不到子串 |
s.rfind(c,pos) |
在当前字符串的pos 索引位置开始,反向查找字符c ,返回找到的位置索引,-1 表示查找不到字符 |
s.find_first_of(str, pos) |
在当前字符串的pos 索引位置(默认为0)开始,查找子串s 的字符,返回找到的位置索引,-1 表示查找不到字符 |
s.find_first_not_of(str,pos) |
在当前字符串的pos 索引位置(默认为0)开始,查找第一个不位于子串s 的字符,返回找到的位置索引,-1 表示查找不到字符 |
s.find_last_of(str, pos) |
在当前字符串的pos 索引位置开始,查找最后一个位于子串s 的字符,返回找到的位置索引,-1 表示查找不到字符 |
s.find_last_not_of(str, pos) |
在当前字符串的pos 索引位置开始,查找最后一个不位于子串s 的字符,返回找到的位置索引,-1 表示查找不到子串 |
1 |
|
- 排序
1 |
|
其他STL函数
sort()
C++标准模板库(STL)中的sort()
函数是一个非常强大的算法,用于对容器内的元素进行排序。它被定义在<algorithm>
头文件中,可以对几乎所有类型的容器进行排序,包括数组、向量(vector
)、列表(list
)等,只要容器的元素可以比较大小。
- 基本用法:
sort()
函数最简单的形式只需要两个参数,即要排序的范围的开始和结束迭代器。这种情况下,它会默认使用元素类型的<
运算符来比较元素大小,因此被排序的元素类型必须支持这个运算符。
1 |
|
- 使用greater和less
在C++中,你可以使用std::sort
函数结合std::greater<>
或std::less<>
来实现降序或自定义排序。std::greater<>
和std::less<>
是定义在头文件<functional>
中的比较函数对象。
使用std::greater<>
实现降序排序
当你想对一个数组或向量进行降序排序时,可以传递std::greater<>
作为std::sort
的第三个参数。
对数组进行降序排序
1 |
|
对向量进行降序排序
1 |
|
使用std::less<>
进行升序排序
实际上,std::sort
默认就是使用std::less<>
进行升序排序的,所以通常你不需要显式地指定它。但是如果你想要明确表示出来,或者使用自定义类型进行排序时指定排序规则,可以这样做:
1 |
|
使用std::less<>
和std::greater<>
可以让代码的意图更加明显,尤其是在涉及复杂类型或自定义排序逻辑的情况下。
- 自定义比较函数:
sort()
也支持自定义比较函数,这使得我们可以控制排序的具体行为,比如实现降序排序或根据对象的某个特定字段进行排序。
自定义比较函数可以是普通函数,也可以是lambda表达式。这个函数需要接收两个待比较的元素作为参数,并返回一个布尔值,表示第一个参数是否应该排在第二个参数之前。
1 |
|
正确使用自定义比较函数
自定义比较函数应当确保对于任意的元素a
和b
,如果a
应排在b
之前,则返回true
;否则返回false
。这通常意味着你应该使用<
(或>
,取决于你想要升序还是降序排序)来比较元素。
如果你真的需要使用<=
或>=
逻辑,可能是因为你想要实现某种特殊的排序逻辑,那么你需要非常小心,确保这种比较逻辑不会违反排序算法要求的性质。
- 重载运算符
如果不使用自定义比较函数,你可以通过为你的类或结构体重载运算符来实现排序。这种方法尤其适合当你需要频繁对某个自定义类型的对象进行排序,或者你想要为对象的默认排序行为提供一个明确的规则。最常见的是重载<
运算符,这样sort()
函数就可以直接使用这个运算符来比较类的实例。
重载<
运算符的例子
假设你有一个Student
类,其中包含姓名(name
)和分数(score
),你想要根据分数来排序学生,分数高的排在前面。你可以这样重载<
运算符:
1 |
|
注意事项
- 当你重载比较运算符来为
sort()
提供排序逻辑时,同样需要确保你的比较逻辑满足严格弱序的要求。在上面的例子中,通过使用>
运算符来比较分数,我们确保了分数较高的学生会被视为较小(即在排序数组中排在前面),这是因为sort()
默认执行升序排序。 - 重载运算符应该提供一致且预期内的行为,避免在不同上下文中产生混淆或误解。特别是当与标准库算法一起使用时,需要确保重载的运算符行为符合算法的预期。
- 重载运算符的类或结构体需要被设计为可以被排序算法访问,这通常意味着重载的运算符需要是公有的(
public
)。
- 经典案例
排序结构体或类的对象:
当我们需要对一组自定义类型的对象进行排序时,可以通过提供自定义比较函数来实现。
1 |
|
这种方式让sort()
算法变得极其灵活和强大,几乎可以应用于任何排序场景。不过,需要注意的是,sort()
默认实现的是不稳定排序,即相等元素的原始顺序可能会改变。如果需要稳定排序,可以考虑使用std::stable_sort()
。
数字 和 字符串互转
C++标准模板库(STL)提供了多种方式来实现字符串和数字之间的转换。
- 使用
std::stoi
、std::stol
、std::stod
等函数将字符串转换为数字: 这些函数分别用于将字符串转换为int
、long
、double
等类型的数字。它们是C++11引入的,位于<string>
头文件中。std::stoi("123")
将字符串"123"转换为整数123。std::stod("123.45")
将字符串"123.45"转换为双精度浮点数123.45。
- 使用
std::to_string
函数将数字转换为字符串:std::to_string
函数可以接受基本数字类型(如int
、long
、float
、double
等)作为参数,并将它们转换为字符串。这个函数也是C++11新增的,位于<string>
头文件中。std::to_string(123)
将整数123转换为字符串"123"。std::to_string(123.45)
将双精度浮点数123.45转换为字符串"123.450000"。