C++语法
前言
虽然C++是⼀⻔⾯向对象语⾔,但是对于刷算法这件事⽽⾔,我们并不需要掌握它⾯向对象的部分~只需要掌握刷算法的时候需要⽤到的部分(基本输⼊输出、STL标准模板库、 string 字符串等)就可以啦~C语⾔和C++有很多相似之处,且C++向下兼容C语⾔
第1讲:变量、输入输出、表达式与顺序语句
常用头文件
iostream
里面包括cin
、cout
、scanf
、printf
cstdio
包括scanf
、printf
cmath
多用于数学
C++头文件
bits/stdc++.h(万能·头文件)
#include <cmath> // 相当于C语⾔⾥⾯的#include <math.h>
#include <cstdio> // 相当于C语⾔⾥⾯的#include <stdio.h>
#include <cctype> // 相当于C语⾔⾥⾯的#include <ctype.h>
#include <cstring> // 相当于C语⾔⾥⾯的#include <string.h>
C++特有的bool变量
bool
变量有两个值,false
和true
,以前⽤C语⾔的时候都是⽤ int 的0
和1
表示false
和true
- 现在C++⾥⾯引⼊了这个叫做
bool
(布尔)的变量,⽽且C++把所有⾮零值
解释为true
,零值
解
释为false
- 直接赋值⼀个数字给 bool 变量也是可以的,它会
⾃动根据 int 值是不是零来决定
给 bool 变量赋值true
还是false
数据类型
bool
int
2Blong
4Blong long
8Bfloat
6~7位小数,4Bdouble
15~16位小数,8Blong double
15~16位小数,8B
字符的读入
scanf("%c%c", &a, &b); // 会把空格读入
cin >> a >> b; // 会忽略中间的空格(1个或多个)
OJ系统经常的输出格式问题
忽略每一行末尾的空格
忽略输出结果最后的换行符
C++输入输出模板
#include <iostream>
using namespace std;
int main()
{
int a;
cin >> a;
cout << a;
return 0;
}
就如同
scanf
和printf
在stdio.h
头⽂件中⼀样,cin
和cout
在头⽂件iostream
⾥⾯,看名字就知
道,io
是输⼊输出input
和output
的⾸字⺟,stream
是流,所以这个iostream
头⽂件⾥包含的⽅
法就是管理⼀些输⼊输出流的~
同样, cout << n
; 和 printf("%d", n)
;; ⼀样的意思.
变量的强制转换
#include <cstdio>
#include <iostream>
using namespace std;
int main()
{
int a = 5;
int t = 97;
float b = (float)a;//强制int转换成float
char c = (char)t;//强制int转换成char
cout << a <<endl;
cout << c <<endl;
printf("%.1f",b);
return 0;
a
与 b
与 c
输出结果如下
5
a
5.0
第2讲:判断,循环
判断浮点数是否为0:
!x
判断语句
//输入一个数的绝对值
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int x;
cin >> x;
if(x>=0)
{
cout<< x <<endl;
}
else
{
cout<< -x <<endl;
}
return 0;
}
swap
函数常用于判断时候进行交换:
在新c++编译器中,iostream
含有swap
函数,可交换基础类型,如swap(int x, int y)
,而不必swap(int &x, int &y)
旧一点的版本需要导入#include <algorithm>
库
#include <algorithm>
里有swap(x, y)函数
#include <iostream>
#include <algorithm>
int main()
{
int x = 1;
int y = 2;
swap(x, y);
cout << x << ":" << y << endl;
return 0;
}
循环语句
//1加到100的循环
#include <iostream>
using namespace std;
int main()
{
int i = 1;
int sum = 0;
while (i<=100)
{
sum += i;
i++;
}
cout<<sum<<endl;//5050
return 0;
}
第3讲:数组
数组的定义
- 数组的定义方式和变量类似
//数组的定义方式如下
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int a[10],b[20];
float f[15];
double d[25];
char c[23];
return 0;
}
数组的初始化
- 在main函数内部,未初始化的数组中的元素是
随机的
。
//初始化如下
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int a[3] = {0, 1, 2};//定义了一个长度是3的数组
int b[] = {0, 1, 3};//也可以这样表示
int c[5] = {0, 1, 2};//定义了一个长度是5的数组,没有赋值的默认是0;等价于c[5] = {0, 1, 2, 0, 0 }
char d[3] = {'a', 'b', 'c'};//字符数组的初始化
int f[10] = {0};//将数组全部初始化的常用写法
return 0;
}
通过下标访问数组
#include <iostream>
using namespace std;
int main()
{
int a[3] = {0, 1, 2};//数组下标一定从0开始
cout << a[0] << ' '<< a[1] <<' '<<a[2]<<endl;
return 0;
}
数组复制
- 一个数组复制到另外一个数组
memcpy
(目标数组,原来数组,长度)memcpy(dest,src,sizeof(src))
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
int a[10],b[10];
for(int i = 0; i < 10; i++) a[i] = i;
memcpy(b, a, sizeof a);
for(int i = 0; i < 10; i++ ) cout<<b[i]<<endl;
return 0;
}
多维数组
- 多维数组就是数组的数组。
int a[3][4]; // 大小为3的数组,每个元素是含有4个整数的数组。
int arr[2][3][4] = {0}; // 将所有元素初始化为
0
2层的3行4列
的数组。(汤老师对不起)
第4讲:字符串
字符串与整数联系——ASCII码
- 每个常用字符都对应一个-128~127的数字,
- 二者之间可以相互转化
#include <iostream>
usingnamespace std;
int main()
{
char c = 'a';
cout<< (int)c <<endl;//97
int a = 66;
cout<< (char)a <<endl;//66
return 0;
}
常用ASCII值:
'A'
-'Z'
是65
~90
,
'a'
-'z'
是97
-122
,
'0'
-'9'
是48
-57
。
字符数组输入输出
#include <iostream>
using namespace std;
int main()
{
char str[100];
cin>>str;//输入字符串时,遇到空格或者回车就会停止
cout<< str <<endl;//输出字符串时,遇到空格或者回车不会停止
//printf("%s\n",str);
return 0;
}
gets()
函数:读入一行字符串,包括空格puts()
函数:用来向屏幕输出字符串并换行
字符数组的常用操作
下面几个函数需要引入头文件:
#include <string.h>
-
strlen(str)
,就是string length
的缩写,求字符串长度 -
strcmp(a,b)
,就是string compare
的缩写比较两个字符串的大小,a < b
返回-1
,a == b
返回0
,a > b
返回1
。这里的比较方式是字典序! -
strcpy(a,b)
,就是string copy
的缩写,字符b
复制从a
开始的字符数组。
//strcpy&&strcmp&&strlen
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
char a[100] = "hello world",b[100];
cout<< strlen(a) <<endl;
strcpy(b,a);
cout<< strcmp(a,b) <<endl;
return 0;
}
- 若要遍历字符数组中的字符
#include <iosream>
#include <string.h>
using namespace std;
int main()
{
char a[100] = "hello world!";
for(int i= 0; i < strlen(a); i++)
cout<< a[i] <<endl;
return 0;
}
标准库函数类型string
-
以前⽤
char[]
的⽅式处理字符串很繁琐,现在有了 string 类,定义、拼接、输出、处理都更加简单啦
不过 string 只能⽤cin
和cout
处理,⽤scanf
和printf
处理比较麻烦:用printf输出string,需要写成:printf(“%s”, s.c_str())
; -
可变长的字符序列,比字符数组更加好用。需要引入头文件:
#include <string>
定义和初始化
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1;//默认初始化,s1是一个空字符
string s2 = s1;//s2是s1的副本
string s3 = "csdn";//字符串字面意思
string s4(5,'a');//aaaaa
return 0;
}
string 上的操作
(1)string的读写:
- C语言读入方法
char s[N];
scanf("%s", s); // 不能读取含空格、换行符的字符串
gets(s); // 能读取含空格的字符串,同时自动去掉换行符\n
fgets(s, N, stdin); // 能读取含空格的字符串,但不会去掉换行符\n。【注意
- C++方法
#include <string>
string str;
cin >> str; // 不能读取含空格、换行符的字符串
getline(cin, str); // 能读取含空格的字符串,同时自动去掉换行符\n
return 0;
⽤
cin
读⼊字符串的时候,是以空格为分隔符的,如果想要读⼊⼀整⾏的字符串,就需要⽤getline
string s; // 定义⼀个空字符串s
getline(cin, s); // 读取⼀⾏的字符串,包括空格
cout << s.length(); // 输出字符串s的⻓度
(2)字符串操作:
- C语言方法
#include <cstring> // 或<string.h>
char a[N], b[N];
strlen(a); // O(N)复杂度,使用前最好用变量保存字符串长度。统计的长度不包括`\0`
strcat(a, b); // 把字符串b拼接到a之后,拼接后的字符串保存在a中
strcmp(a, b); // 根据字典排序比较字符串
strcpy(b, a); // 把字符串a的内容拷贝到字符串b
for (int i = 0; str[i]; i++) {...} // 遍历字符串
- C++方法
string str;
string s(5, 'a'); // 构造重复字符的字符串
str.empty(); // 判空
str.size(); // 长度,与stelen()不同的是,这个复杂度是O(1),不用额外的变量保存
str.c_str(); // 转成char数组,此时才可用printf输出
str.substr(begin, length); // 子串
str.pop_back(); // 删除最后一个字符
// 字符串比较">"、"<"
// 字符串拼接"+"
for (char ch : str) {...} // 遍历(不可修改字符)
for (char &ch : str) {...} // 遍历(可修改字符)
注意:
使用+对字符串拼接时,要求左右两边至少有一个string对象,即str = "a" + "b"
;会报错。
(3)字符串流:
#include <sstream>
using namespace std;
string s;
stringstream ssin(s);
while(ssin >> s) {...} // 按空格拆分s,例如英语句子拆分单词
// 可用如下代码代替
while(cin >> word) {
...
}
char容易记错的地方
char a[] = {'C', '+', '+'};
char b[4] = {'D', '+', '+', '\0'};
char c[5] = {'E', '+', '+', '\0'}; // 最后一个位置会补\0
cout << a << endl; // 输出"C++D++",因为字符数组a不会自动添加'\0',cout会读取到b的部分
第5讲 C++中的函数
- 一个典型的函数定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的列表以及函数体。
(1)编写函数
#include <iostream>
using namespace std;
int csdn(int n)//函数定义接口
{
int res = 1;
for(int i = 0; i <=n; i++)
{
res*=i;
return res;
}
}
int main()
{
int t = csdn(5);//传入参数
cout<< t <<endl;
return 0;
}
- 特殊的返回类型:
void
- 静态变量
static
变量不会随着递归改变,相当于在函数内部开了一个只有该函数能用的全局变量 - 局部变量只可以在函数内部使用,全局变量可以在所有函数内使用。当局部变量与全局变量重名时,会优先使用局部变量。
(2)函数中的数组参数:
int size(int a[]) {
return sizeof a;
}
int main() {
int a[10];
cout << sizeof a << endl; // 4B * 10 = 40B
cout << size(a) << endl; // 8B,虽然函数f能修改实参a的内容,但其本质是一个不同的数组指针指向数组的内存空间,故对函数内的数组参数a调用sizeof,返回的是数组指针的长度。在64位系统中,指针的长度等于64b=8B
}
内联
inline void f() {...} // 编译时把函数体复制到调用函数的位置,减少函数跳转次数
fgets
遇到的坑
fgets
会读入\n
,因此遍历字符串时,应当用for (int i = 0; str[i] != '\n'; i++)
,而不能用
for (int i = 0; str[i]; i++)
函数递归
- 在一个函数内部,也可以调用函数本身
#include <iostream>
using namespace std;
int fact (int n)
{
if(n <= 1) return 1;
return n * fact(n -1);
}
int main()
{
int n;
cin >> n;
cout<<fact(n)<<endl;
return 0;
}
第6讲 结构体、类、指针与引用
(1)结构体
- 类可以将
变量
、数组
和函数
完美地打包在一起。
// 定义
struct Node {
int val;
Node* next;
// 构造器
Node(int _val): val(_val), next(NULL) {}// 编译更快
Node(int _val) {
val = _val;
}
};
// 使用
Node* p = new Node(1);
C++的结构体struct和C语⾔的结构体的区别
- 定义好结构体
stu
之后,使⽤这个结构体类型的时候,C语⾔需要写关键字struct
,⽽C++⾥⾯可以省
略不写:
struct stu {
int grade;
float score;
};
struct stu arr1[10]; // C语⾔⾥⾯需要写成struct stu
stu arr2[10];// C++⾥⾯不⽤写struct,直接写stu就好了
(2)类
class Node {
private:
...
public:
...
}; // 注意类末尾要加分号!
类与结构体的区别
- 结构体默认是
public
- 类默认是
private
- private后面的内容是
私有成员变量
,在类的外部不能访问;public后面的内容是公有成员变量
,在类的外部可以访问。
Leetcode式模板
class Solution {
public:
int f() {...}
};
(3)指针和引用
指针
- 指针指向存放变量的值的地址。可以通过指针来修改变量的值。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int*p = &a;
*p +=5;
cout<< *p <<endl;
return 0;
}
- 数组是一种特殊的指针,指针可以做运算:
#include <iostream>
using namespace std;
int main()
{
int a[5] = {1,2,3,4,5};
for(int i = 0;i < 5;i++)
cout<< *(a+i) <<endl;
return 0;
}
引用
- 引用和指针类似,相当于给变量起了个别名。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int &p = a;
p += 5;
cout<< p <<endl;
return 0;
}
C++的引⽤&和传值的区别
这个引⽤符号
&
要和C语⾔⾥⾯的取地址运算符&
区分开来,他们没有什么关系,C++⾥⾯的引⽤是
指在变量名之前加⼀个&
符号,⽐如在函数传⼊的参数中 int &a ,那么对这个引⽤变量 a 做的所有操作都是直接对传⼊的原变量进⾏的操作,并没有像原来 int a ⼀样只是拷⻉⼀个副本(传值),举
两个例⼦:
(1)
void func(int &a)
{
// 传⼊的是n的引⽤,相当于直接对n进⾏了操作,只不过在func函数中换了个名字叫a
a = 99;
}
int main()
{
int n = 0;
func(n); // n由0变成了99
}
(2)
void func(int a)
{// 传⼊的是0这个值,并不会改变main函数中n的值
a = 99;
}
int main()
{
int n = 0;
func(n);// 并不会改变n的值,n还是0
}
第7讲 STL容器、位运算与常用库函数
(1)STL容器
vector
、stack
、queue
、map
、set
这些在C++中都叫做容器,这些容器的⼤⼩都可以⽤name.size()
获取到,就像string s
的⻓度⽤s.length()
获取⼀样
其实
string
⾥⾯的size
和length
两者是没有区别、可以互换使⽤的
数组类容器
vector
-
C语⾔⾥⾯⽤
int arr[]
定义数组,它的缺点是数组的⻓度不能随⼼所欲的改变,⽽C++⾥⾯有⼀个能完全替代数组的动态数组vector
-
能够在运⾏阶段设置数组的⻓度、在末尾增加新的数据、在中间插⼊新的值、⻓度任意被改变
-
它在头⽂件
vector
⾥⾯,也在命名空间std
⾥⾯,所以使⽤的时候要引⼊头⽂件#include <vector>
和using namespace std
;
#include <vector>
// 定义
vector<int> a; // 一维数组
vector<int> b[N]; // 二维数组
vector<int> c(10); // 直接定义⻓度为10的int数组,默认这10个元素值都为0
vector<int> v1;
v1.resize(8); //先定义⼀个vector变量v1,然后将⻓度resize为8,默认这8个元素都是0
vector<int> v3(100, 9);// 把100⻓度的数组中所有的值都初始化为9
// 初始化
vector<int> a({1, 2, 3});
// 操作
a[k]; // 取值
a.size(); // 长度
a.empty(); // 判空
a.clear(); // 清空
a.front(); // 读取第1个元素
a.back(); // 读取最后1个元素
a.push_back(x); // 在末尾插入元素
int x = a.pop_back(); // 删除末尾元素并返回
int* p = lower_bound(a, a + a.size(), x); // 查找数组在指定范围内大于等于x的元素地址(要求数组有序)
int* p = upper_bound(a, a + a.size(), x); // 查找数组在指定范围内大于x的元素地址(要求数组有序)
int index = lower_bound(a, a + a.size(), x); - a; // 查找数组在指定范围内大于等于x的元素下标(要求数组有序)
int index = upper_bound(a, a + a.size(), x); - a; // 查找数组在指定范围内大于x的元素下标(要求数组有序)
// 遍历
for (int i = 0; i < a.size(); i++) {...} // 方式1,通过a[i]读取元素值
for (vector<int>::iterator i = a.begin(); i < a.end(); i++) {...} // 方式2(迭代器),通过*i读取元素值
for (auto i = a.begin(); i < a.end(); i++) {...} // 方式3(迭代器简化版)
for (int x : a) {...} // 方式4,通过x读取元素值
说明
v.begin()
返回的是vector第1个元素的地址,而v.end()
返回的是最后一个元素的下一个位置的地址
如图
容器
vector
、set
、map
这些遍历的时候都是使⽤迭代器访问的,v.begin()
是⼀个指针,指向容器
的第⼀个元素,v.end()
指向容器的最后⼀个元素的后⼀个位置,所以迭代器指针 it 的for循环判断条
件是it != c.end()
-
访问元素的值要对
it
指针取值,要在前⾯加星号,所以是cout << *it
;
这⾥的auto
相当于vector<int>::iterator
的简写 -
auto
:
auto
是C++11⾥⾯的新特性,可以让编译器根据初始值类型直接推断变量的类型。⽐如这样:
auto x = 100;// x是int变量
auto y = 1.5;// y是double 变量
当然这个在算法⾥⾯最主要的⽤处不是这个,⽽是在STL中使⽤迭代器的时候,
auto
可以代替⼀⼤⻓
串的迭代器类型声明:
// 本来set的迭代器遍历要这样写:
for(set<int>::iterator it = s.begin(); it != s.end(); it++)
{
cout << *it << " ";
}
// 现在可以直接替换成这样的写法:
for(auto it = s.begin(); it != s.end(); it++)
{
cout << *it << " ";
}
a.end() - a.begin() == a.size()
*a.begin() == a[0]
queue
:
#include <queue>
/**************************************************
** 普通队列queue
***************************************************/
// 定义
queue<int> q;//定义⼀个空队列q
// 操作
q.push(x); // 入队(末尾插入元素)
int x = q.pop(); // 出队(删除第1个元素)
a.front(); // 查看队头元素
a.back(); // 查看队尾元素
// a.clear()
cout << q.size() << endl; // 输出队列的元素个数
/**************************************************
** 优先队列(堆)
***************************************************/
// 元素为基本类型
priority_queue<int> a; // 大根堆
priority_queue<int, vector<int>, greater<int>> b; // 小根堆
// 元素为自定义类型
struct Rec {
int a, b;
// 大根堆需要自定义类重载<号
bool operator< (const Rec& t) const {
return a < t.a;
}
// 小根堆需要自定义类重载>号
bool operator> (const Rec& t) const {
return a > t.a;
}
}
priority_queue<Rec> a; // 大根堆
priority_queue<Rec, vector<Rec>, greater<Rec>> b; // 小根堆
// 操作
a.push(x); // 插入元素(位置不确定)
a.top(); // 查看堆顶元素(大根堆是最大值,小根堆是最小值)
a.pop(); // 删除堆顶元素(大根堆是最大值,小根堆是最小值)
注意
- 队列没有
clear()
方法 - 优先队列插入时无序,输出时有序
- 大根堆重载
<
- 大根堆重载
>
- 大根堆重载
stack
:
#include <stack>//头⽂件
// 定义
stack<int> s;
// 操作
s.push(x); // 入栈
s.top(); // 查看栈顶
s.pop(); // 出栈(不放回出栈元素!)
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int> s; // 定义⼀个空栈s
for (int i = 0; i < 6; i++)
{
s.push(i); // 将元素i压⼊栈s中
}
cout << s.top() << endl; // 访问s的栈顶元素
cout << s.size() << endl; // 输出s的元素个数
s.pop(); // 移除栈顶元素
return 0;
}
deque
#include <deque>
// 定义
deque<int> q;
// 操作
q[i] // 随机访问
q.begin(); // 队头元素地址,用*q.begin()读取元素
q.end(); // 队尾元素地址,用*q.end()读取元素
q.front(); // 队头元素值
q.back(); // 队尾元素值
push_back(); // 队尾插入元素
push_front(); // 队头插入元素
pop_back(); // 队尾删除元素
pop_front(); // 队头
set
set
是集合,⼀个set
⾥⾯的各元素是各不相同的,⽽且set
会按照元素进⾏从⼩到⼤排序~以下
是set
的常⽤⽤法:
#include <set>
// 定义
set<int> s; // 集合
multiset<int> ms; // 多重集合(允许元素重复)
// 操作
s.size();
s.empty();
s.clear();
s.begin();
s.end();
s.insert(x);
s.find(x); // 返回迭代器,可用if(s.find(x) == s.end())判断是否存在元素x
s.lower_bound(x); // 返回大于等于x的最小元素的迭代器
s.upper_bound(x); // 返回大于x的最小元素的迭代器
s.erase(x); // 删除x并返回迭代器
s.count(x); // 统计x出现的次数(普通集合只会返回0或1,多重集合可能返回大于1的数)
实例
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s; // 定义⼀个空集合s
s.insert(1); // 向集合s⾥⾯插⼊⼀个1
cout << *(s.begin()) << endl; // 输出集合s的第⼀个元素 (前⾯的星号表示要对指针取值)
for (int i = 0; i < 6; i++) {
s.insert(i); // 向集合s⾥⾯插⼊i
}
for (auto it = s.begin(); it != s.end(); it++) { // ⽤迭代器遍历集合s⾥⾯的每⼀
个元素
cout << *it << " ";
}
cout << endl << (s.find(2) != s.end()) << endl; // 查找集合s中的值,如果结果等于
s.end()表示未找到 (因为s.end()表示s的最后⼀个元素的下⼀个元素所在的位置)
cout << (s.find(10) != s.end()) << endl; // s.find(10) != s.end()表示能找到10
这个元素
s.erase(1); // 删除集合s中的1这个元素
cout << (s.find(1) != s.end()) << endl; // 这时候元素1就应该找不到啦~
return 0; }
说明:
- 自定义类要求重载
<
find()
、erase()
、lower_bound()
和upper_bound()
都是O(logn)
复杂度count()
是O(k+logn)
复杂度
有序对容器
map
map
是键值对,⽐如⼀个⼈名对应⼀个学号,就可以定义⼀个字符串string
类型的⼈名为“键”,学
号int
类型为“值”,如map<string, int> m
;- 当然键、值也可以是其它变量类型~
map
会⾃动将所有的键值对按照键从⼩到⼤排序, map 使⽤时的头⽂件#include <map>
以下是 map 中常⽤的⽅法:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
map<string, int> m; // 定义⼀个空的map m,键是string类型的,值是int类型的
m["hello"] = 2; // 将key为"hello", value为2的键值对(key-value)存⼊map中
cout << m["hello"] << endl; // 访问map中key为"hello"的value, 如果key不存在,则返回0
cout << m["world"] << endl;
m["world"] = 3; // 将"world"键对应的值修改为3
m[","] = 1; // 设⽴⼀组键值对,键为"," 值为1
// ⽤迭代器遍历,输出map中所有的元素,键⽤it->first获取,值⽤it->second获取
for (auto it = m.begin(); it != m.end(); it++) {
cout << it->first << " " << it->second << endl;
}
// 访问map的第⼀个元素,输出它的键和值
cout << m.begin()->first << " " << m.begin()->second << endl;
// 访问map的最后⼀个元素,输出它的键和值
cout << m.rbegin()->first << " " << m.rbegin()->second << endl;
// 输出map的元素个数
cout << m.size() << endl;
return 0;
}
STL之unordered_map
和unordered_set
的使⽤
unordered_map
在头⽂件#include <unordered_map>
中,unordered_set
在头⽂件#include <unordered_set>
- unordered_map 和 map (或者 unordered_set 和 set )的区别是, map 会按照键值对的键 key 进⾏排序( set ⾥⾯会按照集合中的元素⼤⼩进⾏排序,从⼩到⼤顺序),⽽ unordered_map (或者 unordered_set )省去了这个排序的过程,如果偶尔刷题时候⽤ map 或者 set 超时了,可以考虑⽤ unordered_map (或者 unordered_set )缩短代码运⾏时间、提⾼代码效率~⾄于⽤法和map 、 set 是⼀样的~
位运算和库函数
bitset
bitset
⽤来处理⼆进制位⾮常⽅便。头⽂件是#include <bitset>
, bitset 可能在PAT、蓝桥OJ中不常
⽤,但是在LeetCode OJ中经常⽤到~⽽且知道 bitset 能够简化⼀些操作,可能⼀些复杂的问题能够直接⽤ bitset 就很轻易地解决~以下是⼀些常⽤⽤法
#include <iostream>
#include <bitset>
using namespace std;
int main()
// 定义二进制串
bitset s;
bitset<5> b("11"); //5表示5个⼆进位
初始化⽅式:
bitset<5> b; 都为0
bitset<5> b(u); u为unsigned int,如果u = 1,则输出b的结果为00001
bitset<8> b(s); s为字符串,如"1101",则输出b的结果为00001101,在前⾯补0
bitset<5> b(s, pos, n); 从字符串的s[pos]开始,n位⻓度
//注意,bitset相当于⼀个数组,但是它是从⼆进制的低位到⾼位分别为b[0]、b[1]……的
//所以按照b[i]⽅式逐位输出和直接输出b结果是相反的
cout << b << endl; // 如果bitset<5> b("11"); 则此处输出00011(即正常⼆进制顺序)
for(int i = 0; i < 5; i++)
cout << b[i]; // 如果bitset<5> b("11"); 则此处输出11000(即正常⼆进制顺序的倒序)
cout << endl << b.any(); //b中是否存在1的⼆进制位
cout << endl << b.none(); //b中不存在1吗?
cout << endl << b.count(); //b中1的⼆进制位的个数
cout << endl << b.size(); //b中⼆进制位的个数
cout << endl << b.test(2); //测试下标为2处是否⼆进制位为1
b.set(4); //把b的下标为4处置1
b.reset(); //所有位归零
b.reset(3); //b的下标3处归零
b.flip(); //b的所有⼆进制位逐位取反
unsigned long a = b.to_ulong(); //b转换为unsigned long类型
return 0;
}
说明
bitset
元素支持位运算符&
、|
和~
等等- 求
x
的第k
位二进制数:x >> k & 1
- 求
x
从右起的第1
个1
:lowbit(x) = x & -x;
- 实际上是原码和补码做与操作
- 例如
110110
返回10
,11000
返回1000
algorithm库
#include <algorithm>
vector<int> a;
// 翻转
reverse(a.begin(), a.end());
reverse(a, a + a.size());
// 去重
unique(a, a + a.size()); // 返回去重后最后一个元素的地址
int m = unique(a, a + a.size()) - a; // 去重后数组的长度
a.erase(unique(a.begin(), a.end()), a.end()); // 真删除重复元素
// 打乱
random_shuffle(a.begin(), a.end());
// 排序
sort(a.begin(), a.end()); // 升序
sort(a.begin(), a.end(), greater<int>()); // 降序
bool cmp(int a, int b) {return a - b;} // 自定义比较方法
sort(a.begin(), a.end(), cmp); // 自定义排序
unique
并没有真的删除重复元素,它仅将重复的元素放到非重复元素部分的后边
sort库函数
sort
函数在头⽂件#include <algorithm>
⾥⾯,主要是对⼀个数组进⾏排序(int arr[]
数组或
者vector
数组都⾏),vector
是容器,要⽤v.begin()
和v.end()
表示头尾;⽽int arr[]
⽤arr
表示数组的⾸地址,arr+n
表示尾部
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool cmp(int a, int b)
{ // cmp函数返回的值是bool类型
return a > b; // 从⼤到⼩排列
}
int main() {
vector<int> v(10);
for (int i = 0; i < 10; i++)
{
cin >> v[i];
}
sort(v.begin(), v.end());// 因为这⾥没有传⼊参数cmp,所以按照默认,v从⼩到⼤排列
int arr[10];
for (int i = 0; i < 10; i++)
{
cin >> arr[i];
}
sort(arr, arr + 10, cmp); // arr从⼤到⼩排列,因为cmp函数排序规则设置了从⼤到⼩
return 0;
}
使⽤sort⾃定义cmp函数
sort
默认是从⼩到⼤排列的,也可以指定第三个参数cmp
函数,然后⾃⼰定义⼀个 cmp 函数指定
排序规则~ cmp 最好⽤的还是在结构体中,尤其是很多排序的题⽬~⽐如⼀个学⽣结构体 stu 有学
号和成绩两个变量,要求如果成绩不同就按照成绩从⼤到⼩排列,如果成绩相同就按照学号从⼩到⼤
排列,那么就可以写⼀个cmp
数组实现这个看上去有点复杂的排序过程:
#include <iostream>
using namespace std;
struct stu
{ // 定义⼀个结构体stu,number表示学号,score表示分数
int number;
int score;
}
bool cmp(stu a, stu b) { // cmp函数,返回值是bool,传⼊的参数类型应该是结构体stu类型
if (a.score != b.score) // 如果学⽣分数不同,就按照分数从⼤到⼩排列
return a.score > b.score;
else // 如果学⽣分数相同,就按照学号从⼩到⼤排列
return a.number < b.number; }
// 有时候这种简单的if-else语句我喜欢直接⽤⼀个C语⾔⾥⾯的三⽬运算符表示~
bool cmp(stu a, stu b)
{
return a.score != b.score ? a.score > b.score : a.number < b.number;
}
注意
sort
函数的cmp
必须按照规定来写,即必须只是>
或者<
,⽐如:return a > b
; 或 者return a <
b; ⽽不能是<=
或者>=
,因为快速排序的思想中,cmp
函数是当结果为false
的时候迭代器指针暂停开始交换两个元素的位置,当cmp
函数return a <= b
时,若中间元素前⾯的元素都⽐它⼩,⽽后⾯的元素都跟它相等或者⽐它⼩,那么cmp
恒返回true
,迭代器指针会不断右移导致程序越界,发⽣段错误~
random_shuffle 随机打乱
用法与reverse相同
cctype头⽂件⾥的⼀些函数
#include <cctype>
本质上来源于C语⾔标准函数库中的头⽂件#include<ctype.h>
,其实并不属C++新特性的范畴,在刷PAT⼀些字符串逻辑题的时候也经常⽤到
可能平时我们判断⼀个字符是否是字⺟,可能会写
char c;
cin >> c;
if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
cout << "c is alpha"; }
但是在 cctype
中已经定义好了判断这些字符应该所属的范围,直接引⼊这个头⽂件并且使⽤⾥⾯的函数判断即可,⽆需⾃⼰⼿写
#include <iostream>
#include <cctype>
using namespace std;
int main()
{
char c;
cin >> c;
if (isalpha(c))
{
cout << "c is alpha";
}
return 0;
}
不仅仅能判断字⺟,还能判断数字、⼩写字⺟、⼤写字⺟等~C++官⽅⽂档中对这些函数归纳成了⼀个表格,
isalpha
、islower
、isupper
、isalnum
、isblank
、isspace
函数
- 总的来说常⽤的只有以下⼏个:
isalpha
字⺟(包括⼤写、⼩写)islower
(⼩写字⺟)isupper
(⼤写字⺟)isalnum
(字⺟⼤写⼩写+数字)isblank
(space和 \t )isspace
( space 、 \t 、 \r 、 \n )
cctype
中除了上⾯所说的⽤来判断某个字符是否是某种类型,还有两个经常⽤到的函数:tolower
和toupper
,作⽤是将某个字符转为⼩写或者⼤写,这样就不⽤像原来那样⼿动判断字符c是否是⼤写,如果是⼤写字符就c = c + 32
; 的⽅法将char c
转为⼩写字符啦~这在字符串处理的题⽬中也是经常⽤到:
char c = 'A';
char t = tolower(c); // 将c字符转化为⼩写字符赋值给t,如果c本身就是⼩写字符也没有关系~
cout << t; // 此处t为'a'
C++11的一些特性
C++11是2011年官⽅为C++语⾔带来的新语法新标准,C++11为C++语⾔带来了很多好⽤的新特性,⽐
如auto
、to_string() 函数
、stoi
、stof
、unordered_map
、unordered_set
之类的~现在⼤多数OJ都是⽀持C++11语法的,有些编译器在使⽤的时候需要进⾏⼀些设置才能使⽤C++11中的语法,否则可能会导致编译器上编译不通过⽆法运⾏
C++11特性中的to_string
to_string
的头⽂件是#include <string>
,to_string
最常⽤的就是把⼀个int
型变量或者⼀个数字转化为string
类型的变量,当然也可以转double
、float
等类型的变量,这在很多PAT字符串处理的题⽬中很有⽤处,以下是示例代码:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1 = to_string(123); // 将123这个数字转成字符串
cout << s1 << endl;
string s2 = to_string(4.5); // 将4.5这个数字转成字符串
cout << s2 << endl;
cout << s1 + s2 << endl; // 将s1和s2两个字符串拼接起来并输出
printf("%s\n", (s1 + s2).c_str()); // 如果想⽤printf输出string,得加⼀个.c_str()
return 0;
}
C++11特性中的stoi
、stod
使⽤
stoi
、stod
可以将字符串string
转化为对应的int
型、double
型变量,这在字符串处理的很多问题中很有帮助~以下是示例代码和⾮法输⼊的处理⽅法:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str = "123";
int a = stoi(str);
cout << a;
str = "123.44";
double b = stod(str);
cout << b;
return 0;
}
stoi如果遇到的是⾮法输⼊(⽐如stoi(“123.4”),123.4不是⼀个int型变量):
- 1.会⾃动截取最前⾯的数字,直到遇到不是数字为⽌(所以说如果是浮点型,会截取前⾯的整数部分)
- 2.如果最前⾯不是数字,会运⾏时发⽣错误
stod如果是⾮法输⼊:
- 1.会⾃动截取最前⾯的浮点数,直到遇到不满⾜浮点数为⽌
- 2.如果最前⾯不是数字或者⼩数点,会运⾏时发⽣错误
- 3.如果最前⾯是⼩数点,会⾃动转化后在前⾯补0
不仅有stoi、stod两种,相应的还有:
stof
(string to float)stold
(string to long double)stol
(string to long)stoll
(string to long long)stoul
(string to unsigned long)stoull
(string to unsigned long long)
暂时完结撒花
分割线
想到的东西再加了,就这样hhh
bye~
2021-09-17