目录
2.3数组名也是一种特殊的指针,被称为数组指针,可以做运算。
2.1 reverse翻转:reverse(起始位置,末尾位置的下一个)
2.2 unique 去重:unique(起始位置,末尾位置的下一个)
2.3 random_shuffle()随机打乱数组顺序:random_shuffle(起始位置,末尾位置的下一个)
2.4 sort 排序:sort(起始位置,末尾位置的下一个)
2.5 .lower_bound()/.upper_bound() 二分:.lower_bound(.begin(),.end(),x:比较的值)
一、变量、输入输出、表达式和顺序语句
1、C++语法基本框架
#include <iostream> //头文件,cin >>读入,cout << 输出 和 << endl #include <cstdio> //头文件,里面有函数 printf输出、scanf输入 using namespace std; //使用std的命名空间,没有的话cin、cout函数都在std命名空间中,如果没有这句话就会出现cout未定义的error int main() { //int main() {} 可以看作是函数的入口 cout << "Hello, World!" << endl; //写逻辑的地方 return 0; //必须是return 0; }
2、变量
变量的定义:让程序可以记录信息的一个东西
常用变量类型及其范围
变量类型 | 关键字 | 描述 | 大小/byte |
布尔型 | bool | true/false(可以直接赋值给bool变量,它会自动根据int值是不是零来决定给bool赋值成true/false) | 1 |
字符型 | char | 单引号引起来'a' '\n' | 1 |
整型 | int | -2^31~2^31-1,int是特殊的float | 4 |
浮点型 | float | 可以用科学计数法表示,1.234e5;精度低,单精度浮点数6-7位有效数字;float表示后面要加F | 4 |
双浮点型 | double | 15-16位有效数字 | 8 |
长整型 | long int | -2^63~2^63-1;long型表示后面要加L | 8 |
64位长整型 | long long | ||
长双浮点型 | long double | 18-19位有效数字 | 16 |
字节byte
文件大小:18.6KB
B:byte
b:bit
1byte = 8 bit
3、输入和输出
cin和cout输入输出语句(头文件iostream)
输入语句:cin >> a >> b; 输出一个变量的语句:cout << a+b << endl; 输出多个变量的语句:cout << a+b << ' ' << a-b << endl; 格式化输出:cout << "a+b =" << a+b; 输出多个变量时,在每个变量之间加一个引号空格。多个变量也可分为多个语句输出
scanf和printf输入输出语句(头文件cstdio)
输入语句:scanf("%d%d", &a , &b); %d%d是指读入整数类型 , &是取地址符号 输出语句:printf("%d %d\n", a +b, a-b); %f是浮点数,输出x位小数就%.xf就可以了 格式化输出:printf("a+b = %d\na-b = %d\n", a+b, a-b); int:%d char:%c bool:%d float:%f double:%lf long long:%lld
cin/cout和scanf/printf的区别
可以用cin/cout的地方必然可以用scanf/printf 但是可以用scanf/printf的地方不一定可以用cin/cout scanf/printf效率比cin/cout高 scanf和cin区别:scanf会读入空格,cin不会读入空格
scanf只能读入一个字符串,后面不需要加取地址符号&。但是不能读入一个整数数组,整数数组只能一个变量一个变量的读,int a[0]就不是一个地址了就是一个变量了,每一个变量都要加一个取地址符号。
4、表达式
整数的加减乘除四则运算
整除的除法是整除,得到的都是整数 整数的取余%,余数的正负取决于被除数的正负
整数的自增和自减
a++和++a都是a=a+1 b=a++是先把a的值赋给b,a再加1 b=++a是先把a+1,再把a的值赋给b b+=a;b=b+a; b-=a;b=b-a; b=a;b=ba; b/=a;b/a=a;
变量的强制转换
float_double:损失精度 float/double_int:下取整 int_char:ASCII码表对照 隐式类型转换:几种不同类型的变量做运算时,会默认把精度低的转换成精度高的 C++里^不表示乘方的意思,乘方拆开写
5、顺序语句
二、判断结构
1、printf输出语句
注意:使用printf 时要添加头文件 #include <cstdio>。
printf保留小数很方便
printf整数型格式化输出:
printf("%5d\n",a)//向左补空格至占5位
printf("%-5d\n",a)//向右补空格至占5位 ???printf("%4d ",a)//4d后面要加空格????
printf("%05d\n",a)//向左补0至占5位
printf浮点型格式化输出: printf("%5.5f\n",a)//向左补空格至占5位 小数点前的数字表示补位数
2、if语句
if(条件语句){}else{}:else部分可省略 当{}里只有一句语句时,{}可省略。
if语句后面不要加;!!!
大于 a > b 小于 a < b 大于等于 a >= b 小于等于 a <= b 等于 a == b 不等于 a ! = b
每嵌套一层缩进一格 变量作用域:{}里面可以用括号外面的,但是括号外面的不能用括号里面的 单引号是一个字符,双引号是一个字串。C++不以空格和tab作为运行格式标准,以{}为
3、条件表达式
1、与 &&/and 短路原则,前面的条件不满足的话后面的条件不再判断 2、或||/or 3、非 !/not
优先级:!> && > ||
C++里只能同时比较两个数,不能同时比较三个数。如果是if(a>b>c)的话,含义是先比较a和b,如果a>b成立,则表示为布尔值1,1再和c进行比较
当判断一个数不等于0的时候,可以把!=0省略
如果想要输出特殊符号的话,要在前面加上转义字符\。例如\\,\',\n 百分比号的转义是%%
不能写a == b == c要写a == b && b ==c
三、循环结构
学些编程语言语法都是次要的,重要的是如何把头脑中的想法变成简洁的代码的思想
学习循环只要抓住一点就是代码的执行顺序!!!
while dowhile for 循环可以实现的功能都是完全等价的,
1、while循环
1.1 while循环
while循环就可以简单理解为是循环版的if语句。if语句是只判断一次,条件成立就执行后面的语句;while是每次判断,条件成立就执行后面的语句,否则就停止 =while循环语句中最后一定要记得加i++,否则就会一直循环下去,成为一个死循环==
1.2 do while 循环
do while 循环不常用
do while循环和while循环非常相似,区别只在于第一次循环,do while循环是先上车后买票,至少会执行一次
当while和 do while的条件都成立时,while循环和do while循环的结果完全一致。当while和do while的条件不成立时,结果会不一致,因为do while会执行一次,而while一次都不会执行
do { s += i; i ++; }while(i < 10); cout << s << endl; return 0;
!!!注意while语句之后的分号
2、for循环
# inlcude <iostream> using namespce std; int main(){ for(i = 1, i < 10, i ++){ cout << i << endl; } return 0; }
for循环执行顺序,for(1,2,4){3},且1只执行一次
for (init语句 : 条件语句: 表达式) { statement }
init语句可以是声明语句、表达式、空语句,一般用来初始化循环变量,可以定义多个变量,用逗号隔开;
condition 是条件表达式,和while中的条件表达式作用一样;可以为空,当条件语句为空时,就表示为true,但是while循环的条件语句不能为空;
表达式一般负责修改循环变量,可以为空
for循环,init语句,条件语句和表达式都可以去掉,但是分号不能省
一定不要在for()后面加分号,分号表示空语句,加分号表示循环空语句
3、跳转语句
break语句
break语句可以提前从循环中推出,一般和if搭配使用
continue语句
continue语句是直接跳到当前循环体的结尾。作用与if语句类似
4、多层循环
输出一个n行n列的矩阵,矩阵从1开始按行排列
外循环表示多少行,内循环表示每行每个数的位置,而k表示每个数的大小,因为外循环定义k在内外循环都有意义,所有k变量在外循环定义。
5、循环结构note
1、while(条件语句),while条件语句可以为多个语句,用逗号隔开,从左到右依次执行,但是逻辑表达式的值为最后一个数的值。用逗号隔开的表达式被称为逗号表达式,逗号表达式的值等于最后一个数的值。 只在逻辑表达式中成立。一般的表达式是没有值的,只有在表达式放在条件的位置上,也就是if的括号里,while的括号里以及for括号里的第二个位置的时候。
四、数组
程序 = 逻辑 + 数据 , 数组就是存储数据的强有力的手段。
1、一维数组
1.1数组的定义:就是数据类型 +变量名称[数组长度]
char数组其实就是字符串。
1.2数组的初始化
在main函数内部,未初始化的数组中的元素是随机的。
第一种初始化方式:
第二种初始化方式:
全局变量和局部变量:定义在main函数内部的变量会开在栈里,栈空间默认一兆,数组太大会报错。但是定义在main函数外部的变量会放在堆空间里。函数内部的变量不初始化都是随机的,但是定义在函数外部的变量不初始化都是0.
1.3访问数组元素
通过下标访问数组。
数组的下标一定是从0 开始
比较两个数大小的时候,看两个数差的绝对值是否小于一个足够小的数。
1.4数组的存储
一般情况下因为在进行加减乘的时候,需要进位,所以一般将数倒过来存进数组,即从个位开始存。a[0]-个位 a[1]-十位 a[2]-百位...以此类推。
const:当你的变量不想改的时候就用const,唯一的作用就是减小代码出错的概率。
高精度运算:高精度除高精度,可以用式除法,从一开始试,也可以用二分法。
2、多维数组
2.1多维数组就是数组的数组。
二维数组:Int a[3][4]; // 大小为3的数组,每个元素是含有4个整数的数组。
二维数组的初始化: int a[3][4] = {{1,2,3,4},{2,4,5,7},{8,9,4,6}}
a[0] | a[0][0] | a[0][1] | a[0][2] | a[0][3] |
a[1] | a[1][0] | a[1][1] | a[1][2] | a[1][3] |
a[2] | a[2][0] | a[2][1] | a[2][2] | a[2][3] |
三维数组:Int arr[10][20][30] = {0}; // 将所有元素初始化为0
// 大小为10的数组,它的每个元素是含有20个整数的数组。这些数组的元素是含有30个整数的数组
2.2数组的初始化
头文件:cstring 函数:memset()
函数memset的用法:memset(初始化开始的位置,初始化的值,字节数)
memset里所有单位都是字节byte
1 byte = 1 bit(bit就是01) | 一般说网络带宽的时候会用到bit 1Mb = 1/8 MB |
1 KB = 1024 byte (byte一般说的都是内存) | 8Mb = 1MB |
1 MB = 1024 KB | 100Mb = 12.5MB |
1GB = 1024 MB |
2.3数组的复制
memcpy()函数
用法:memcpy(b,a,sizeof a)//把a复制到b
五、字符串string
字符串就是人类和计算机沟通的重要手段。
1、字符与整数之间的联系—ASCII码
所有字符在计算机中的存储方式都是01串,每个常用字符都对应着一个-128~127的数字,二者之间可以相互转换。
48 | 0 |
65 | A |
97 | a |
char其实存储的时候存储的是数字,只不过在输出的时候计算机自动将整数转化成字符输出到屏幕上,除了输出到屏幕上这一步,他存储运算读取存的都是数字。char是一个字节,int是四个字节。
字符可以参加运算,运算时会将其当作是整数,在计算机中都是整数,只有在输出的时候才是字符。
2、字符数组
在C++和Java中,字符串是用" ",字符是用' ';但是在Python和Javascript中单引号和双引号无区别。
字符串就是字符数组加'\0'。'\0'在ASCII码中对应的数字就是0. C++中的0很灵活,int,double,float中表示是0,char中是'\0',在指针里面就是NULL,在bool里就表示false。-1也很灵活,很多也都是-1.
字符串是由数字、字母、下划线组成的一串字符;字符数组是用来存储字符串的一个数组。
字符数组就是一个普通的字符类型的数组,字符数组和字符串之间的关系:字符串是存储在字符数组中连续的一段以'\0'结尾的字符。
可以使用字符串来初始化字符数组,但是要注意,每个字符串最后都暗含一个'\0'字符,因此字符数组的长度至少要比字符串的数组多1。
2.1字符数组的初始化
2.2字符数组的输入输出
字符数组的输入和输出:
读入一行字符串,包括空格:
字符数组的输入:char s[100]
1、scanf - fgets(s,100,stdin)
2、cin- cin.getline(s,100)
如果是string类型的话,就用 getline(s,100)
字符数组的输出:
1、printf("%s\n",s);
2、cout << s << endl;
3、puts(s)//等价于printf,包括换行符
字符串和string哪个用的比较多:如果输入数据量比较大用字符串比较好;如果没有时间限制的话用string比较好。
2.3字符数组的常用操作
下面几个函数需要引入头文件 #include <string.h>
1、strlen(str) //string length:求字符串的长度,strlen只计算字符串元素的长度,'\0'不计入其中。
2、strcmp(a,b) //string compare:比较两个字符串的大小,如果a > b返回1;如果 a == b返回0;如果a < b返回-1。这里的比较方式是字典序!字典序是指按照字母顺序,或者数字小大顺序,由小到大的形成序列。10和2按照字典序比较的话,10<2.字典序一般和贪心相关。
3、strcpy(b,a) // string copy:将字符串a复制给从b开始的字符数组。
2.4遍历字符数组中的字符
把字符串当作一个字符数组就可以了
3、标准库类型 string
可变长的字符序列,比字符数组更加好用。需要引入头文件:#include <cstring>
3.1string的定义和初始化
string是以类型形式对字符串进行的封装,使得他除了像一个存储字符的容器外,还包含了字符序列的处理操作。
3.2 string上的操作
1、string 的读写
string的读入只能用cin,不能用scanf;但是string的输出既可以用cout,也可以用printf,还可以用用put。
注意:不能直接用printf和put输出,需要写成printf(“%s”, s.c_str());和put(s.c_str)
2、使用getline读取一整行
string的读入一行:用getline(cin,s)
字符串的读入一行:用cin.getline(s,100) 和fgets(s,100,stdin)
3、string的empty和size操作(???注意size是无符号整数,因此 s.size() <= -1一定成立):
empty:判断string是不是为空,true返回1,false返回0
size:等价于strlen()函数,但是比strlen()函数要快
4、string的比较
支持 > < >= <= == !=等所有比较操作,按字典序进行比较。string比字符数组方便很多,字符数组比较需要用到strcmp()函数,string直接比较即可。
5、string的赋值
string s1(10, ‘c’), s2; // s1的内容是 cccccccccc;s2是一个空字符串
s1 = s2; // 赋值:用s2的副本替换s1的副本,此时s1和s2都是空字符串
6、两个string对象相加:就是直接拼接在一起,支持累加,string也可以加字符串和字符。
string s1 = “hello, ”, s2 = “world\n”;
string s3 = s1 + s2; // s3的内容是 hello, world\n
s1 += s2; // s1 = s1 + s2
7、字面值和string对象相加
做加法运算时,字面值和字符都会被转化成string对象,也就是加法的左右两边只有有一个是string对象,不管是字符还是字符串都会转换成string类型。因此直接相加就是将这些字面值串联起来。
string s1 = “hello”, s2 = “world”; // 在s1和s2中都没有标点符号
string s3 = s1 + “, “ + s2 + ‘\n’;
当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符的两侧的运算对象至少有一个是string:
string s4 = s1 + “, “; // 正确:把一个string对象和有一个字面值相加
string s5 = “hello” +”, “; // 错误:两个运算对象都不是string
string s6 = s1 + “, “ + “world”; // 正确,每个加法运算都有一个运算符是string
string s7 = “hello” + “, “ + s2; // 错误:不能把字面值直接相加,运算是从左到右进行的,第一个加号两侧没有一个是string对象
8、string中一个很常用的函数substr
作用是截取某个字符串中的子串,用法有两种形式:
string s1 = s.substr(4) //表示从下标4开始一直到结束
string s2 = s.substr(5,3) //表示从下标5开始,截取3个字符
3.3处理string对象中的字符
第一种方式:把string当作是一个普通的字符数组来处理
第二种特殊方式:C++的范围遍历
六、函数
函数让代码变得更加简洁。
1、函数基础
1.1函数定义:
函数的组成包括函数的返回类型、函数名字以及由0个或者多个函数形参组成的列表,函数的返回值可以是任意类型(int char double float bool
string),但是不能是数组类型或函数类型,但可以是指向数组或者函数的指针。有一个特殊的是void,就表示返回为空。
1.2函数的调用
函数的执行顺序:执行顺序先按照顺序执行,等到函数部分后,再去执行函数部分,输出返回值后,再回来按照顺序继续执行。
函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数。此时,主调函数的执行被暂时中断,被调函数开始执行。
1.3 函数的声明:int foo(int n);
函数声明和函数定义的区别:函数声明不需要写函数体,但是函数定义需要写函数体。
1.4形参和实参
实参是形参的初始值。第一个实参初始化第一个形参,第二个实参初始化第二个形参,依次类推。形参和实参的类型和个数必须匹配。
形参也可以设置默认值,但所有默认值必须是最后几个。当传入的实参个数少于形参个数时,最后没有被传入值的形参会使用默认值。
1.5局部变量、全局变量与静态变量
局部变量存储在栈中,只可以在函数内部使用,不初始化的话变量值是随机的;全局变量存储在堆中,可以在所有函数内使用,不初始化变量值默认为0。
当局部变量与全局变量重名时,会优先使用局部变量。
静态变量:eg. static int cnt
有时候,我们希望函数中局部变量的值在函数调用结束之后不会消失,而仍然保留其原值。即它所占用的存储单元不释放,在下一次调用该函数时,其局部变量的值仍然存在,也就是上一次函数调用结束时的值。这时候,我们就应该将该局部变量用关键字 static 声明为“静态局部变量”。
静态变量可以理解为在函数内部定义的只有该函数可以使用的全局变量,静态变量未初始化时默认为0,与全局变量相同,开在堆空间里。但是静态变量与全局变量相比有一个好处就是不用担心变量重名的问题。
2、参数传递
2.1传值参数
参数传递进来之后只是一个局部变量,修改形参不会改变实参的值,在函数内部修改参数的值,main函数内该参数对应变量的值不会发生改变。
2.2传引用参数
引用就相当于别名,和变量名指同一变量。 使用引用的作用:避免拷贝、让函数返回多个信息的时候可以通过函数参数的形式返回。
函数内部对形参进行修改,main函数内部实参随之发生修改。
当函数名一样的时候不会报错,函数一不一致要看函数名和函数的参数类型,如果都一直就会报错。同一个函数名可以定义多个函数,只要参数列表不一样就可以。
2.3数组形参
在函数中修改数组中的值,会影响函数外面的数组。(数组的传递其实也是引用传递)
数组就是一种特殊的指针
一维数组形参的写法:
// 尽管形式不同,但这三个print函数是等价的
void print(int *a) {/* … */} //指针的写法
void print(int a[]) {/* … */}
void print(int a[10]) {/* … */}
多维数组形参的写法:
// 多维数组中,除了第一维之外,其余维度的大小必须指定
void print(int (*a)[10]) {/* … */}
void print(int a[][10]) {/* … */}
2.4 sizeof
2.5 inline
在函数前面加上inline就相当于在main函数内调用函数的时候就是直接带入函数体里面的内容,好处就是有时候运行可能会更快一点
3、返回类型和return语句
return 语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。
return语句有两种形式:
return;
return expression;
3.1无返回值的return语句
没有返回值的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语句
}
3.2 有返回值的return语句
只要函数的返回类型不是void,则该函数内的每条return语句必须返回一个值。return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换函数的返回类型。
4、函数的递归
在函数内部可以随便调用其他函数,也可以调用自己。
函数调用过程:
函数的递归:在一个函数内部,也可以调用函数本身。
七、类、结构体、指针、引用
类可以将变量、数组和函数完美的打包在一起。
1、类与结构体
1.1类的定义
类其实也是一种数据类型,而且是一种复杂的数据类型。只不过不同于一般的数据类型,类中包含了不同的数据类型及和这些数据类型相关的操作。
类中的变量和函数被统一称为类的成员变量。!!!注意:class 类名 { };
类里面的pubilc和private可以不写,但是不写的话class默认都是private
第一种class的定义方式:
第二种class的定义方式
1.2类的使用
1.3结构体
结构体和类的作用是一样的。不同点在于类默认是private,结构体默认是public。
在习惯上,一般把只有数据的,函数比较少的,比较短的和数据相关的东西定义成结构体;比较抽象的比较复杂比较长的打包成一个class。
1.4构造函数
类和结构体都是有构造函数的。构造函数可以实现在class或struct对象创建时初始化数据成员。在创建一个class或struct对象时,系统会为该对象创建一个不带参数的默认的构造函数。一个对象可以定义多个构造函数
结构体构造函数第一种定义方式:
结构体构造函数的第二种定义方式:
类的构造函数前面要先加上public:
2、指针和引用
2.1变量的存储
内存空间图
变量的存储和赋值
2.2指针
指针变量所存的内容就是变量存储的内存地址编号,可以通过指针来修改变量的值。
指针的定义: int* p = &a;//在这里int* 表示指针的类型,p是指针的变量名
通过指针修改变量的值:*p里的 * 是一个操作符和 !类似,表示查看指针p所存地址对应的变量的值;可以通过*p来改变相应变量的值。
指针也是有指针的,指针的指针就是存放指针的地址。 int** p1 = &p;
2.3数组名也是一种特殊的指针,被称为数组指针,可以做运算。
对于数组来说,int a[5] = {1,4,6,8,9}; int* p;
数组名a本身就是一个指针,a = p
a和p,以及a + n, p + n存储的都是地址
*(a + n)和 *(p + n)存储的都是地址对应的变量值
!!同类型的指针可以相减,得到的结果是地址相隔变量的个数;不同类型的指针不可以相减。
2.4 C++的引用
引用和指针类似,相当于给变量起了个别名。引用中的&与取地址符号&区别开。
3、链表
3.1结点
用一组任意的存储单元存放线性表的数据元素,且元素的逻辑次序和物理次序不一定相同。为了能够正确表示数据元素之间的逻辑关系,在存储元素值的同时,还必须存储指示其后续得地址信息,称为指针或链,这两部分组成一个“结点(Node)”表示线性表中的一个数据元素。
单链表的结点由两部分组成,一个是数据域,用来存放数据元素的值;一个是指针域,用来存放后继元素的地址。
调用struct或class里的成员变量时,要看调用成员变量的类型,如果是指针的话要用->,如果单纯是一个变量的话,要用.
3.2单链表
通过每个结点的指针将线性表中的数据元素按照其逻辑顺序链接在一起的存储方式称为链式存储结构,采用这种存储结构的线性表称为链表。????
3.3 单链表的遍历
定义一个指针 Node* i,初始值为头指针head;因为最后一个结点的指针为NULL,所以条件表达式为 i != 0;
最后修改循环变量表达式应该为下一个指针 i = i -> next;输出为结点的数据值。
3.4 添加、删除结点
1、生成一个新结点作为头结点
2、结点的删除 :在链表中结点的删除并不是传统意义的删除,而是指在链表遍历时跳过遍历不到此结点就是删除了这个结点。
八、STL
1、 STL定义
STL,标准模板库或泛型库,其包含有大量的模板类和模板函数,是 C++ 提供的一个基础模板的集合,用于完成诸如输入/输出、数学计算等功能;从根本上来说就是STL 是一些容器、算法和其他一些组件的集合。
2、 STL的一些常用容器
除了队列之外,所有容器都可以遍历。
2.1、 vector
1)vector的定义
vector是一个可以自动改变长度的动态数组;它能够在运行阶段设置数组的长度、在末尾增加新的数据、在中间插入新的值、长度任意被改变。它在头文件#include<vector>里,也在命名空间std里面。vector以倍增的方式开辟空间。
2)vector的声明
格式:vector<数据类型> 数组名;//数据类型也可以为结构体和类
例子:vector<int> a({1,2,3}); //定义了一个整型的vector数组
vector<int> b[233]; //相当于第一维长233,第二位长度动态变化的int数组
struct res {int x; int y;}; vector<res> c; //自定义的结构体类型也可以保存在vector中
3)vector的函数
(1).size()/.empty()函数
.size()函数返回vector的实际长度(包含的元素个数),.empty()函数返回一个bool类型,表明vector是否为空。所有的STL容器都支持这两个方法,含义也相同.
(2).clear()函数
.clear()函数表示把当前vector全部清空。除了队列,优先队列和栈之外所有的STL容器里都有.clear()函数。
4)迭代器
迭代器就像STL容器的“指针”,可以用星号“*”操作符解除引用。可以把迭代器当作一个指针来看。
4.1迭代器的声明
vector<int> :: iterator it = a.begin(); //定义了一个名为it的迭代器;a.begin()表示起始迭代器
4.2迭代器相加减
vector的迭代器是“随机访问迭代器”,可以把vector的迭代器与一个整数相加减,其行为和指针的移动类似。可以把vector的两个迭代器相减,其结果也和指针相减类似,得到两个迭代器对应下标之间的距离。
5) .begin()和.end()
每一个vector有两个特殊的迭代器a,begin()和a.end();.begin()函数返回指向vector中第一个元素的迭代器。例如a是一个非空的vector,则*a.begin()与a[0]的作用相同。.end()函数返回vector的尾部,即第n个元素再往后的下一个的迭代器。所有的容器都可以视作一个“前闭后开”的结构,即[begin, end)
6)vector的遍历
第一种方式按照数组的方式:
for (int i = 0; i < a.size() ; ++i) cout << a[i] << ' ';
cout << endl;
第二种使用迭代器来遍历:
for (vector<int> ::iterator i = a.begin(); i != a.end() ; ++i) cout << *i << ' ';
cout << endl;
//vector<int> ::iterator部分就可以用auto,就很方便
第三种范围遍历:
for (int x : a) cout << x << ' ';
cout << endl;
7).front()/.back()
.front()函数返回vector的第一个元素,等价于*a.begin() 和 a[0]。
.back()函数返回vector的最后一个元素,等价于a[a.size() – 1]。
.push_back() 和 .pop_back()
a.push_back(x) 把元素x插入到vector a的尾部。
b.pop_back() 删除vector a的最后一个元素。
8)vector自带比较运算
vector<int> a, b;
if(a > b)//vector按照字典序来比较
2.2、queue
头文件queue主要包括循环队列queue和优先队列priority_queue两个容器。
循环队列queue:先进先出,类似于一个管道,从左边进右边出。
优先队列priority_queue:无序的,优先输出最大值。
1)queue声明
格式:
//循环队列
queue<int> q;
queue<double> s;
struct res{
int x,a,b;
};
queue<res> r;
//优先队列
priority_queue<int> s;//大根堆:每次返回一个最大值
priority_queue<int, vector<int>,greater<int>> b;//小根堆:每次返回一个最小值
priority_queue<pair<int,int>> a;//pair是一个二元组
struct res{
int x;
int y;
bool operator < (const res& t) const{
return a < t.a; //表示a越大越靠前
}//想定义一个结构体类型的优先队列,必须在结构体内重载一个<号,因为对应的是大根堆
};
priority_queue <res> c;
struct rec{
int x;
int y;
bool operator > (const rec&t) const{
return a > t.a
}//对应小根堆,重载>号
};
priority_queue<int, vector<int>, greater<int>> f;
2)循环队列queue
循环队列queue的长度和队列当中现存的元素数量相关,和插入次数无关。
循环队列只能在队尾插入,队头弹出。队头指向第一个插入元素。
queue<int> q;
q.push(1);//在队头插入一个元素
q.pop();//弹出队尾元素
q.front();//返回队头元素
q.back();//返回队尾元素
3)优先队列
priority_queue<int> d;
d.push(1);//插入一个数
d.top();//取最大值
d.pop();//删除最大值
4)清除队列
p = queue<int>();//重新初始化一下
2.3 stack
栈和队列类似,先进后出,弹出元素是最后一个插入元素,其余类似。队尾进队尾出。
stack<int> stk;
stk.push(1);//插入一个元素
stk.pop();//删除一个元素
stk.top();//输出最大元素
2.4 deque
双端队列deque在两端都可以进行元素进出的操作。像vector一样支持随机访问。
deque<int> d;
d.begin(), d.end();//返回deque头尾迭代器
d.front(), d.back();//返回队头/队尾元素
d.push_back(),d.push_front();//从队尾/队头入队
d[0];//随机访问双端队列
d.pop_back(),d.pop_front();//从队尾/队头出队
d.clear();//清空队列
2.5set
1)声明
头文件set主要包括set和multiset两个容器,分别是“有序集合”和“有序多重集合”
set:元素不能重复 multiset:元素可重复 二者支持函数基本相同。
set<int> a;//元素不可重复,插入重复元素会被自动忽略
multiset<int> b;//元素可以重复
struct res{
int x;
int y;
bool operator< (const res& t) const{
return x < t.x;
}//set里面需要作比较,需要重载一个<号
};
set<res> s;
2).size()/.empty()/.clear()
与vector类似
3)迭代器
set和multiset的迭代器称为“双向访问迭代器”,不支持“随机访问”,支持星号(*)解除引用,仅支持”++”和--“两个与算术相关的操作。
set<int>::iterator it = a.begin();
it ++;//it ++:表示指向“下一个”元素,这里的“下一个”是指从小到大排序排在it下一名的元素。
it --;//同理it --:表示指向“上一个”元素。
4)set相关函数
set就像vector一样,是“前闭后开”的
set<int> a;//元素不可重复,插入重复元素会被自动忽略
multiset<int> b;//元素可以重复
set<int>::iterator it = a.begin();
it ++;//it ++:表示指向“下一个”元素,这里的“下一个”是指从小到大排序中排在it下一名的元素。
it --;//同理it --:表示指向“上一个”元素。
a.begin();//指向集合中最小元素的迭代器
a.end();//指向集合中最大元素下一个位置的迭代器
a.insert(x);//插入一个x元素
a.find(x);//查找x元素,返回指向该元素的迭代器;如果不存在,就会返回a.end()
if(a.find(x) == a.end())//判断x是否在a中
a.lower_bound(x);//找到大于等于x的最小元素的迭代器
a.upper_bound(x);//找到大于x的最小元素的迭代器
a.erase(x);//把等于x的所有元素删掉
a.erase(it);//删除迭代器it指向的元素
a.count(x);//返回集合s中等于x的元素个数,因为a是一个set,x存在返回1;不存在返回0
2.6 map
map容器是一个键值对key-value的映射,其内部实现是一棵以key为关键码的红黑树。Map的key和value可以是任意类型,其中key必须定义小于号运算符。
map<key_type, value_type> name;
map<int,int> a;//map存的是一个二元组,第一个元素映射到第二个元素
a[1] = 2;
a[10000000] = 3;
cout << a[10000000] << endl;//map定义了之后可以当作数组使用
map<string,int> b;
b["hyn"] = 2;//数组下标可以定义成一个字符串
cout << b["hyn"] << endl;
map<string,vecor<int>> c;
c["hyn"] = vector<int>({1,2,3,4});
cout << c["hyn"][3]<< endl; //map可以让我们像用数组一样取用其他结构
1)声明
map<long,long,bool> vis;
map<string,int> hash;
mao<pair<int,int>,vector<int>> test;
2)map的相关函数
.size(key)/.empty(key)/.clear(key)/.begin(key)/.end(key)均与set类似。
.insert()/.erase():参数是pair(key,value)二元组
.find(key)
map<string,vector<int>> a;
a.insert({'a',{}});//插入元素
a.erase({'a',{}});//删除元素
a.find('a');//a.find(x) 在变量名为a的map中查找key为x的二元组。
[]操作符: a[key] 返回key映射的value的引用,[]操作符是map最吸引人的地方。我们可以很方便地通过h[key]来得到key对应的value,还可以对h[key]进行赋值操作,改变key对应的value。
2.7 unordered_set
unordered_set就是一个无序的set,unordered_set里面也是由两个容器,分别是unordered_set和unordered_multiset。
unordered_set和set完全一样,只不过没有.lower_bound()和.upper_bound()两个函数。因为它是无序的.
unordered_set<int> s;//哈希表,不能存储重复元素
unordered_multiset<int> d;//哈希表,可以存储重复元素
2.8 unordered_map
unordered_map<int> a; //哈希表
2.9 pair就是一个二元组
pair<int,string> a, b;
a = {2,"hyn"}; //pair赋值
b = {4,"yud"};
if(a > b) //pair支持比较运算,是双关键字比较,第一个相同,再比较第二个
cout << a.first << ' ' << a.second << endl; //输出
a = make_pair(1,"abg");//make_pair()函数赋值
九、位运算和常用库函数
1、位运算
1.1位运算就是直接对整数在内存中的二进制位进行操作。
运算符 | ||||
与& (and) | 0&0 = 0 | 0&1=1 | 1&0=0 | 1&1=1 |
或| (or) | 0|0=0 | 0|1=1 | 1|0=1 | 1|1=1 |
取反~ (not) | ~0=1 | ~1=0 | ||
异或^ (xor) | 0^0=0 | 1^0=1 | 0^1=1 | 1^1=0 |
左移 << | 左移n位就是向后补n位0 | a左移n位就相当于 a*(2^n) | ||
右移 << | 右移n位就是删掉右侧几位 | a右移n位就相当于 a/(2^n) |
如果是两个整数的话,int a,b; a和b分别是32位0和1,分别对他们每一位进行位运算
1.2 位运算的常用操作
1)求x的第k位数字 x >> k &1
a = 110110,则a的第n位是指从第0位开始从右向左数第n位。
int a = 13;
cout << (a >> 4 & 1) << endl;//返回13的二进制的第4位
for(int i = 5; i >= 0; i --) cout << (a >> i & 1);
2)lowbit(x) = x& - x,返回x的最后一位1以及后面的0;在计算机中-a和~a+1的二进制表示完全一样
a = 110110; //返回10
a = 11000; //返回1000
2、常用的库函数
常用的库函数基本上都在头文件 #include<algorithm>
2.1 reverse翻转:reverse(起始位置,末尾位置的下一个)
//翻转一个vector
vector<int> a({1,2,3,4,5});
reverse(a.begin(),a.end());//翻转一个vector
for(int x : a) cout << x << ' ';//返回结果5 4 3 2 1
cout << endl;
//翻转一个数组
int a[] = {1,2,3,4,5};
reverse(a,a + 5);//reverse(数组的第一个元素的指针,数组最后一个位置的下一个的位置)
2.2 unique 去重:unique(起始位置,末尾位置的下一个)
unique函数去重要求重复元素必须挨在一起;会把所有数组的不同元素放在最前面。
unique函数返回值为,去重后数组的end()
//数组去重
int a[] = {1,1,2,2,2,3,4};
int m = unique(a, a+7) - a; //m为去重后新数组的长度
for(int i = 0; i <= m; i ++) cout << a[i] << ' ';//输出返回后新数组
cout << endl;
//vector去重第一种方式
vector<int> a({1,1,2,2,2,3,4});
int m = unique(a.begin(),a.end()) - a.begin();
for(int i = 0; i <= m; i ++) cout << a[i] << ' ';
cout << endl;
//vector去重第二种方式
a.erase(unique(a.begin(),a.end()),a.end());//.erase(a,b)表示删除a和b之间的所有元素
2.3 random_shuffle()随机打乱数组顺序:random_shuffle(起始位置,末尾位置的下一个)
写法和reverse相同
vector<int> a({1,2,3,4,5});
random_shuffle(a.begin(),a.end());
for(int x : a) cout << x << ' ';
cout << endl;
2.4 sort 排序:sort(起始位置,末尾位置的下一个)
//对数组和vector进行排序
vector<int> a({1,2,3,4,5});
sort(a.begin(),a.end());//默认从小到大排序
sort(a.begin(),a.end(),greater<int>());//从大到小排序
for(int x : a) cout << x << ' ';
cout << endl;
//按照自己想要的顺序进行排序
bool cmp(int a, int b){ //a是否应该排在b的前面
return a > b; //按照从大到小的顺序排列
return a < b; //按照从小到大的顺序排列
}
vector<int> a({1,2,3,4,6});
sort(a.begin(),a.end(),cmp);//按照函数cmp指定的方式进行排列
//对结构体进行排序的第一种方式
struct res{
int x, y;
bool operator< (const res &t) const{
return x < t.x;
}//在结构体内部重载小于号,可以直接使用sort()函数,且排列顺序是从小到大
}a[5];
for(int i = 0; i < 5; i ++){
a[i].x = -i;
a[i].y = i;
}
sort(a,a+5);//重载大于号,排列顺序是从大到小
//对结构体的第二种排序方式
struct res{
int x, y;
}a[5];
bool cmp(res x, res y){
return x > y;//从大到小
return x < y;//从小到大
}
int main(){
for(int i = 0; i < 5; i ++){
a[i].x = -i;
a[i].y = i;
}
sort(a,a+5,cmp);//按照函数的指定顺序进行排列
return 0;
}
2.5 .lower_bound()/.upper_bound() 二分:.lower_bound(.begin(),.end(),x:比较的值)
lower_bound 的第三个参数传入一个元素x,在两个迭代器(指针)指定的部分上执行二分查找,返回指向第一个大于等于x的元素的位置的迭代器(指针)。
upper_bound 的用法和lower_bound大致相同,唯一的区别是查找第一个大于x的元素。当然,两个迭代器(指针)指定的部分应该是提前排好序的。
在有序int数组(元素存放在下标1~n)中查找大于等于x的最小整数的下标:
int I = lower_bound(a + 1, a + 1 + n,. x) – a;
在有序vector<int> 中查找小于等于x的最大整数(假设一定存在):
int y = *--upper_bound(a.begin(), a.end(), x);
//数组的二分查找
int a[] = {1,4,6,8,9};
int* p = lower_bound(a, a+5, 3);//返回一个迭代器所以要定义一个指针
cout << *p << endl;//返回4
int t = lower_bound(a, a+5, 3) - a;//返回在数组中的下标
cout << a[t] << endl;//返回元素值
//vector的二分查找
vector<int> a{1,2,5,8,0};
int t = lower_bound(a.begin(),a.end(),5) - a.begin();
cout << a[t] << endl;