文章转载自行码棋,并稍作修改
务必做到读万卷书,行万里路。
另外C版本一定要对(可能要加编译参数-std=c++11),C11即可,C++17或20更好。
使DEV支持C++20 : https://blog.csdn.net/qq_50285142/article/details/122930647
'\n' vs 'std::endl': link
#vector#介绍vector为可变长数组(动态数组),定义的vector数组可以随时添加数值和删除元素。
注意:在局部区域中(比如局部函数里面)开vector,是在堆空间里面开的。
在局部区域开array是在栈空间开的,而栈空间比较小,如果开了非常长的array就会发生爆栈。
故局部区域不可以开大长度array,但是可以开大长度vector。
头文件1
2
#include
using namespace std;
初始化
一维初始化
1
2
3
vector
vector
vector
指定长度和初始值的初始化
1
2
3
vector
vector
//注意:指定数组长度之后(指定长度后的数组就相当于正常的数组了)
初始化中有多个元素
1
vector
拷贝初始化
1
2
vector
vector
二维初始化
定义第一维固定长度为5,第二维可变化的二维数组
1
2
3
vector
//注意:行不可变(只有5行), 而列可变,可以在指定行添加元素
//第一维固定长度为5,第二维长度可以改变
vector
1
2
v[1].push_back(2);
v[2].push_back(3);
行列均可变
1
2
//初始化二维均可变长数组
vector
应用:可以在v数组里面装多个数组
1
2
3
4
5
vector
vector
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列初始值为0
1
vector
c++17或者c++20支持的形式(不常用),与上面相同的初始化
1
vector a(n + 1, vector(m + 1, 0));
#方法函数知道了如何定义初始化可变数组,下面就需要知道如何添加,删除,修改数据。
c指定为数组名称,含义中会注明算法复杂度。
代码含义c.front()返回第一个数据$O(1)$c.back()返回数组中的最后一个数据 $O(1)$c.pop_back()删除最后一个数据$O(1)$c.push_back(element)在尾部加一个或多个数据push_back((1,2,3)) $O(1)$c.size()返回实际数据个数(unsigned类型)$O(1)$c.clear()清除元素个数$O(N)$,N为元素个数c.resize(n, v)改变数组大小为n,值为v,如果没有默认赋值为0c.insert(it, x)向任意迭代器it插入一个元素x ,$O(N)$例:c.insert(c.begin()+2,-1)将-1插入c[2]的位置c.erase(first,last)删除[first,last)的所有元素,$O(N)$c.begin()/c.rbegin()返回首/逆元素的迭代器(通俗来说就是地址)$O(1)$c.end() /c.rend()返回最后/逆一个元素后一个位置的迭代器(地址)$O(1)$c.empty()判断是否为空,为空返回真,反之返回假 $O(1)$注意: end()返回的是最后一个元素的后一个位置的地址,不是最后一个元素的地址,所有STL容器均是如此
排序
使用sort排序要: sort(c.begin(), c.end());
sort()为STL函数,请参考本文最后面STL函数系列。
对所有元素进行排序,如果要对指定区间进行排序,可以对sort()里面的参数进行加减改动。
1
2
vector
sort(a.begin() + 1, a.end()); // 对[1, n]区间进行从小到大排序
#访问下标法: 和普通数组一样注意:一维数组的下标是从$0$到$v.size()-1$,访问之外的数会出现越界错误
迭代器法: 类似指针一样的访问 ,首先需要声明迭代器变量,和声明指针变量一样,可以根据代码进行理解(附有注释)。代码如下:
1
2
vector
vector
#下标访问直接和普通数组一样进行访问即可。
1
2
3
4
5
6
7
8
//添加元素
for(int i = 0; i < 5; i++)
vi.push_back(i);
//下标访问
for(int i = 0; i < 5; i++)
cout << vi[i] << " ";
cout << "\n";
#迭代器访问类似指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//迭代器访问
vector
//相当于声明了一个迭代器类型的变量it
//通俗来说就是声明了一个指针变量
//方式一:
vector
for(int i = 0; i < 5; i++)
cout << *(it + i) << " ";
cout << "\n";
//方式二:
vector
for(it = vi.begin(); it != vi.end();it ++)
cout << *it << " ";
//vi.end()指向尾元素地址的下一个地址
#智能指针只能遍历完数组,如果要指定的内容进行遍历,需要另选方法。
auto 能够自动识别并获取类型。
1
2
3
4
5
vector
v.push_back(12);
v.push_back(241);
for(auto val : v)
cout << val << " "; // 12 241
vector注意:
vi[i] 和 *(vi.begin() + i) 等价
vector和string的STL容器支持*(it + i)的元素访问,其它容器可能也可以支持这种方式访问,但用的不多,可自行尝试。
#stack#介绍栈为数据结构的一种,是STL中实现的一个先进后出,后进先出的容器。
1
2
3
4
5
6
7
8
9
10
11
//头文件需要添加
#include
using std::stack;
//声明
stack
stack
stack
// 初始化
stack
// 不能进行列表初始化是因为必须用Container初始化,而不是allocator
// [Template parameters](https://en.cppreference.com/w/cpp/container/stack)
#方法函数代码含义s.push(ele)元素ele入栈,增加元素 $O(1)$s.pop()移除栈顶元素 $O(1)$s.top()取得栈顶元素(但不删除)$O(1)$s.empty()检测栈内是否为空,空为真 $O(1)$s.size()返回栈内元素的个数 $O(1)$stack/queue没有clear()#栈遍历#栈遍历栈只能对栈顶元素进行操作,如果想要进行遍历,只能将栈中元素一个个取出来存在数组中
#数组模拟栈进行遍历通过一个数组对栈进行模拟,一个存放下标的变量top模拟指向栈顶的指针。
特点: 比STL的stack速度更快,遍历元素方便
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int s[100]; // 栈 从左至右为栈底到栈顶
int tt = -1; // tt 代表栈顶指针,初始栈内无元素,tt为-1
for(int i = 0; i <= 5; i++) {
//入栈
s[++tt] = i;
}
// 出栈
int top_element = s[tt--];
//入栈操作示意
// 0 1 2 3 4 5
// tt
//出栈后示意
// 0 1 2 3 4
// tt
#queue#介绍队列是一种先进先出的数据结构。
1
2
3
4
//头文件
#include
//定义初始化
queue
#方法函数代码含义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)$#队列模拟使用q[]数组模拟队列
hh表示队首元素的下标,初始值为0
tt表示队尾元素的下标,初始值为-1,表示刚开始队列为空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include
using namespace std;
const int N = 1e5+5;
int q[N];
int main() {
int hh = 0,tt = -1;
// 入队
q[++tt] = 1;
q[++tt] = 2;
// 将所有元素出队
while(hh <= tt) {
int t = q[hh++];
printf("%d ",t);
}
return 0;
}
#priority_queue#介绍优先队列是在正常队列的基础上加了优先级,保证每次的队首元素都是优先级最大的。
可以实现每次从优先队列中取出的元素都是队列中优先级最大的一个。
它的底层是通过堆来实现的。
1
2
3
4
5
6
7
//头文件
#include
using namespace std;
//初始化定义
priority_queue
// 与stack/queue不同的是,需要加入比较函数less
priority_queue
#函数方法代码含义q.top()访问队首元素q.push()入队q.pop()堆顶(队首)元素出队q.size()队列元素个数q.empty()是否为空注意没有clear()!不提供该方法优先队列只能通过top()访问队首元素(优先级最高的元素)#设置优先级#基本数据类型的优先级1
2
priority_queue
priority_queue
参数解释:
第二个参数:
vector< int > 是用来承载底层数据结构堆的容器,若优先队列中存放的是double型数据,就要填vector< double >
总之存的是什么类型的数据,就相应的填写对应类型。同时也要改动第三个参数里面的对应类型。
第三个参数:
less< int > 表示数字大的优先级大,堆顶为最大的数字
greater< int >表示数字小的优先级大,堆顶为最小的数字
int代表的是数据类型,也要填优先队列中存储的数据类型
下面介绍基础数据类型优先级设置的写法。
1. 基础写法(非常常用)
1
2
3
4
priority_queue
priority_queue
priority_queue
2. 自定义排序(不常见,主要是写着麻烦)
下面的代码比较长,基础类型优先级写着太麻烦,用第一种即可。
1
2
3
4
5
6
7
8
9
10
11
12
struct 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
priority_queue
#结构体优先级设置即优先队列中存储结构体类型,必须要设置优先级,即结构体的比较运算(因为优先队列的堆中要比较大小,才能将对应最大或者最小元素移到堆顶)。
优先级设置可以定义在结构体内进行小于号重载,也可以定义在结构体外。
1
2
3
4
5
//要排序的结构体(存储在优先队列里面的)
struct Point
{
int x,y;
};
版本一:自定义全局比较规则 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
版本二:直接在结构体里面写因为是在结构体内部自定义的规则,一旦需要比较结构体,自动调用结构体内部重载运算符规则。
结构体内部有两种方式
方式一
1
2
3
4
5
6
7
struct node {
int x, y;
friend bool operator < (Point a, Point b) {//为两个结构体参数,结构体调用一定要写上friend
return a.x > b.x;//按x从小到大排,x大的在堆顶
}
};
priority_queue
方式二
1
2
3
4
5
6
7
8
struct node {
int x, y;
bool operator < (const Point &a) const {//直接传入一个参数,不写friend,但写const函数
return x < a.x;//按x升序排列,x大的在堆顶
}
};
// priority_queue 默认大根堆
priority_queue
优先队列的定义
1
priority_queue
注意: 优先队列自定义排序规则和sort()函数定义cmp函数很相似,但是最后返回的情况是相反的。即相同的符号,最后定义的排列顺序是完全相反的。
所以只需要记住sort的排序规则和优先队列的排序规则是相反的就可以了。
#存储特殊类型的优先级#存储pair类型排序规则:
默认先对pair的first进行降序排序,然后再对second降序排序
对first先排序,大的排在前面,如果first元素相同,再对second元素排序,保持大的在前面。pair请参考下文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include
using namespace std;
int main() {
priority_queue
// 完整默认写法
// priority_queue
q.push({7, 8});
q.push({7, 9});
q.push(make_pair(8, 7));
for (; !q.empty(); q.pop()) {
auto i = q.top();
cout << i.first << " " << i.second << endl;
}
return 0;
}
结果:
8 7
7 9
7 8
#stack、queue、priority_queue小结它们的构造器都为Container,因此不包含诸如clear()、insert()、erase()函数
#deque#介绍首尾都可插入和删除的队列为双端队列。
1
2
3
4
//添加头文件
#include
//初始化定义
deque
#方法函数代码含义push_back(x)/push_front(x)把x插入队尾后 / 队首 $O(1)$back()/front()返回队尾 / 队首元素 $O(1)$pop_back() / pop_front()删除队尾 / 队首元素 $O(1)$erase(iterator it)删除双端队列中的某一个元素erase(iterator first,iterator last)删除双端队列中[first,last)中的元素empty()判断deque是否空 $O(1)$size()返回deque的元素数量 $O(1)$clear()清空deque#注意点deque可以进行排序
1
2
3
4
5
//从小到大
sort(q.begin(), q.end())
//从大到小排序
sort(q.begin(), q.end(), greater
sort(q.begin(), q.end(), greater());//高版本C++才可以用
#map#介绍映射类似于函数的对应关系,每个x对应一个y,而map是每个键对应一个值。会python的朋友学习后就会知道这和python的字典非常类似。
1
2
3
4
5
6
7
8
//头文件
#include
using std::map;
using std::pair;
//初始化定义
map
map
map
map特性:map会按照键的顺序从小到大自动排序,键的类型必须可以比较大小
#函数方法#函数方法代码含义mp.find(key)返回键为key的映射的迭代器 $O(logN)$注意:find函数返回一个迭代器。当数据存在时,返回数据所在位置的迭代器,数据不存在时,返回$mp.end()$mp.erase(it)删除迭代器对应的键和值$O(1)$mp.erase(key)根据映射的键删除键和值 $O(logN)$mp.erase(first,last)删除左闭右开区间迭代器对应的键和值 $O(last-first)$mp.size()返回映射的对数$O(1)$mp.clear()清空map中的所有元素$O(N)$mp.insert()插入元素,插入时要构造键值对 $O(logN)$mp.empty()如果map为空,返回true,否则返回falsemp.begin()返回指向map第一个元素的迭代器(地址)mp.end()返回指向map尾部的迭代器(最后一个元素的下一个地址)mp.rbegin()返回指向map最后一个元素的迭代器(地址)mp.rend()返回指向map第一个元素前面(上一个)的逆向迭代器(地址)mp.count(key)查看元素是否存在,因为map中键是唯一的,所以存在返回1,不存在返回0mp.lower_bound()返回一个迭代器,指向键值**>= key**的第一个元素mp.upper_bound()返回一个迭代器,指向键值**>** key的第一个元素#注意点下面说明部分函数方法的注意点
注意:
查找元素是否存在时,可以使用
①mp.find() ② mp.count() ③ mp[key]
但是第三种情况,如果不存在对应的key时,会自动创建一个键值对(产生一个额外的键值对空间)
所以为了不增加额外的空间负担,最好使用前两种方法
#迭代器进行正反向遍历mp.begin()和mp.end()用法:用于正向遍历map
1
2
3
4
5
6
7
8
9
map
mp[1] = 2;
mp[2] = 3;
mp[3] = 4;
auto it = mp.begin();
while(it != mp.end()) {
cout << it->first << " " << it->second << "\n";
it ++;
}
结果:
1 2
2 3
3 4
mp.rbegin()和mp.rend()用于逆向遍历map
1
2
3
4
5
6
7
8
9
map
mp[1] = 2;
mp[2] = 3;
mp[3] = 4;
auto it = mp.rbegin();
while(it != mp.rend()) {
cout << it->first << " " << it->second << "\n";
it ++;
}
结果:
3 4
2 3
1 2
#二分查找二分查找lower_bound() upper_bound()
map的二分查找以第一个元素(即键为准),对键进行二分查找
返回值为map迭代器类型
1
2
3
4
5
6
7
8
9
10
11
#include
using namespace std;
int main() {
map
map
cout << it1->first << "\n";//it1->first=2
map
cout << it2->first << "\n";//it2->first=6
return 0;
}
#添加元素1
2
//先声明
map
方式一:1
2
mp["学习"] = "看书";
mp["玩耍"] = "打游戏";
方式二:插入元素构造键值对1
mp.insert(make_pair("vegetable","蔬菜"));
方式三:1
mp.insert(pair
方式四:1
mp.insert({"hahaha","wawawa"});
#访问元素#下标访问(大部分情况用于访问单个元素)
1
2
mp["菜哇菜"] = "强哇强";
cout << mp["菜哇菜"] << "\n";
#遍历访问方式一:迭代器访问1
2
3
4
5
6
7
8
map
for(it = mp.begin(); it != mp.end(); it++) {
// 键 值
// it是结构体指针访问所以要用 -> 访问
cout << it->first << " " << it->second << "\n";
//*it是结构体变量 访问要用 . 访问
//cout<<(*it).first<<" "<<(*it).second;
}
方式二:智能指针访问1
2
for(auto i : mp)
cout << i.first << " " << i.second << endl;//键,值
方式三:对指定单个元素访问1
2
map
cout << it -> first << " " << it->second << "\n";
方式四:c++17特性才具有1
2
3
for(auto [x, y] : mp)
cout << x << " " << y << "\n";
//x,y对应键和值
#与unordered_map的比较这里就不单开一个大目录讲unordered_map了,直接在map里面讲了。
#内部实现原理map:内部用红黑树实现,具有自动排序(按键从小到大)功能。
unordered_map:内部用哈希表实现,内部元素无序杂乱。
#效率比较map:
优点:内部用红黑树实现,内部元素具有有序性,查询删除等操作复杂度为$O(logN)$
缺点:占用空间,红黑树里每个节点需要保存父子节点和红黑性质等信息,空间占用较大。
unordered_map:
优点:内部用哈希表实现,查找速度非常快(适用于大量的查询操作)。缺点:建立哈希表比较耗时。两者方法函数基本一样,差别不大。
注意:
随着内部元素越来越多,两种容器的插入删除查询操作的时间都会逐渐变大,效率逐渐变低。
使用[]查找元素时,如果元素不存在,两种容器都是创建一个空的元素;如果存在,会正常索引对应的值。所以如果查询过多的不存在的元素值,容器内部会创建大量的空的键值对,后续查询创建删除效率会大大降低。
查询容器内部元素的最优方法是:先判断存在与否,再索引对应值(适用于这两种容器)
1
2
3
4
5
// 以 map 为例
map
int x = 999999999;
if(mp.count(x)) // 此处判断是否存在x这个键
cout << mp[x] << "\n"; // 只有存在才会索引对应的值,避免不存在x时多余空元素的创建
另外:
还有一种映射:multimap
键可以重复,即一个键对应多个值,如要了解,可以自行搜索。
#set#介绍set容器中的元素不会重复,当插入集合中已有的元素时,并不会插入进去,而且set容器里的元素自动从小到大排序。
即:set里面的元素不重复 且有序
1
2
3
4
//头文件
#include
//初始化定义
set
#函数方法代码含义s.begin()返回set容器的第一个元素的地址(迭代器)$O(1)$s.end()返回set容器的最后一个元素的下一个地址(迭代器)$O(1)$s.rbegin()返回逆序迭代器,指向容器元素最后一个位置$O(1)$s.rend()返回逆序迭代器,指向容器第一个元素前面的位置$O(1)$s.clear()删除set容器中的所有的元素,返回unsigned int类型$O(N)$s.empty()判断set容器是否为空$O(1)$s.insert()插入一个元素s.size()返回当前set容器中的元素个数$O(1)$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的第一个元素的迭代器$O(logN)$s.upper_bound(k)返回大于k的第一个元素的迭代器$O(logN)$#访问迭代器访问1
2
for(set
cout << *it << " ";
智能指针1
2
for(auto i : s)
cout << i << endl;
访问最后一个元素1
2
//第一种
cout << *s.rbegin() << endl;
1
2
//第二种 rend()也可以
cout << (*s.end()) << endl; // 对end()++/--没有效果
1
2
3
//逆序输出,注意是++it
for (auto it = s.rbegin(); it != s.rend(); ++it)
std::cout << *it << std::endl;
#重载<运算符基础数据类型方式一:改变set排序规则,set中默认使用less比较器,即从小到大排序。(常用)
1
2
set
set
方式二:重载运算符。(很麻烦,不太常用,没必要)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//重载 < 运算符
struct cmp {
bool operator () (const int& u, const int& v) const {
// const参数 / const函数
return u > v;
}
};
set
for(int i = 1; i <= 10; i++)
s.insert(i);
for(auto i : s)
cout << i << " ";
// 10 9 8 7 6 5 4 3 2 1
方式三:初始化时使用匿名函数定义比较规则
1
2
3
4
5
6
7
8
#include
set
return i > j; // 从大到小
});
for(int i = 1; i <= 10; i++)
s.insert(i);
for(auto x : s)
std::cout << x << " ";
高级数据类型(结构体)直接重载结构体运算符即可,让结构体可以比较。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Point {
int x, y;
// friend bool operator < (const Point &a, const Point &b){
// if (a.x == b.x) return a.y < b.y;
// return a.x < b.x;
// }
bool operator < (const Point &p) const {
// 按照点的横坐标从小到大排序,如果横坐标相同,纵坐标从小到大
if(x == p.x)
return y < p.y;
return x < p.x;
}
};
set
for(int i = 1; i <= 5; i++) {
int x, y;
cin >> x >> y; // vscode [控制台输入](https://stackoverflow.com/a/49846389)
s.insert({x, y});
}
for(auto i : s)
cout << i.x << " " << i.y << "\n";
#其它setmultiset:元素可以重复,且元素有序
unordered_set :元素无序且只能出现一次
unordered_multiset : 元素无序可以出现多次
#总结Containers library
容器迭代器类型Container有序?时间复杂度底层备注array随机访问(支持iter+2)sequence随机读改) $O(1)$静态数组array
应用:
代替二元结构体作为map键值对进行插入(代码如下)1
2
3
4
map
mp.insert(pair
// mp.insert(make_pair("xingmaqi", 1));
// mp.insert({"xingmaqi", 1});
1
2
3
4
5
6
7
8
9
10
11
12
//头文件
#include
using std::pair;
//1.初始化定义
pair
pair
//2.赋值
p = {"wang", 18};
p = std::make_pair("wang", 18);
p = pair
#访问1
2
3
4
5
6
//定义结构体数组
pair
for(int i = 0; i < 20; i++) {
//和结构体类似,first代表第一个元素,second代表第二个元素
cout << p[i].first << " " << p[i].second;
}
#string#介绍string是一个字符串类,和char型字符串类似。
可以把string理解为一个字符串类型,像int一样可以定义
#初始化及定义 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//头文件
#include
using std::string;
//1.
string str1; //生成空字符串
//2.
string str2("123456789"); //生成"1234456789"的复制品
//3.
string str3("12345", 0, 3);//结果为"123" ,从0位置开始,长度为3
//4.
string str4("123456", 5); //结果为"12345" ,长度为5
//5.
string str5(5, '2'); //结果为"22222" ,构造5个字符'2'连接而成的字符串
//6.
string str6(str2, 2); //结果为"3456789",截取第三个元素(2对应第三位)到最后
简单使用
访问单个字符:1
2
3
4
5
6
7
8
9
#include
#include
using namespace std;
int main() {
string s = "xing ma qi!!!";
for(int i = 0; i < s.size(); i++)
cout << s[i] << " ";
return 0;
}
string数组使用: 1
2
3
4
5
6
7
8
9
10
11
#include
#include
using namespace std;
int main() {
string s[10];
for(int i = 1; i < 10; i++) {
s[i] = "loading... " ;
cout << s[i] << i << "\n";
}
return 0;
}
#string 特性支持比较运算符
string字符串支持常见的比较操作符(>,>=,<,<=,==,!=),支持string与C-string的比较(如 str < "hello")。
在使用>,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按 字典顺序 进行逐一比较。字典排序靠前的字符小, 比较的顺序是从前向后比较,遇到不相等的字符就按这个位置上的两个字符的比较结果确定两个字符串的大小(前面减后面)。
同时,string ("aa") < string("aaa")。
支持+运算符,代表拼接字符串
string字符串可以拼接,通过"+"运算符进行拼接。
1
2
3
4
string s1 = "123";
string s2 = "456";
string s = s1 + s2;
cout << s; //123456
#读入详解读入字符串,遇空格,回车结束
1
2
string s;
cin >> s;
读入一行字符串(包括空格),遇回车结束
1
2
string s;
getline(cin, s);
注意: getline(cin, s)会获取前一个输入的换行符,需要在前面添加读取换行符的语句。如:getchar() 或 cin.get()
错误读取:
1
2
3
4
int n;
string s;
cin >> n;
getline(cin, s); //此时读取相当于读取了前一个回车字符
正确读取:
1
2
3
4
5
int n;
string s;
cin >> n;
getchar(); //cin.get()
getline(cin, s);//可正确读入下一行的输入
cin与cin.getline()混用
cin输入完后,回车,cin遇到回车结束输入,但回车还在输入流中,cin并不会清除,导致getline()读取回车,结束。
需要在cin后面加cin.ignore();主动删除输入流中的换行符。(不常用)
cin和cout解锁
代码(写在main函数开头):
1
2
3
4
5
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
}
为什么要进行cin和cout的解锁,原因是:
在一些题目中,读入的数据量很大,往往超过了1e5(10^5)的数据量,而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
2
string s = "xing ma qi";
char s2[] = s.c_str(); // string to c's str
#函数方法获取字符串长度代码含义s.size()和s.length()返回string对象的字符个数,他们执行效果相同。s.max_size()返回string对象最多包含的字符数,超出会抛出length_error异常s.capacity()重新分配内存之前,string对象能包含的最大字符数插入代码含义s.push_back()在末尾插入例:s.push_back('a')末尾插入一个字符as.insert(iter_pos,element)在pos位置插入element,pos为迭代器例:s.insert(s.begin(),'1')在第一个位置插入1字符。以上方法只能插入一个字符s.insert(size_t(3), "c");s.append(str)在s字符串结尾添加str字符串例:s.append("abc")在s字符串末尾添加字符串“abc”删除代码含义erase(iterator p)删除字符串中p所指的字符erase(iterator first, iterator last)删除字符串中迭代器区间[first,last)上所有字符erase(int_pos, len)删除字符串中从索引位置pos开始的len个字符,只有字符串可以用整形索引来删除clear()删除字符串中所有字符字符替换代码含义s.replace(pos,n,str)把当前字符串从索引pos开始的n个字符替换为strs.replace(pos,n,n1,c)把当前字符串从索引pos开始的n个字符替换为n1个字符cs.replace(it1,it2,str)把当前字符串[it1,it2)区间替换为str it1 ,it2为迭代器哦大小写转换法一:
代码含义tolower(s[i])转换为小写toupper(s[i])转换为大写法二:
通过stl的transform算法配合tolower 和toupper 实现。
有4个参数,前2个指定要转换的容器的起止范围,第3个参数是结果存放容器的起始位置,第4个参数是一元运算。
1
2
3
4
5
#include
std::string s("abc");
std::transform(s.cbegin(), s.cend(),
s.begin(), [](unsigned char c)
{ return std::toupper(c); }); //转换大写
分割代码含义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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 使用用法推荐:https://en.cppreference.com/w/cpp/string/basic_string/find_first_of
#include
#include
int main() {
string s("dog bird chicken bird cat");
//字符串查找-----找到后返回首字母在字符串中的下标
// 1. 查找一个字符串
cout << s.find("chicken") << endl;// 结果是:9
// 2. 从下标为6开始找字符'i',返回找到的第一个i的下标
cout << s.find('i',6) << endl;// 结果是:11
// 3. 从字符串的末尾开始查找字符串,返回的还是首字母在字符串中的下标
cout << s.rfind("chicken") << endl;// 结果是:9
// 4. 从字符串的末尾开始查找字符
cout << s.rfind('i') << endl;// 结果是:18因为是从末尾开始查找,所以返回第一次找到的字符
// 5. 在该字符串中查找第一个属于字符串s的字符
cout << s.find_first_of("13rb98") << endl;// 结果是:4---b
// 6. 在该字符串中查找第一个不属于字符串s的字符------先匹配dog,然后bird匹配不到,所以打印4
cout << s.find_first_not_of("hello dog 2006") << endl; // 结果是:4
cout << s.find_first_not_of("dog bird 2006") << endl; // 结果是:9
// 7. 在该字符串最后中查找第一个属于字符串s的字符
cout << s.find_last_of("13r98") << endl;// 结果是:19
// 8. 在该字符串最后中查找第一个不属于字符串s的字符------先匹配t--a---c,然后空格匹配不到,所以打印21
cout << s.find_last_not_of("teac") << endl;// 结果是:21
}
排序1
sort(s.begin(),s.end()); //按ASCII码排序
#bitset#介绍bitset 在 bitset 头文件中,它类似数组,并且每一个元素只能是0或1,每个元素只用1bit空间
1
2
//头文件
#include
#初始化定义初始化方法
代码含义bitset < n >aa有n位,每位都为0bitset < n >a(b)a是unsigned long型u的一个二进制副本bitset < n >a(s)a是string对象s中含有的位串的副本bitset < n >a(s,pos,n)a是s中从位置pos开始的n个位的副本注意:n必须为常量表达式
演示代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include
using namespace std;
int main() {
bitset<4> bitset1; //无参构造,长度为4,默认每一位为0
bitset<9> bitset2(12); //长度为9,二进制保存,前面用0补充
string s = "100101";
bitset<10> bitset3(s); //长度为10,前面用0补充
char s2[] = "10101";
bitset<13> bitset4(s2); //长度为13,前面用0补充
cout << bitset1 << endl; //0000
cout << bitset2 << endl; //000001100
cout << bitset3 << endl; //0000100101
cout << bitset4 << endl; //0000000010101
return 0;
}
#10.3 特性bitset可以进行位操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
bitset<4> foo (string("1001"));
bitset<4> bar (string("0011"));
cout << (foo^=bar) << endl;// 1010 (foo对bar按位异或后赋值给foo)
cout << (foo&=bar) << endl;// 0001 (按位与后赋值给foo)
cout << (foo|=bar) << endl;// 1011 (按位或后赋值给foo)
cout << (foo<<=2) << endl;// 0100 (左移2位,低位补0,有自身赋值)
cout << (foo>>=1) << endl;// 0100 (右移1位,高位补0,有自身赋值)
cout << (~bar) << endl;// 1100 (按位取反)
cout << (bar<<1) << endl;// 0110 (左移,不赋值)
cout << (bar>>1) << endl;// 0001 (右移,不赋值)
cout << (foo==bar) << endl;// false (1001==0011为false)
cout << (foo!=bar) << endl;// true (1001!=0011为true)
cout << (foo&bar) << endl;// 0001 (按位与,不赋值)
cout << (foo|bar) << endl;// 1011 (按位或,不赋值)
cout << (foo^bar) << endl;// 1010 (按位异或,不赋值)
访问
1
2
3
4
5
6
//可以通过 [ ] 访问元素(类似数组),注意最低位下标为0,如下:
bitset<4> foo ("1011");
cout << foo[0] << endl; //1
cout << foo[1] << endl; //0
cout << foo[2] << endl; //1
#10.4 方法函数代码含义b.any()b中是否存在值为1的二进制位,有 返回trueb.none()b中是否没有1,没有 返回trueb.count()b中为1的个数b.size()b中二进制位的个数b.test(pos)测试b在pos位置是否为1,是 返回trueb[pos]返回b在pos处的二进制位b.set()把b中所有位都置为1b.set(pos)把b中pos位置置为1b.reset()把b中所有位都置为0b.reset(pos)把b中pos位置置为0b.flip()把b中所有二进制位取反b.flip(pos)把b中pos位置取反b.to_ulong()用b中同样的二进制位返回一个unsigned long值#array#介绍头文件
1
#include
array是C++11新增的容器,效率与普通数据相差无几,比vector效率要高,自身添加了一些成员函数。
和其它容器不同,array 容器的大小是固定的,无法动态的扩展或收缩,只允许访问或者替换存储的元素。
注意:
array的使用要在std命名空间里
#声明与初始化基础数据类型
声明一个大小为100的int型数组,元素的值不确定
1
array
声明一个大小为100的int型数组,初始值均为0(初始值与默认元素类型等效)
1
array
声明一个大小为100的int型数组,初始化部分值,其余全部为0
1
array
或者可以用等号
1
array
高级数据类型
不同于数组的是,对元素类型不做要求,可以套结构体
1
2
array
array
#存取元素修改元素1
2
array
a[0] = 4;
访问元素下标访问
1
2
3
array
for(int i = 0; i < 4; i++)
cout << a[i] << " \n"[i == 3];
利用auto访问
1
2
for(auto i : a)
cout << i << " ";
迭代器访问
1
2
3
auto it = a.begin();
for(; it != a.end(); it++)
cout << *it << " ";
at()函数访问
下标为1的元素加上下标为2的元素,答案为5
1
2
3
array
int res = a.at(1) + a.at(2);
cout << res << "\n";
get方法访问
将a数组下标为1位置处的值改为x
注意:获取的下标只能写数字,不能填变量
1
2
int x = 10;
get<1>(a) = x;
#成员函数成员函数功能begin()返回容器中第一个元素的访问迭代器(地址)end()返回容器最后一个元素之后一个位置的访问迭代器(地址)rbegin()返回最后一个元素的访问迭代器(地址)rend()返回第一个元素之前一个位置的访问迭代器(地址)size()返回容器中元素的数量,其值等于初始化 array 类的第二个模板参数Nmax_size()返回容器可容纳元素的最大数量,其值始终等于初始化 array 类的第二个模板参数 Nempty()判断容器是否为空at(n)返回容器中 n 位置处元素的引用,函数会自动检查 n 是否在有效的范围内,如果不是则抛出 out_of_range 异常front()返回容器中第一个元素的直接引用,函数不适用于空的 array 容器back()返回容器中最后一个元素的直接引用,函数不适用于空的 array 容器。data()返回一个指向容器首个元素的指针。利用该指针,可实现复制容器中所有元素等类似功能fill(x)将 x 这个值赋值给容器中的每个元素,相当于初始化array1.swap(array2)交换 array1 和 array2 容器中的所有元素,但前提是它们具有相同的长度和类型#部分用法示例data()
指向底层元素存储的指针。对于非空容器,返回的指针与首元素地址比较相等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void pointer_func(const int* p, std::size_t size)
{
std::cout << "data = ";
for (std::size_t i = 0; i < size; ++i)
std::cout << p[i] << ' ';
std::cout << '\n';
}
int main()
{
std::array
// Prefer container.data() over &container[0]
pointer_func(container.data(), container.size());
}
fill()
array的fill()函数,将a数组全部元素值变为x
1
a.fill(x);
另外还有其它的fill()函数:将a数组$[begin,end)$全部值变为x
1
fill(a.begin(), a.end(), x);
排序
1
sort(a.begin(), a.end());
#tuple#介绍tuple模板是pair的泛化,可以封装不同类型任意数量的对象。
可以把tuple理解为pair的扩展,tuple可以声明二元组,也可以声明三元组。
tuple可以等价为结构体使用
头文件
1
#include
#声明初始化声明一个空的tuple三元组
1
tuple
赋值
1
t1 = make_tuple(1, 1, "hahaha");
创建的同时初始化
1
tuple
可以使用pair对象构造tuple对象,但tuple对象必须是两个元素
1
2
auto p = make_pair("wang", 1);
tuple
#元素操作通过get
1
int first = get<0>(t);
修改tuple对象t的第一个元素
1
get<0>(t) = 1;
#函数操作获取元素个数1
2
tuple
cout << tuple_size
通过tie解包 获取元素值tie可以让tuple变量中的三个值依次赋到tie中的三个变量中
1
2
3
4
5
int one, three;
string two;
tuple
tie(one, two, three) = t;
cout << one << two << three << "\n"; // 1hahaha3
#STL函数#accumulateaccumulate(beg, end, init)
复杂度: $O(N)$
作用:对一个序列的元素求和
init为对序列元素求和的初始值
返回值类型:与init
基础累加求和:1
2
3
4
5
6
7
int a[]={1,3,5,9,10};
//对[0,2]区间求和,初始值为0,结果为0+1+3+5=9
int res1 = accumulate(a, a + 3, 0);
//对[0,3]区间求和,初始值为5,结果为5+1+3+5+9=23
int res2 = accumulate(a, a + 4, 5);
自定义二元对象求和:使用lambda表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef long long ll;
struct node {
ll num;
}st[10];
for(int i = 1; i <= n; i++)
st[i].num = i + 10000000000;
//返回值类型与init一致,同时注意参数类型(a)也要一样
//初始值为1,累加1+10000000001+10000000002+10000000003=30000000007
ll res = accumulate(st + 1, st + 4, 1ll, [](ll a,node b) {
return a + b.num;
});
#atoiatoi(const char *)
将字符串转换为int类型
注意参数为char型数组,如果需要将string类型转换为int类型,可以使用stoi函数(参考下文),或者将string类型转换为const char *类型。
关于输出数字的范围:
atoi不做范围检查,如果超出上界,输出上界,超出下界,输出下界。
stoi会做范围检查,默认必须在int范围内,如果超出范围,会出现RE(Runtime Error)错误。
1
2
3
string s = "1234";
int a = atoi(s.c_str());
cout << a << "\n"; // 1234
或者
1
2
3
char s[] = "1234";
int a = atoi(s);
cout << a << "\n";
#fillfill(beg,end,num)
复杂度: $O(N)$
对一个序列进行初始化赋值
1
2
3
4
5
6
//对a数组的所有元素赋1
int a[5];
fill(a,a+5,1);
for(int i=0;i<5;i++)