《算法笔记》C&C++快速入门指南
1 基本数据类型#
1.1 变量的定义#
- 定义格式
变量类型 变量名;
变量类型 变量名 = 初值; //变量可以在定义的时候就赋初值
-
变量名取名满足条件:
-
不能是C语言标识符,建议取有实际意义的变量名,这样可以提高程序可读性
-
变量名的第一个字符必须是字母或下划线,除第一个字符之外的其他字符必须是字母、数字或下划线
-
区分大小写
-
1.2 变量类型#
整型#
整型可以分为短整型(short)、整型(int)、长整型(long long),其中短整型(short)一般用不到,整型int也被称为long int,长整型也被称为long long int.
-
整型int
- 一个整数占用32bit(即32位),也即4Byte(即4字节),取值范围是$-2^{31}- +(2{31}-1)$,绝对值在$109$范围以内的整数都可以定义成int
-
长整型long long
-
一个整数占用64bit,也即8Byte,取值范围是-2^{63} - +(2{63}-1),如果题目要求的整数取值范围超过2147483647(例如10),就得用long long型来存储,如果long long型赋大于2^{31}-1的初值,则需要在初值后面加上LL,否则会编译错误
-
在整型数据前面加unsigned,以表示无符号型。例如unsigned int和unsigned long long,占用的位数和原先相同,但是把负数范围挪到正数上来了。unsigned int的取值范围是$0-2^{32}-1$
-
浮点型#
对于浮点型来说,不要使用float,碰到浮点型的数据都应该用double来存储
-
单精度float
- 浮点数的范围是$-2{128}-+2$,有效精度有6~7位
-
双精度double
- 一个浮点数占用64bit,范围是$-2{1024}-+2$,其有效精度有15~16位
字符型#
-
字符变量和字符常量
-
字符常量使用ASCII码统一编码,标准ASCII码的范围是0~127。
-
小写字母比大写字母的ASCII码值大32
-
字符常量(必须是单个字符)必须用单引号标注,%c是char型的输出格式
-
-
转义字符
-
\n:换行,\t:Tab键
-
\0:代表空字符NULL,其ASCII码为0,请注意\0不是空格
-
-
字符串常量
-
字符串常量是由双引号标记的在字符集
-
字符串常量可以作为初值赋给字符数组,并使用%s的格式输出
-
不能把字符串常量赋值给字符变量,因此char c = "abcd"的写法是不允许的
-
布尔型#
-
布尔型在C++中可以直接使用,但是在C语言中必须添加stdbool.h头文件才可以使用
-
“bool型变量”取值只能是true(真)或者false(假)
-
整型常量在赋值给布尔型变量时会自动转换为true(非零)或者false(零),true和false在存储时分别为1和0
1.3 强制类型转换#
-
格式:(新类型名)变量
-
%.1f是指保留一位小数输出
-
如果将一个类型的变量赋值给另一个类型的变量,却没有写强制类型转换操作,那么编译器会自动转换。不是任何时候都可以不写类型转换,因为如在计算时需要类型转换,那么久不能等它算完再在赋值的时候转换。
1.4 符号常量和const常量#
- 用一个标识符来替代常量,又称"宏定义"或者"宏替换"
#define 标识符 常量
#define pi 3.14 //末尾不加分号
#const 数据类型 变量名 = 常量;
#const double pi = 3.14;
//两种写法都为常量,一旦确定其值后就无法改变
- define除了可以定义常量外,其实可以定义任何语句或片段
#define 标识符 任何语句或片段
#define ADD(a,b) ((a)+(b))//直接使用ADD(a,b)来代替a+b的功能
//为保险起见,多加括号是必须的,宏定义是直接对应的部分替换,然后才进行编译和运行,因为替换的部分直接原封不动替换进去
//尽量不要使用宏定义来做除了定义常量以外的事情,除非给能加的地方都加上引号
1.5运算符#
算术运算符#
-
+,-,*,/,%,++,- -
-
对于除法运算符,当被除数跟除数都是整数时,并不会得到double浮点型的数,而是直接舍去小数部分(向下取整)
-
除数如果为0,会导致程序异常退出或是得到错误输出"1.#INF00"
-
取模运算返回被除数与除数相除得到的余数
-
自增运算符:i++是先使用i再将i加1,而++i则是先将i加1再使用i
关系运算符#
<、>、<=、>=、==、!=
逻辑运算符#
&&、!、||
条件运算符#
( ? : )三目运算符
A?B:C;
//如果A为真,那么执行并返回B的结果;如果A为假,那么执行并返回C的结果
位运算符#
-
位运算符使用得较少,最常用的是左移运算符
-
由于int型的上限为$2{31}-1$,因此程序中无穷大的数INF可以设置成(1<<31)-1(注意:必须加括号,因为位运算符的优先级没有算术运算符高),但一般更常用的是$2-1$,二进制的形式为0x3fffffff
const int INF = (1<<30)-1;
const int INF = 0x3fffffff;
-
六种位运算符
-
<<(左移a<<x):整数a按二进制位左移x位
-
(右移 a>>x):整数a按二进制位右移x位
-
&(位与 a&b):整数a和b按二进制对齐,按位进行与运算(除了11得1,其他均为0)
-
|(位或 a|b):整数a和b按二进制对齐,按位进行或运算(除了00得0,其他均为1)
-
^(位抑或 a^b):整数a和b按二进制对齐,按位进行抑或运算(相同为0,不同为1)
-
~(位取反 ~a):整数a的二进制的每一位进行0变1、1变0的操作
2 顺序结构#
2.1 赋值表达式#
- 等号来表示赋值运算
int n = 5;
n = 6;
int m;
n = m = 5;
- 赋值运算符可以通过将其他运算符放在前面来实现赋值操作的简,如n+=2
n/= m+1等价于n=n/(m+1)
2.2 使用scanf和printf输入/输出#
scanf#
-
格式:scanf("格式控制",变量地址); 例如scanf("%d",&n);
-
为了得到变量的地址,需要在变量前加一个&(称为取地址运算符),也就是"&变量名"的写法
-
常见数据类型变量的scanf格式符
数据类型 | 格式符 |
---|---|
int | %d |
long long | %lld |
float | %f |
double | %lf |
char | %c |
字符串(char数组) | %s |
-
数组名称本身就代表了这个数组第一个元素的地址,所以不需要再加取地址运算scanf("%s",str)
在scanf中,除了char数组整个输入的情况不加&之外,其他变量类型都需要加& -
scanf的双引号的内容其实就是整个输入,只不过把数据换成它们对应的格式符并把变量的地址按次序写在后面而已
-
除%c外,scanf对其他格式符(如%d)的输入是以空白符(即空格、换行等)为结束判断标志的,字符数组使用%s读入的时候以空格跟换行为读入结束的标志。
-
在写scanf时不要忘记&
print#
-
格式:printf("格式控制",变量名称);
-
常见的printf格式表只有double类型与scanf格式不同,double类型的变量,其输出格式变成了%f,而在scanf中却是%lf。
-
在printf中可以使用转义字符,如果想要输出%或\,则需要在前面再加一个%或/。
printf("%%");
printf("\\");
-
三种实用输出格式
-
%md:事不足m位的int型变量以m位进行右对齐输出
-
%0md:当变量不足m位时,将在前面补足足够数量的0而不是空格。
-
%.mf:让浮点数保留m位小数输出,精度使用“四舍六入五成双”规则(如果是四舍五入,那么需要用到round函数)%m.nf:m控制宽度,n表示保留位数
2.3使用getchar和putchar输入/输出字符#
- getchar用来输入单个字符,putchar用来输出单个字符,getchar可以识别换行符
2.4 注释#
-
多行注释:/**/
-
单行注释://
2.5 typedef#
- 给复杂的数据类型起一个别名
#include<cstdio>
typedef long long LL; //给long long起个别名LL
int main()
{
LL a = 123456789012345LL;
printf("%lld\n",a);
return 0;
}
2.6 常用math函数#
-
fabs(double x):用于对double型变量取绝对值
-
floor(double x)和ceil(double x):分别用于double型变量的向下取整和向上取整,返回类型为double型
-
pow(double r,double p):该函数用于返回$r^{p}$,要求r和p都是double型
-
sqrt(double x):返回double型变量的算术平方根
-
log(double x):返回double型变量以自然对数为底的对数
注:C语言中没有对任意底数求对数的函数,因此必须使用换底公式来将不是以自然对数为底的对数转换为以e为底的对数 -
sin(double x)、cos(double x)和tan(double x):三个函数分别返回double型变量的正弦值、余弦值和正切值,参数要求是弧度制
const double pi = acos(-1.0); //因为cos(pi)=-1
-
asin(double x)、acos(double)和atan(double x):分别返回double型变量的反正弦值、反余弦值和反正切值
-
round(double x):将double型变量四舍五入,返回类型也是double型
3 选择结构#
3.1 if语句#
- if语句格式
if(条件A){
...
}
- if...else格式
if(条件A){
...
}else{
...
}
- if...elseif...else格式
if(条件A){
...
}else if(条件B){
...
} else{
...
}
- 如果表达式是"!= 0",则可以省略"!= 0";如果表达式为"0",则可以省略"0"
if(n)表示if(n!=0) if(!n)表示if(n==0)
3.2 if语句的嵌套#
- if语句的嵌套是指在if或者else的执行内容中使用if语句
3.3 switch语句#
-
switch语句在分支条件较多时会显得比较精炼
-
格式
switch(表达式){
case 常量表达式1:
...
break;
case 常量表达式2:
...
break;
case 常量表达式n:
...
break;
default:
...
}
-
case下属语句没有使用大括号将它们括起来,这是由于case本身默认把两个case之间的内容全部作为上一个case的内容,因此不用加大括号。
-
break的作用在于可以结束当前switch语句,如果将其删去,则程序将会从第一个匹配的case开始执行语句,直到其下面的所有语句都执行完毕才会退出switch
4 循环结构#
4.1 while语句#
- 格式
while(条件A){
...
}
4.2 do...while语句#
- 格式
do{
...
}while(条件A); //注意:这里有分号
- do...while会先执行循环体一次,然后才去判断循环条件是否为真,这就使得do..while语句的实用性远不如while
4.3 for语句#
- for循环使用频率最高
for(表达式A;表达式B;表达式C){
...
}
//1.在for循环开始前,首先执行表达式A
//2.判断表达式B是否成立:若成立,执行省略号内容;否则,退出循环
//3.在省略号内容执行完毕后,执行表达式C,之后回到2.
- 在C语言中不允许在for语句的表达式A里定义变量(例如int i的写法是不允许的),但是在C++中可以,因此下面这种写法需要把文件保存为.cpp文件才能通过编译
4.4 break和continue语句#
-
break在需要的场合下退出循环
-
continue:在需要的地方临时结束循环的当前轮回,然后进入下一个轮回
5 数组#
5.1 一维数组#
-
一维数组定义格式:数据类型 数组名[数组大小]; 注意数组大小必须是整数常量,不可以是变量。
-
访问格式:数组名称[下标]
-
一维数组初始化:需要给出用逗号隔开的从第一个元素开始的若干个元素的初值,并用大括号括住。后面未被赋初值的元素将会由不同编译器内部实现的不同而被赋不同的初值(可能是很大的随机数),而一般情况默认初值为0。
int a[10]={5,3,2,6,8,4};
- 给整个数组赋初值0,只需要把第一个元素赋为0,或者只用一个括号来表示
int a[10]={0};
int a[10]={};
- 递推(分为递推和逆推):可以不断让后一位的结果由前一位或前若干位计算得来。
5.2 冒泡排序#
-
冒泡排序的本质在于交换,即每次通过交换的方式把当前剩余元素的最大值移动到一端,而当剩余元素减少为0时,排序结束。
-
交换两个数
#include<stdio.h>
int main()
{
int a=1,b=2;
int temp = a;
a = b;
b = temp;
printf("a = %d,b = %d\n",a,b);
return 0;
}
- 冒泡排序:当第i趟时,从a[0]到a[n-i-1]都需要与下一个数比较
#include<stdio.h>
int main()
{
int a[10] = {3,1,4,5,2};
for(int i=1;i<=4;i++) //进行n-1趟
{
//第i趟时从a[0]到a[n-i-1]都与它们下一个数比较
for(int j=0;j<5-i;j++)
{
if(a[j]>a[j+1]) //如果左边的数更大,则交换a[j]和a[j+1]
{
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
for(int i=0;i<5;i++)
{
printf("%d",a[i]);
}
return 0;
}
5.3 二维数组#
-
格式:数据类型 数组名[第一维大小][第二维大小]; 访问:数组名[下标1][下标2];
-
二维数组初始化:需要按第一维的顺序依次用大括号给出二维数组初始化情况,然后将它们用逗号分隔,并用大括号全部括住,而在这些被赋初值的元素之外的部分被默认赋值为0;
int a[5][6] = {{3,1,2},{8,4},{},{1,2,3,4,5}};
-
如果数组大小较大(大概$10^6$级别),则需要将其定义在主函数外面,否则会使程序异常退出,原因是函数内部申请的局部变量来自系统栈,允许申请的空间较小;而函数外部申请的全局变量来自静态存储区,允许申请的空间较大。
-
多维数组
5.4 memset——对数组中每一个元素赋相同的值#
-
给数组中每一个元素赋相同的值有两种方法:memset函数和fill函数
-
格式:memst(数组名,值,sizeof(数组名)); 添加string.h头文件
-
memset按字节赋值,只建议初学者使用memset赋0或-1,这样不容易弄错
-
如果要对数组赋其他数字(例如1),那么请使用fill函数(但memset的执行速度快)
-
对二维数组或多维数组的赋值方法也是一样的(仍然只需要写数组名),不需要改变任何东西
5.5 字符数组#
- 字符数组初始化:可以和普通数组一样,也可以直接赋值字符串来初始化(仅限于初始化,程序其他位置不允许这样直接赋值整个字符串)
char str1[15]={'G','o','o','d','','S','t','o','r','y','!'};
char str2[15]="Good Story!";
-
字符数组就是char数组,一维时可以当作字符串,二维时可以当作字符串数组,即若干字符串
-
scanf输入,printf输出
scanf对字符类型有%c和%s两种格式(printf同理,下同),其中%c用来输入单个字符,%s用来输入一个字符串并存在字符数组里。%c格式能够识别空格跟换行并将其输入,而%s通过空格或换行来识别一个字符串的结束。
-
getchar输入,putchar输出
用来输入和输出单个字符,可以用getchar()函数将每行末尾的换行符吸收掉
-
gets输入,puts输出
-
gets用来输入一行字符串(注意:gets识别换行符\n作为输入结束,因此scanf完一个整数后,如果要使用gets,需要先用getchar接受整数后的换行符),并将其存放于一维数组(或二维数组的一维)中;
-
puts用来输出一行字符串,即将一维数组(或二维数组的一维)在界面上输出,并紧跟一个换行
-
-
-
字符数组的存放方式
-
在一维字符数组(或是二维字符数组的第二维)的末尾都有一个空字符串\0,以表示存放的字符串的结尾,空字符串\0在使用gets或scanf输入字符串时会自动添加在输入的字符串的结尾,并占用一个字符位,而puts与printf就是通过识别\0作为字符串的结尾来输出的。
-
结束符\0的ASCII码为0,即空字符NULL,占用一个字符位,因此字符数组的长度要比实际存储字符串的长度至少多1。int型数组的末尾不需要加\0,只有char型数组需要。
-
\0跟空格不是一个东西,空格的ASCII码是32。
-
若不是使用scanf函数的%s格式或gets函数输入字符串(例如使用getchar),请一定要在输入的每个字符串后加入“\0”,否则printf和puts输出字符串会因为无法识别字符串末尾而输出一大堆乱码。
-
5.6 string.h头文件#
- string.h头文件包含了许多用于字符数组的函数
-
strlen():可以得到字符数组中第一个\0前的字符的个数,格式:strlen(字符数组);
-
strcmp():返回两个字符串大小的比较结果,比较原则是按字典序
-
格式:strcmp(字符数组1,字符数组2);
-
字典序就是字符串在字典中的顺序,strcmp的返回结果如下:
-
如果字符数组1<字符数组2,则返回一个负整数(不同编译器处理不同,不一定是-1);
-
如果字符数组1==字符数组2,则返回0;
-
如果字符数组1>字符数组2,则返回一个正整数(不同编译器处理不同,不一定是+1);
-
-
-
strcpy():可以把一个字符串复制给另一个字符串,格式:strcpy(字符数组1,字符数组2),是把字符数组2复制给字符数组1,这里的“复制”包括了结束符\0。
-
strcat():把一个字符串接到另一个字符串后面,格式:strcat(字符数组1,字符数组2); 是把字符数组2接到字符数组1后面
5.7 sscanf与sprintf#
-
sscanf(str,"%d",&n); sscanf写法的作用是把字符数组str中的内容以"%d"的格式写到n中(还是从左至右),可以配合正则表达式使用。
-
sprintf(str,"%d",n); sptintf写法的作用是把n以"%d"的格式写到str字符数组组中(还是从右至左)
-
利用sscanf和sprintf进行复杂输入和输出
#include<stdio.h>
int main()
{
int n;
double db;
char str[100]="2048:3.14,hello",str2[100];
sscanf(str,"%d:%lf,%s",&n,&db,str2);
printf("n=%d,db=%.2f,str2=%s\n",n,db,str2);
return 0;
}
#include<stdio.h>
//#include<string.h>
int main()
{
int n=12;
double db=3.1415;
char str[100],str2[100] = "good";
sprintf(str,"%d:%.2f,%s",n,db,str2);
printf("str = %s\n",str);
return 0;
}
6 函数#
6.1 函数的定义#
-
函数:是一个实现一定功能的语句和集合,并在需要时可以反复强调用而不必每次都重新写一遍。
-
格式
返回类型 函数名称(参数类型 参数)
{
函数主体
}
-
全局变量:在定义之后的所有程序段内都有效的变量(即定义在其之后所有函数之前)
-
局部变量:与全局变量相对,局部变量定义哦在函数内部,且只在函数内部生效,函数街商户时局部变量销毁
-
函数参数传递是值传递
6.2 再谈main函数#
- 一个程序只能有一个主函数,并且无论主函数写在哪个位置,整个程序一定是从主函数的第一个语句开始执行。
6.3 以数组作为函数参数#
-
函数的参数可以是数组,且数组作为参数时,参数中数组的第一维不需要填写长度(如果是二维数组,那么第二维需要填写长度),实际调用时也只是需要填写数组名。
-
数组作为参数时,在函数中对数组元素的修改就等同于是对原数组元素的修改(这与普通的局部变量不同)
-
虽然数组可以作为参数,但是却不允许作为返回类型出现,如果想要返回数组,则只能用上面的方法,将想要返回的数组作为参数传入。
6.4 函数的嵌套调用#
6.5 函数的递归调用#
- 函数的递归调用是指一个函数调用该函数自身
7 指针#
7.1 什么是指针#
-
在C语言中用“指针”来表示内存地址,指针是一个unsigned类型的整数
-
只要在变量前面加上&,就表示变量的地址
7.2 指针变量#
- 指针变量用来存放指针(或者可以理解成地址),在某种数据类型后加*用来表示这是一个指针变量
int* p1,p2; //只有p1是int*型的,而p2是int型的
//正确写法
//int *p1,*p2,*p3;
int a;
p1 = &a; //把变量的地址取出来,赋给对应的指针变量
-
int*是指针变量的类型,而后面的p才是变量名,星号是类型的一部分
-
用*p可以获得地址中存放的元素
-
指针可以进行加减法,减法的结果就是两个地址偏移的距离,对于一个int*类型的指针变量p来说,p+1是指p所指的int型变量的下一个int型变量地址。这个所谓的“下一个”是跨越了整个int型(即4Byte)
-
指针变量支持自增自减操作
-
基类型:指针变量存储的地址的类型称为基类型,基类型必须和指针变量存储的地址类型相同。
7.3 指针与数组#
-
数组名称可作为数组的首地址
-
a+i等同于&a[i],*(a+i)和a[i]等价,因此输入数组元素又可以写成:
scanf("%d",a+i);
- 枚举数组中的元素
#include<stdio.h>
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
for(int *p=a;p<a+10;p++)
printf("%d",*p);
return 0;
}
- 两个int型的指针相减,等价于在求两个指针之间相差了几个int
7.4 使用指针变量作为函数参数#
-
指针可以作为函数参数传参,这就是把变量的地址传入函数。如果在函数中对这个地址中的元素进行改变,原先的数据就会确实地改变。
-
利用指针交换两个数
#include<stdio.h>
void swap(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 1,b = 2;
int *p1 = &a , *p2 = &b;
swap(p1,p2);
printf("a=%d,b=%d\n",*p1,*p2);
return 0;
}
- 典型错误写法
void swap(int *a,int *b)
{
int *temp = a;
a = b;
b = temp;
}
//main函数传给swap函数的“地址”其实是一个“无符号整数”的数
//其本身也和普通变量一样只是“值传递”
//swap函数对地址本身进行修改并不能对地址指向的数据进行修改
能够使main函数里的数据发生变化的只能是swap函数中对地址指向的数据进行的修改
7.5 引用#
-
C++中的引用不产生副本,只是给变量起了个别名,对引用变量的操作就是对原变量的操作
-
不管是否使用引用,函数的参数名和实际传入的参数名可以不同
-
要把引用的&跟取地址运算符&区分开来,引用并不是取地址的意思
-
使用引用交换两个变量的值
void swap(int* &p1,int* &p2)
{
int *temp = p1;
p1 = p2;
p2 = temp;
}
//简单地把int*型理解成unsigned int型,而直接交换这样的两个整数变量是需要加引用的
- 引用是变量的别名,常量不可以使用引用
8 结构体(struct)的使用#
8.1 结构体的定义#
- 基本格式:
struct Name
{
//一些基本的数据结构或者自定义的数据类型
};
-
结构体定义
-
直接定义
struct studentInfo
{
int id;
char gender; //'F'or'M'
char name[20];
char major[20];
}Alice,Bob,stu[1000];
- 按照基本数据类型定义
studentInfo a;
studentInfo b[1000];
- 结构体里面能定义除了自己本身之外的任何数据类型(循环定义问题),但是可以定义自身类型的指针变量
struct node
{
//node n; //不能定义node型变量
node* next; //可以定义node*型指针变量
};
8.2 访问结构体内的元素#
- 访问结构体内的元素有两种方法:“.”操作和“->”操作
struct studentInfo
{
int id;
char name[20];
studentInfo* next;
}stu,*p;
//访问
stu.id;
(*p).name;
p->next;
8.3 结构体的初始化#
-
构造函数:不需要写返回类型,且函数名与结构体名相同。结构体内部会生成默认的构造函数,但是不可见。
-
自己重新定义构造函数
struct studentInfo
{
int id;
char gender;
//下面的参数用以对结构体内部变量进行赋值
studentInfo(int _id,char _gender)
{
id = _id;
gender = _gender;
}
//构造函数简化写法
//studentInfo(int _id,char _gender):id(_id),gender(_gender){}
};
studentInfo stu = studentInfo(10086,'M');
- 如果自己重新定义了构造函数,则不能不经初始化就定义结构体变量,因此加上“studentInfo(){}”就可以适用不同场合。
9 补充#
9.1 cin和cout#
cin#
- cin=c+in,采用输入运算符“>>”来进行输入,不需要写&,直接写变量名就可以
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
return 0;
}
- 读入一整行,使用getline函数
char str[100];
cin.getline(str,100);//把一整行都读入char型数组str[100]中
- string容器,使用如下方式输入
string str;
getline(cin,str);
cout#
- cout=c+out,使用输出运算符“<<”,输出中间没有空格,读者可以自己加入
cout<<n<<" "<<db<<" "<<c<<" "<<str;
cout<<n<<"haha"<<db<<"heihei"<<c<<"wawa"<<str;
- 换行:"\n"和endl
cout<<n<<"\n"<<db<<endl;
- 控制double型精度,要加上头文件#include
头文件
cout<<setiosflags(ios::fixed)<<setprecision(2)<<123.4567<<endl;
9.2 浮点数的比较#
-
计算机采用有限位二进制编码,经过大量计算后,浮点数在计算机中的存储并不精确,因此会影响比较操作的准确性,引入极小数eps来对这种误差进行修正。
-
eps取$10^{-8}$是一个合适的数字,因此将eps定义为常量1e-8
#include<stdio.h>
#include<math.h>
const double eps = 1e-8;
#define Equ(a,b) ((fabs((a)-(b)))<(eps)) // ==,想要使用不等于,用(!Equ(a,b))即可
#define More(a,b) ((fabs((a)-(b)))>(eps)) // > a-b大于eps
#define Less(a,b) (((a)-(b))<(-eps)) // < a-b小于eps
#define MoreEqu(a,b) (((a)-(b))>(-eps)) // >= a-b大于-eps
#define LessEqu(a,b) (((a)-(b))<(eps)) // <= a-b小于eps
- 圆周率π
const double Pi = acos(-1.0); //cos(π)=-1
-
由于精度问题,在经过大量运算后,可能一个变量中存储的0是个很小的负数。这时如果开根号,就会因为不在定义域内而出错。同样的问题还可能出现在asin(x)当x存放+1、acos(x)当存放-1时。这种情况需要用eps使变量保证在定义域内。
-
由于编译环境的原因,本应为0.00的变量在输出时会变成-0.00。这是编译环境的bug,只能把结果存放到字符串中,然后与-0.00进行比较,如果对比成功,则加上eps来修正0.00。
9.3 复杂度#
- 时间复杂度
-
时间复杂度是算法需要执行基本运算的次数所处的等级,基本运算是类似加减乘除这种计算机可以直接实现的运算,时间复杂度是评判算法时间效率的有效标准。
-
在时间复杂度中,高等级的幂次会覆盖低等级的幂次
-
当时间复杂度的常数比较大时,即便时间复杂度相同,其性能也会有较大差距
-
常数复杂度O(1)表示算法消耗的时间不随规模的增长而增长
-
O(1)<O(log n)<O(n)<O($n^2$)
-
对一般的OJ系统来说,一秒能承受的运算次数大概是****$107$****~****$108$
- 空间复杂度
-
如果消耗的最大数据空间是一个二维数组,那么该算法空间复杂度就是O($n^2$)
-
O(1)的空间复杂度是指算法消耗的空间不随数据规模的增大而增大
-
考虑到空间够用,因此常采用以空间换时间的策略
- 编码复杂度
- 编码复杂度是一个定性概念,并没有量化标准,如果代码量巨大,其编码复杂度就会非常大
10 黑盒测试#
黑盒测试:系统后台准备若干组输入数据,然后让提交的程序去运行这些数据,如果输出的结果与正确答案完全相同(字符串意义上的比较,那么就称通过了这道题的黑盒测试,否则会根据错误类型而返回不同的结果)。
10.1 单点测试#
-
单点测试:系统会判断每组数据的输出结果是否正确。
-
如果输出正确,那么对改组数据来说就通过了测试,并获得了这组数据的分值。在这种情况下。总得分等于通过的数据的分值之和。
-
PAT就采用了单点测试
-
从代码编写上来看,单点测试只需要按正常的逻辑执行一遍程序即可,是“一次性”的写法。
10.2 多点测试#
-
多点测试要求程序能一次运行所有数据,并要求所有输出结果都必须完全正确,才能算这道题正确。
-
大部分在线评测系统都采用了这种方式,这样可以严格考验代码是否严谨
-
三种不同的输入方式
- while...EOF型:题目没有给定输入的结束条件,默认读到文件末尾
while(scanf("%d",&n) != EOF)
{
...
}
//scanf函数的返回值为其成功读入的参数的个数,读入失败时会返回-1,使用EOF来代表-1
- 在黑框里手动触发EOF,可以按<Ctrl+Z>组合键,这时就会显示一个^Z,按<Enter>就可以结束当前while了
- 读入字符串,则有scanf("%s",str)与gets(str)两种方式
while(sanf("%s",str)!=EOF)
{
...
}
while(gets(str)!= NULL)
{
...
}
- while...break型:while...EOF类型的延伸
题目描述:当输入的两个a和b都为0时结束输入
#include<stdio.h>
int main()
{
int a,b;
while(scanf("%d%d",&a,&b)!= EOF)
{
if(a==0 && b==0)
break;
printf("%d\n",a+b);
}
return 0;
}
//代替while...break的简便写法
// while(scanf("%d%d",&a,&b),a||b)
- while(T--)型:题目给出循环次数,用一个变量T来存储,进行T次循环
-
三种输出类型
-
正常输出:每两组输出数据之间没有额外的空行,即输出数据是连续的多行
-
每组数据输出之后都额外加一个空行:在每组输出结束之后额外输出一个换行符\n即可
-
两组输出数据之间有一个空行,最后一组数据后面没有空行:当使用while(T—)时,只需要判断T是否已经减小到0来判断是否应当输出额外的行。
例如:输出一行N个整数,每两个整数之间用空格隔开,最后一个整数后面不允许加上空格
-
for(int i=0;i<N;i++)
{
printf("%d",a[i]);
if(i<N-1)
printf(" ");
else
print("\n");
}
-
在多点测试中,每一次循环都要重置一下变量和数组,否则在下一组数据来临的时候变量和数组的状态就不是初始状态了。例如sum=0的语句就必须放在while之内。
-
重置数组一般使用memset函数或fill函数。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现