C++语法

前言

虽然C++是⼀⻔⾯向对象语⾔,但是对于刷算法这件事⽽⾔,我们并不需要掌握它⾯向对象的部分~只需要掌握刷算法的时候需要⽤到的部分(基本输⼊输出、STL标准模板库、 string 字符串等)就可以啦~C语⾔和C++有很多相似之处,且C++向下兼容C语⾔

第1讲:变量、输入输出、表达式与顺序语句

常用头文件

  • iostream 里面包括cincoutscanfprintf
  • cstdio 包括 scanfprintf
  • 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 变量有两个值, falsetrue ,以前⽤C语⾔的时候都是⽤ int 的 01 表示 falsetrue
  • 现在C++⾥⾯引⼊了这个叫做 bool (布尔)的变量,⽽且C++把所有⾮零值解释为 true零值
    释为 false
  • 直接赋值⼀个数字给 bool 变量也是可以的,它会⾃动根据 int 值是不是零来决定
    给 bool 变量赋值 true 还是 false

数据类型

  • bool
  • int 2B
  • long 4B
  • long long 8B
  • float 6~7位小数,4B
  • double 15~16位小数,8B
  • long 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;
}

就如同 scanfprintfstdio.h 头⽂件中⼀样, cincout 在头⽂件 iostream ⾥⾯,看名字就知
道, io 是输⼊输出 inputoutput 的⾸字⺟, 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;

abc输出结果如下

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返回-1a == b 返回0a > 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!"forint i= 0; i  < strlen(a); i++)
	cout<< a[i] <<endl;
	return 0;
}

标准库函数类型string

  • 以前⽤ char[] 的⽅式处理字符串很繁琐,现在有了 string 类,定义、拼接、输出、处理都更加简单啦
    不过 string 只能⽤ cincout 处理,⽤ scanfprintf 处理比较麻烦:用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 = 1for(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容器

vectorstackqueuemapset 这些在C++中都叫做容器,这些容器的⼤⼩都可以⽤ name.size()获取到,就像 string s 的⻓度⽤ s.length() 获取⼀样

其实 string ⾥⾯的 sizelength 两者是没有区别、可以互换使⽤

数组类容器

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()返回的是最后一个元素的下一个位置的地址

如图

在这里插入图片描述

容器 vectorsetmap 这些遍历的时候都是使⽤迭代器访问的, 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_mapunordered_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从右起的第11lowbit(x) = x & -x;
    • 实际上是原码和补码做与操作
    • 例如110110返回1011000返回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++官⽅⽂档中对这些函数归纳成了⼀个表格,isalphaislowerisupperisalnumisblankisspace函数

  • 总的来说常⽤的只有以下⼏个:
    • isalpha 字⺟(包括⼤写、⼩写)
    • islower(⼩写字⺟)
    • isupper(⼤写字⺟)
    • isalnum(字⺟⼤写⼩写+数字)
    • isblank(space和 \t )
    • isspace ( space 、 \t 、 \r 、 \n )

cctype 中除了上⾯所说的⽤来判断某个字符是否是某种类型,还有两个经常⽤到的函数:tolowertoupper ,作⽤是将某个字符转为⼩写或者⼤写,这样就不⽤像原来那样⼿动判断字符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++语⾔带来了很多好⽤的新特性,⽐
autoto_string() 函数stoistofunordered_mapunordered_set 之类的~现在⼤多数OJ都是⽀持C++11语法的,有些编译器在使⽤的时候需要进⾏⼀些设置才能使⽤C++11中的语法,否则可能会导致编译器上编译不通过⽆法运⾏

C++11特性中的to_string

to_string 的头⽂件是#include <string>to_string最常⽤的就是把⼀个 int 型变量或者⼀个数字转化为 string 类型的变量,当然也可以转 doublefloat 等类型的变量,这在很多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特性中的stoistod

使⽤ stoistod可以将字符串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

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值