c++语法基础
#include <iostream> using namespace std; int main(){ cout << "Hello World" << endl; return 0; }
入门及简单的顺序结构
1、变量的定义
变量类型:
类型 | 关键字 | 大小 |
---|---|---|
布尔型 | bool | 1btye |
字符型 | char | 1btye |
整型 | int | 4btye |
浮点型(单精度) | float | 4btye |
双浮点型(双精度) | double | 8btye |
变量必须先定义,才可以使用。不能重名。
int main() { int a, b = 2, c = b; float d = 1.5, f = 1.23e2; bool g = true; char j = 'a'; return 0; }
2、输入输出
int main() { int a, b; cin >> a >> b;//输入 cout << a + b <<' '<< a * b << endl;//输出,endl是回车 //先输出a+b,再输出空格,再输出a*b,最后回车 return 0; }
int main() { int a, b; scanf("%d%d", &a, &b); printf("%d %d", a + b, a * b); printf("a+b=%d\na*b=%d", a + b, a * b); float c; scanf("%f",&c); printf("a+c=%.2f",a+c); return 0; } /* * int:%d * float:%f * double:%lf * char:%c * long long:%lld * bool:用0和1表示 */
scanf会把空格读入,cin不会读入空格。
printf拓展:
-
Float, double 等输出保留若干位小数时用:%.4f, %3lf
-
%8.3f, 表示这个浮点数的最小宽度为 8,保留 3 位小数,当宽度不足时 在前面补空格。
-
%-8.3f,表示最小宽度为 8,保留 3 位小数,当宽度不足时在后面补上空 格
-
%08.3f, 表示最小宽度为 8,保留 3 位小数,当宽度不足时在前面补上 0
3、表达式
int main() { int a = 6 + 3 * 4 / 2 - 2; cout << a << endl; int b = a * 10 + 5 / 2; cout << b << endl; cout << 23 * 56 - 78 / 3 << endl; cout << 5 % 2 << endl;//1 正数取模为正 cout << -5 % 2 << endl;//-1 负数取模为负 return 0; }
4、变量类型转化
int main() { float x = 123.12; int y = (int) x; cout << x << ' ' << y << endl;//向下取整 return 0; }
隐式转换:低精度转为高精度
5、常量的定义
#include <iostream> #include <cstdio> using namespace std; const double eps=1e-6; //定义常量 int main() { …… return 0; }
浮点数的比较
int main() { if (sqrt(3) * sqrt(3) - 3 <= eps) { printf("equal"); } else { printf("no"); } return 0; }
判断结构
1.基本 if-else 语句:
当条件成立时,执行某些语句;否则执行另一些语句。else语句可以省略
int main() { int score; cin >> score; if (score >= 60) { cout << "及格" << endl; } else { cout << "不及格" << endl; } return 0; }
2.if-else if-else语句:
int main() { int score; cin >> score; if (score >= 85) cout << 'A' << endl; else if (score>=60) cout << 'B' << endl; else cout<<'C'<<endl; return 0; }
3.条件表达式
与 && 或 || 非 !
循环
1.while循环
int main() { int i=1,sum=0; while(i<=100){ sum+=i; i++; } printf("%d",sum); return 0; }
求斐波那契数列的第 n 项。f(1)=1, f(2)=1, f(3)=2, f(n)=f(n-1) + f(n-2)。
int main() { int n; cin>>n; int a=1,b=1,i=1; while(i<n){ int c=a+b; a=b; b=c; i++; } cout<<a<<endl; return 0; }
2.do while
int main() { int n=10,i=1,sum=0; do{ sum+=i; i++; }while(i<=10); printf("%d",sum); return 0; }
3.for循环
for(初始化语句;条件语句;表达式)语句;
int main() { int i,sum; for(i=1,sum=0;i<=100;i++){ sum+=i; } printf("%d",sum); return 0; }
4.continue语句
int main() { int i,sum; for(i=1,sum=0;i<=100;i++){ if(i%2==0){ continue; } sum+=i; } printf("%d",sum); return 0; }
5.break语句
int main() { int i,sum; for(i=1,sum=0;i<=100;i++){ if(sum>2000){ break; } sum+=i; } printf("%d",sum); return 0; }
数组
1.一维数组
1.1数组初始化
int main() { int a[10],b[20]; float f[33]; double d[123]; char c[21]; return 0; }
1.2数组初始化
int main() { int a[3]={0,1,2}; int b[]={0,1,2}; int c[5]={0,1,2}; //等价于c[]={0,1,2,0,0},默认值为0 char d[3]={'a','b','c'}; //字符数组初始化 int f[10]={0}; //将数组全部初始化为0的写法 return 0; }
函数内部定义数组有长度限制,如果数组太长运行报错(超出栈长),可以将数组定义在函数外(堆)。
函数内部定义的变量(局部变量)为随机值,函数外面定义的变量(全局变量)都是0。
1.3数组下标引用
int main() { int a[3] = {0, 1, 2}; cout << a[0] << ' ' << a[1] << ' ' << a[2] << endl; a[0] = 5; cout << a[0] << endl; return 0; }
1.4翻转数组
//头文件 #include <algorithm> //使用方法 reverse(a, a+n);//n为数组中的元素个数 reverse(a,a+n);//翻转整个数组 reverse(a,a+k);//翻转前k个元素 reverse(a+k,a+n);//反转后n-k个元素
1.5高精度计算
#include <iostream> #include <cstdio> using namespace std; int main() { int a[10000] = {1}; int n, size = 1; cin >> n; while (n--) { int t =0; for (int i = 0; i < size; i++) { t+=a[i]*2; a[i]=t%10; t/=10; } if(t) a[size++]=1; } for (int i = size - 1; i >= 0; i--) { cout << a[i]; } return 0; }
2.多维数组
1.1数组定义
int main() { int a[3][4]; int arr[10][20][30]={0}; int b[3][4]={ {0,1,2,3}, {4,5,6,7}, {8,9,10,11} }; for(int i=0;i<3;i++){ for(int j=0;j<4;j++){ cout<<b[i][j]<<' '; } } return 0; }
1.2初始化数组
#include <iostream> #include <cstring>//头文件 using namespace std; int main() { int a[10]; memset(a,0,40);//数组的名字,初始化内容(把每一个字节赋值为0或其他),初始化的长度(以字节为单 位,一个int占4个byte) memset(a,-1,sizeof a); for (int i = 0;i<10;i++){ cout<<a[i]<<' '; } return 0; }
1.3复制数组
#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);//目标数组,原数组,长度 return 0; }
字符串
1.字符与整数的联系——ASCII 码
每个常用字符都对应一个-128~127 的数字,二者之间可以相互转化:
int main() { char c = 'a'; cout << (int) c << endl; cout << (char) 97 << endl; return 0; }
常用 ASCII 值:’A’-‘Z’ 是 65~90,’a’-‘z’是 97-122,’0’-‘9’是 48-57。 字符可以参与运算,运算时会将其当做整数:
int main() { int a='B'-'A'; int b='A'*'B'; char c='A'-2; cout<<a<<endl; cout<<b<<endl; cout<<c<<endl; return 0; }
2.字符数组
字符串就是字符数组加上结束符’\0’。 可以使用字符串来初始化字符数组,但此时要注意,每个字符串结尾会暗含一个’\0’字符,因此字符数组的长度至少要比字符串的长度多 1!
int main() { char a1[]={'C','+','+'}; char a2[]={'C','+','+','\0'}; char a3[]="C++"; //a[2],a[3]等价 char a4[6]="abcdef"; //错误:没有空间存放空字符 return 0; }
2.1字符数组输入输出
int main() { char str[100]; cin>>str; //输入时,遇到空格或回车就会停止 char str[100]; scanf("%s",str); //注:不加& cout<<str<<endl; printf("%s\n",str); //输出时,遇到空格或回车不会停止 puts(str); return 0; }
读入一行字符串,包括空格:
int main() { char str[100]; fgets(str,100,stdin); //最大长度为100 //会读入回车(不建议使用) cin.getline(str,100); cout << str << endl; string s; getline(cin,s); //必须是string类型 cout<<s<<endl; return 0; }
从某个位置开始输出:
int main() { char str[] = "abcdefgh"; cout << str << endl; //从a开始输出 cout << str + 1 << endl; //从b开始输出(从下标1开始输出) return 0; }
数组从下标[1]开始:
int main() { char str[100]; scanf("%s",str+1); cin>>str+1; cout<<str<<endl; return 0; }
2.2 字符数组的常用操作
下面几个函数需要引入头文件:
#include <string.h><cstring> (1) strlen(str),求字符串的长度
(2) strcmp(a, b),比较两个字符串的大小,a < b 返回-1,a == b 返回 0,a > b 返回 1。这里的比较方式是字典序!
(3) strcpy(a, b),将字符串 b 复制给从 a 开始的字符数组。
3.3遍历字符数组中的字符:
int main() { char a[100]="hello world"; for(int i=0;i<strlen(a);i++){ //双层循环,每次strlen都要遍历一次 cout<<a[i]<<endl; } for(int i=0,len=strlen(a);i<len;i++){ //优化 cout<<a[i]<<endl; } return 0; }
3.标准库类型string
可变长的字符序列,比字符数组更加好用。需要引入头文件: #include <string>
3.1 定义和初始化
int main() { string s1; //默认初始化,s1是一个空字符串 string s2=s1; //s2是s1的副本 string s3="hello"; //s3是该字符串字面值的副本 string s4(10,'c'); //s4的内容是cccccccccc return 0; }
3.2 string 上的操作
(1)输入输出操作
int main() { string s1, s2; cin >> s1 >> s2; cout << s1 << ' ' << s2 << endl; printf("%s\n",s1.c_str()); return 0; }
注意:不能用scanf输入;不能用 printf 直接输出 string,需要写成:printf(“%s”, s.c_str())
(2)使用 getline 读取一整行:
int main() { string s1, s2; getline(cin, s1); cout << s1 << endl; return 0; }
(3)string 的 empty 和 size 操作(注意 size 是无符号整数,因此 s.size()<= -1 一定成立):
int main() { string s1, s2 = "abc"; cout << s1.empty() << endl; cout << s2.empty() << endl; cout << s2.size() << endl; return 0; }
(4)string 的比较: 支持 > < >= <= == !=等所有比较操作,按字典序进行比较。
(5)两个 string 对象相加:
int main() { string s1="hello ",s2="world\n"; string s3 = s1 + s2; string s4 = s1 + "," + s2 + '\n'; cout<<s3<<endl; return 0; }
当把 string 对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符的两侧的运算对象至少有一个是 string: string s4 = s1 + “, “; // 正确:把一个 string 对象和有一个字面值相加 string s5 = “hello” +”, “; // 错误:两个运算对象都不是 string string s6 = s1 + “, “ + “world”; // 正确,每个加法运算都有一个运算符是 string string s7 = “hello” + “, “ + s2; // 错误:不能把字面值直接相加,运算是从左到右进行的
3.3 处理 string 对象中的字符
可以将 string 对象当成字符数组来处理:
int main() { string s="hello world"; for(int i=0;i<s.size();i++){ cout<<s[i]<<endl; } return 0; }
int main() { string s = "hello world"; for (char c:s) cout << c << endl; for (char &c:s) c = 'a'; //改变原内容需要加& for (auto c:s) cout << c << endl; //auto:编译器自己猜类型 return 0; }
3.4取字符串的某一段
int main() { string s1; getline(cin,s1); cout<<s1.substr(0,5); //参数:(起止位置,长度)(长度省略一直到结尾) return 0; }
3.5字符串流
导入库函数 #include <sstream>
把一个字符串初始化成类似cin的东西,得到我们任意想要的格式
int main() { string s; getline(cin, s); stringstream ssin(s); int a, b; string str; double c; ssin >> a >> str >> b >> c; cout << a << endl << str << endl << b << endl << c << endl; return 0; }
3.5字符串最后一位处理
int main() { string s; cin>>s; cout<<s.back()<<endl; //s.back()返回最后一位字符 s.pop_back(); // s.pop_back()删去最后一位字符 return 0; }
函数
1. 函数基础
一个典型的函数定义包括以下部分:返回类型、函数名字、由 0 个或多个形参组成的列表以及函数体。
1.1 编写函数
//函数声明 int func(int n); //函数定义 int func(int n) { int res = 1; for (int i = 1; i <= n; i++) { res *= i; } return res; } //调用函数 int main() { int t = func(5); cout << t << endl; return 0; }
1.2形参和实参
实参是形参的初始值。第一个实参初始化第一个形参,第二个实参初始化第二个形参,依次类推。形参和实参的类型和个数必须匹配。 fact(“hello”); // 错误:实参类型不正确 fact(); // 错误:实参数量不足 fact(42, 10, 0); // 错误:实参数量过多 fact(3.14); // 正确:该实参能转换成 int 类型,等价于 fact(3); 形参也可以设置默认值,但所有默认值必须是最后几个。当传入的实参个数少于形参个数时,最后没有被传入值的形参会使用默认值。
1.3函数的形参列表
函数的形参列表可以为空,但是不能省略。 void f1() {/ .... /} // 隐式地定义空形参列表 void f2(void) {/ ... /} // 显式地定义空形参列表 形参列表中的形参通常用逗号隔开,其中每个形参都是含有一个声明符的声 明。即使两个形参的类型一样,也必须把两个类型都写出来: int f3(int v1, v2) {/ ... /} // 错误 int f4(int v1, int v2) {/ ... /} // 正确
1.4局部变量、全局变量与静态变量
局部变量只可以在函数内部使用,全局变量可以在所有函数内使用。当局部 变量与全局变量重名时,会优先使用局部变量。
静态变量:(等价于在函数内部开了一个只有该函数能用的全局变量)
int output(){ static int cnt=0; //静态变量:无论调用函数多少次,都使用同一个变量,初始化只在第一次调用执行 cnt++; cout<<cnt<<"times"<<endl; } int main() { output(); output(); output(); output(); return 0; }
2.参数传递
2.1 传值参数
当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响初始值。
int func(int x){ x=5; return x; } int main() { int x=10; int t=func(x); cout<<x<<' '<<t<<endl; //10 5 return 0; }
2.2 传引用参数
当函数的形参为引用类型时,对形参的修改会影响实参的值。使用引用的作用:避免拷贝、让函数返回额外信息。
int func(int &x){ x=5; return x; } int main() { int x=10; int t=func(x); cout<<x<<' '<<t<<endl; //5 5 return 0; }
2.3 数组形参
在函数中对数组中的值的修改,会影响函数外面的数组。
一维数组形参的写法: // 尽管形式不同,但这三个 print 函数是等价的 void print(int *a) {/ ... /} void print(int a[]) {/ ... /} void print(int a[10]) {/ ... /}
void output(int n,int a[3]){ //output(int n,int *a) output(int n,int a[]) for(int i=0;i<n;i++){ cout<<a[i]<<endl; } } int main() { int a[3]={1,2,3}; output(3,a); return 0; }
多维数组形参的写法: // 多维数组中,除了第一维之外,其余维度的大小必须指定 void print(int (*a)[10]) {/ ... /} void print(int a[][10]) {/ ... /}
void output(int n, int m, int a[][3]) { for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cout << a[i][j] << endl; } } } int main() { int a[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, }; output(3, 3, a); return 0; }
3.返回类型和 return 语句
return 语句终止当前正在执行的函数并将控制权返回到调用该函数的地方 。 return 语句有两种形式: return; return expression;
3.1有返回值的函数
只要函数的返回类型不是 void,则该函数内的每条 return 语句必须返回一个值。return 语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换函数的返回类型。
3.2无返回值函数
没有返回值的 return 语句只能用在返回类型是 void 的函数中。返回 void 的函数不要求非得有 return 语句,因为在这类函数的最后一句后面会隐式地执行return。 通常情况下,void 函数如果想在它的中间位置提前退出,可以使用 return 语句。return 的这种用法有点类似于我们用 break 语句退出循环。
void swap(int &v1, int &v2) { // 如果两个值相等,则不需要交换,直接退出 if (v1 == v2) return; // 如果程序执行到了这里,说明还需要继续完成某些功能 int tmp = v2; v2 = v1; v1 = tmp; // 此处无须显示的 return 语句 }
4.函数递归
在一个函数内部,调用函数本身。
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; }
类、结构体、指针、引用
1.类与结构体
1.类
类中的变量和函数被统一称为类的成员变量。
private 后面的内容是私有成员变量,在类的外部不能访问;public 后面的内容是公有成员变量,在类的外部可以访问。
class Person{ private: int age,height; double money; string books[100]; public: string name; void say(){ cout<<"i am "<<name<<endl; } int get_age(){ return age; } void add_money(double x){ money+=x; } }; //类后面要加分号 int main() { Person c; c.name="hutao"; //c.age=18; //错误!age是私有变量 c.add_money(1000); cout<<c.get_age()<<endl; return 0; }
2.结构体
结构体和类的作用是一样的。不同点在于类默认是 private,结构体默认是 public。
struct Person{ private: int age,height; double money; string books[100]; public: string name; void say(){ cout<<"i am "<<name<<endl; } int set_age(int a){ age=a; } int get_age(){ return age; } void add_money(double x){ money+=x; } } person_a,person_b,persons[100]; //定义完直接声明
(习惯上把只有数据的、函数比较少的定义为结构体;函数麻烦的、多的打包为类)
3.构造函数
struct Person{ int age,height; double money; Person(){} //没有参数的构造函数 Person(int _age,int _height,double _money){ //构造函数 age =_age; height=_height; money=_money; } //构造函数也可以写成 Person(int _age,int _height,double _money) : age(_age),height(_height),money(_money){} }; int main() { Person p1(18,185,100.0); Person p2={20,186,200.5}; cout<<p1.age<<' '<<p2.money<<endl; return 0; }
2.指针与引用
指针指向存放变量的值的地址。因此我们可以通过指针来修改变量的值。
int main() { int a = 10; int *p = &a; //这里的*代表变量p是指针类型,可以看作int* p(定义int类型的指针,p为变量名称) *p += 5; cout << *p << endl; //求变量p的值,此处*为操作符 return 0; }
数组名是一种特殊的指针。指针可以做运算:
int main() { int a[5] = {1, 2, 3, 4, 5}; for (int i = 0; i < 5; i++) { cout << *(a + i) << endl; //指针+1的长度加的是一个类型的长度 } return 0; }
int main() { int a[5] = {1, 2, 3, 4, 5}; int *p = &a[0], *q = &a[2]; cout << q - p << endl; //2 return 0; }
引用和指针类似,相当于给变量起了个别名。
int main() { int a=10; int &p=a; //定义一个变量p,与a同一个地址。 p+=5; cout<<p<<' '<<a<<endl; return 0; }
3.链表
struct Node { int val; Node *next; Node(int _val) : val(_val), next(NULL) {} }; int main() { Node node = Node(1); Node *p = &node; //一般习惯定义完放指针里 //以上两步等价于一步 Node *p=new Node(1);//没有new代表定义一个Node变量值是1;有new代表定义了一个Node变量返回值是地址 Node *q = new Node(2); Node *o = new Node(3); p->next = q;//调用类里面成员变量时,如果变量是指针(地址)用’->‘;不是指针用变量名+‘.’ q->next = o; Node *head = p;//说链表的头结点大部分情况下说的是第一个节点的地址,而不是值。 for(Node* i=head;i;i=i->next){ //链表的遍历 cout<<i->val<<endl; } return 0; }
STL
1.vector
#include <vector>
vector 是变长数组,支持随机访问,不支持在任意位置 O(1)插入。为了保证效率,元素的增删一般应该在末尾进行。
int main() { vector<int> a; //相当于一个长度动态变化的 int 数组 vector<int> a({1,2,3}); vector<int> b[233]; //相当于第一维长 233,第二维长度动态变化的 int 数组 struct rec{ int x,y; }; vector<rec> c; //自定义的结构体类型也可以保存在 vector 中 a.size(); //返回 vector 的实际长度(包含的元素个数) a.empty(); //返回一个bool 类型,表明 vector 是否为空。 a.clear(); //把 vector 清空。 return 0; }
迭代器: 迭代器就像 STL 容器的“指针”,可以用星号“*”操作符解除引用。 一个保存 int 的 vector 的迭代器声明方法为:
vector<int>::iterator it = a.begin();
vector 的迭代器是“随机访问迭代器”,可以把 vector 的迭代器与一个整数相加减,其行为和指针的移动类似。可以把 vector 的两个迭代器相减,其结果也和指针相减类似,得到两个迭代器对应下标之间的距离。
begin/end: begin 函 数 返 回 指 向 vector 中 第 一 个 元 素 的 迭 代 器 。 例 如 a 是 一 个 非 空 的vector,则*a.begin()与 a[0]的作用相同。 所有的容器都可以视作一个“前闭后开”的结构,end 函数返回 vector 的尾部,即 第 n 个 元 素 再 往 后 的 “ 边 界 ” *a.end() 与 a[n] 都 是 越 界 访 问 , 其 中n=a.size()。
//下面代码都遍历了 vector<int>a,并输出它的所有元素。 for (int i = 0; i < a.size(); i ++) cout << a[i] << endl; for (vector<int>::iterator it = a.begin(); it != a.end(); it ++) cout <<*it << endl; for (auto it = a.begin(); it != a.end(); it ++) cout <<*it << endl;//用auto替代 for(int x:a) cout<<x<<' ';//范围遍历,与string一样
front/back front 函数返回 vector 的第一个元素,等价于*a.begin() 和 a[0]。 back 函数返回 vector 的最后一个元素,等价于a[a.size() –1]。
cout<<a.front()<<' '<<a[0]<<endl; cout<<a.back()<<' '<<a[a.size()-1]<<endl;
push_back() 和 pop_back() a.push_back(x) 把元素 x 插入到 vector a 的尾部。 a.pop_back() 删除 vector a 的最后一个元素。
2.queue
#include <queue>
头文件 queue 主要包括循环队列 queue 和优先队列 priority_queue 两个容器。
queue<int> q; struct rec{ int a,x,y; }; queue<rec> q;//结构体rec的队列 priority_queue<int> q; // 大根堆 priority_queue<int, vector<int>, greater<int> q; // 小根堆 priority_queue<pair<int, int>>q; //pair二元组 struct rec { int a, b; bool operator<(const rec &t) const { return a < t.a; } }; priority_queue<rec> q;//定义结构体类型的大根堆,结构体 rec 中必须定义小于号;小根堆定义大于号.
循环队列 queue: queue<int> q;//队头插入,队尾弹出 q.push(1);//在对头插入一个元素 q.pop();//弹出队尾元素 q.front();//返回队头 q.back();//返回队尾 优先队列 priority_queue: priority_queue<int> a; a.push(1);//插入一个数 a.top();//取最大值 a.pop();//删除最大值
队列没有clear,如果要清空队列,重新初始化即可
q = queue<int>();
3.stack
#include <stack>
stack<int> stk; stk.push(1); //向栈顶插入 stk.top(); //返回栈顶元素 stk.pop(); //弹出栈顶元素
4.deque
#include <deque>
双端队列 deque 是一个支持在两端高效插入或删除元素的连续线性存储空间。它就像是 vector 和 queue 的结合。与 vector 相比,deque 在头部增删元素仅需要 O(1)的时间;与 queue 相比,deque 像数组一样支持随机访问。
deque<int> a; a.begin(),a.end(); //返回 deque 的头/尾迭代器 a.front(),a.back(); //队头/队尾元素 a.push_back(1); //从队尾入队 a.push_front(1); //从队头入队 a.pop_back(); //从队尾出队 a.pop_front(); //从队头出队 a.clear(); //清空队列
5.set
#include <set>
头文件 set 主要包括 set 和 multiset 两个容器,分别是“有序集合”和“有序多重集合”,即前者的元素不能重复,而后者可以包含若干个相等的元素。set 和 multiset 的内部实现是一棵红黑树,它们支持的函数基本相同。
set<int> a; //元素不能重复 multiset<int> b; //元素可以重复 struct rec { int x, y; bool operator<(const rec &t) const { return x < t.x; } }; set<rec> c;
a.size(); a.empty(); a.clear();
迭代器: set 和 multiset 的迭代器称为“双向访问迭代器”,不支持“随机访问”,支持星号(*)解除引用,仅支持”++”和--“两个与算术相关的操作。 设 it 是一个迭代器,例如 :
set<int>::iterator it = a.begin();
若把 it++,则 it 会指向“下一个”元素。这里的“下一个”元素是指在元素从小到大排序的结果中,排在 it 下一名的元素。同理,若把 it--,则 it 将会指向排在“上一个”的元素。
s.begin();//是指向集合中最小元素的迭代器。 s.end();//是指向集合中最大元素的下一个位置的迭代器。因此--s.end()是指向集合中最大元素的迭代器。
insert/find:
s.insert(x); //把一个元素 x 插入到集合 s 中,时间复杂度为 O(logn)。 s.find(x); //在集合 s 中查找等于 x 的元素,并返回指向该元素的迭代器。若不存在,则返回 s.end()。时间 复杂度为 O(logn)。 if(s.find(x)==s.end())//来判断x是否存在
lower_bound/upper_bound:
这 两 个 函 数 的 用 法 与 find 类 似 , 但 查 找 的 条 件 略 有 不 同 , 时 间 复 杂 度 为O(logn)。
s.lower_bound(x);//查找大于等于 x 的元素中最小的一个,并返回指向该元素的迭代器。 s.upper_bound(x);//查找大于 x 的元素中最小的一个,并返回指向该元素的迭代器。
erase:
s.erase(it);//从 s 中删除迭代器 it 指向的元素,时间复杂度为O(logn) s.erase(x);//从 s 中删除所有等于 x 的元素,时间复杂度为O(k+logn),其中 k 是被删除的元素个数。
count:
s.count(x);//返回集合 s 中等于 x 的元素个数,时间复杂度为 O(k +logn),其中k 为元素 x 的个数。
6.map
#include <map>
map 容器是一个键值对 key-value 的映射,其内部实现是一棵以 key 为关键码的红黑树。Map 的 key 和 value 可以是任意类型,其中 key 必须定义小于号运算符。
map<string,int> a; a["hello"]=2; cout<<a["hello"]<<endl;
map<string,vector<int>> a; a["hello"]=vector<int>({1,2,3,4}); cout<<a["hello"][2]<<endl;
size/empty/clear/begin/end 均与 set 类似。
insert/erase:
map<string,int> a; a.insert({"hi",3}); //插入一个二元组 cout<<a["hi"]<<endl; cout << (a.find("hi") == a.end()) << endl;//a.find(x) 在变量名为 a 的 map 中查找 key 为 x 的二元组。
7.bitset
#include <bitset>
bitset<1000> a; //定义一个长度为1000的0、1串,默认为0 a[0]=1; cout<<a.count();//返回1的个数 a.set(3);//把第三位设成1 a.reset(3);//把第三位设成0
位运算
& 与 0&0=0; 0&1=0; 1&0=0; 1&1=1; | 或 0|0=0; 0|1=1; 1|0=1; 1|1=1; ~ 非 ~0=1; ~1=0; ^ 异或 0^0=0; 1^1=0; 1^0=1; 0^1=1; >> 右移 a>>k(a右移k位) 等价于 a除以2的k次方 << 左移 a<<k(a左移k位) 等价于 a乘以2的k次方
常用操作: (1) 求 x 的第 k 位数字 x >> k & 1 (2) lowbit(x) = x & -x,返回 x 的最后一位 1 等价于a&(~a+1)
常用库函数
#include <algorithm>
1.reverse 翻转
翻转一个 vector: reverse(a.begin(), a.end());
翻转一个数组,元素存放在下标 1~n: reverse(a + 1, a + 1 + n);
int a[]={1,2,3,4,5}; reverse(a,a+5);//取到最后一个元素的下一个
2.unique 去重
前提:相同元素必须挨在一起
返回去重之后的尾迭代器(或指针),仍然为前闭后开,即这个迭代器是去重之后末尾元素的下一个位置。该函数常用于离散化,利用迭代器(或指针)的减法,可计算出去重后的元素个数。
把一个 vector 去重: int m = unique(a.begin(), a.end()) – a.begin(); 把一个数组去重,元素存放在下标 1~n: int m = unique(a + 1, a + 1 + n) – (a +1);
数组去重处理:
vector<int> a({1, 1, 2, 2, 3, 3, 4}); sort(a.begin(), a.end()); // 对向量进行排序 a.erase(unique(a.begin(),a.end()),a.end());//去除重复的元素之后剩下的数组 for (auto x:a) cout << x << ' ';
3.random_shuffle 随机打乱
#include <algorithm> #include <ctime> int main() { vector<int> a({1, 2, 3, 4, 5}); srand(time(0));//设置种子,通常传入时间作为随机 random_shuffle(a.begin(),a.end()); for(auto x:a) cout<<x<<' '; return 0; }
4.sort排序
对两个迭代器(或指针)指定的部分进行快速排序。可以在第三个参数传入定义 大小比较的函数,或者重载“小于号”运算符。
vector<int> a({4, 3, 2, 1, 5}); sort(a.begin(),a.end()); for(auto x:a) cout<<x<<' ';
bool cmp(int a,int b){//a是否应该排在b的前面 return a>b; } int main() { vector<int> a({4, 3, 2, 1, 5}); sort(a.begin(),a.end());//从小到大排 for(auto x:a) cout<<x<<' '; sort(a.begin(),a.end(),greater<>());//从大到小排 for(auto x:a) cout<<x<<' '; sort(a.begin(),a.end(),cmp);//自定义排序 for(auto x:a) cout<<x<<' '; return 0; }
比较结构体:
struct Rec { int x, y; };// bool cmp(Rec a, Rec b) { //a是否应该排在b的前面 return a.x < b.x; } int main() { Rec a[5]; for (int i = 0; i < 5; i++) { a[i].x = -i; a[i].y = i; } for (int i = 0; i < 5; i++) printf("(%d,%d)", a[i].x, a[i].y); cout<<endl; sort(a, a + 5,cmp); for (int i = 0; i < 5; i++) printf("(%d,%d)", a[i].x, a[i].y); return 0; }
5.lower_bound/upper_bound 二分
lower_bound 的第三个参数传入一个元素 x,在两个迭代器(指针)指定的部分上执行二分查找,返回指向第一个大于等于 x 的元素的位置的迭代器(指针)。 upper_bound 的用法和 lower_bound 大致相同,唯一的区别是查找第一个大于x 的元素。当然,两个迭代器(指针)指定的部分应该是提前排好序的。
int a[] = {1, 2, 4, 5, 6}; int *p = lower_bound(a, a + 6, 3); cout << *p << endl; //返回值 int t = lower_bound(a, a + 6, 3)-a; cout << t << endl; //返回下标
vector<int> a{1,2,3,4,5,6}; int t= lower_bound(a.begin(),a.end(),3)-a.begin(); cout<<a[t]<<endl;
6.next_permutation()
next_permutation(start,end),和prev_permutation(start,end)。这两个函数作用是一样的,区别就在于前者求的是当前排列的下一个排列,后一个求的是当前排列的上一个排列。
当当前序列不存在下一个排列时,函数返回false,否则返回true;
vector<vector<int>> res; do{ res.push_back(nums); }while(next_permutation(nums.begin(),nums.end())); return res;