【PAT】0. 入门基础
- cin和cout消耗的时间比scanf和printf多得多
- 不要在同一个程序中使用cout和printf
stdio.h
是标准输入输出库,在c++中可以使用等价写法cstdio
1. 基本数据类型
- 变量名的第一个字符必须是字母或下划线,除第一个字符之外的其他字符必须是字母、数字或下划线
- 区分大小写
基本数据类型分为整型、浮点型、字符型,C++中还包括布尔型
1.1 整型
- 一个整数占用
32bit
,即4Byte
,取值范围是 − 2 31 ∼ + ( 2 31 − 1 ) -2^{31} \sim +(2^{31}-1) −231∼+(231−1),绝对值在 1 0 9 10^9 109范围以内的整数都可以定义成int型,输出格式是%d
。 - 对长整型long long来说,一个整数占用64bit,即8bit,取值范围是 − 2 63 ∼ + ( 2 63 − 1 ) -2^{63} \sim +(2^{63}-1) −263∼+(263−1),如果题目要求的整数取值范围超过了2147483647(如 1 0 10 10^{10} 1010),就得用long long型来存储。
- 注意:如果long long型赋大于
(
2
31
−
1
)
(2^{31}-1)
(231−1)的初值,则需要在初值后面加上
LL
,否则会编译错误。 - 总结:题目要求 1 0 9 10^9 109以内或者说32位整数,就用int型来存放;如果是 1 0 18 10^{18} 1018以内或者说64位整数,就用long long型存放。
1.2 浮点型
- 单精度float,一个浮点数占用32bit,其中1bit作为符号位、8bit作为指数位、23bit作为尾数位,可以存放的浮点数的范围是 − 2 128 ∼ + 2 128 -2^{128} \sim +2^{128} −2128∼+2128,但其有效精度只有6~7位。
- 双精度double,一个浮点数占用64bit,可以存放的浮点数的范围是 − 2 1024 ∼ + 2 1024 -2^{1024} \sim +2^{1024} −21024∼+21024,其有效精度有15~16位。
%f
是float和double型的输出格式。- 总结:对于浮点型来说,不要使用float,碰到浮点型的数据都应该用double来存储。
1.3 字符型
- 小写字母比大写字母的ASCII码值大32
- C语言中字符常量(必须是单个字符)必须用单引号标注,以区分是作为字符变量还是字符常量出现
%c
是char型的输出格式- 在计算机内部,字符就是按ASCII码存储的,下面的输出结果是zju,因为’u’的ASCII码就是117,这里的117并没有加单引号。
char c1='z',c2='j',c3=117; printf("%c%c%C",c1,c2,c3);
\0
表示空字符NULL,其ASCII码值为0,注意\0
不是空格。- 字符串常量是由双引号标记的字符集,字符串常量可以作为初值赋给字符数组,并使用
%s
的格式输出。char str1[25] = "hello "; char str2[25] = "world"; printf("%s%s",str1,str2); //hello world
- 不能把字符串常量赋值给字符变量,如
char c="abcd"
的写法是错误的。
1.4 布尔型
- 布尔型在C++中可以直接使用,但在C语言中必须添加
stdbool.h
头文件才可以使用。 - 赋值时可以直接使用true或false进行赋值,或是使用整形常量进行赋值,只不过整形常量在赋值给布尔型变量时会自动转换为true(非零)或者false(零)。
- 注意:“非零”是包括正整数和负整数的,1和-1都会转换为true。但是对于计算机来说,true和false在存储时分别为1和0,因此如果使用
%d
输出bool型变量,则true和false会输出1和0.
1.5 强制类型转换
格式如下:
(新类型名)变量名
1.6 符号常量和const常量
符号常量即用一个标识符来替代常量,又称为“宏定义”或”宏替换“,格式如下,注意末尾不加分号
#define 标识符 常量
#define pi 3.14
另一种定义常量的方法是使用const,格式如下:
const 数据类型 变量名 = 常量
const double pi = 3.14
define除了可以定义常量外,其实可以定义任何语句或片段,如
define ADD(a,b) ((a)+(b))
可以直接使用ADD(a,b)来代替a+b的功能。注意必须加那么多括号,因为宏定义是直接将对应的部分替换,然后才能进行编译和运行。
1.7 typedef
typedef可以给复杂的数据类型起一个别名,这样在使用中可以用别名来代替原来的写法。如
typedef long long LL; //给long long起个别名LL
int main(){
LL a = 123456789012345LL, b = 234567890123456LL; //直接使用LL
printf("%lld\n",a+b);
}
1.8 运算符
- 对于除法运算符,当被除数跟除数都是整型时,并不会得到一个double浮点型的数,而是直接舍去小数部分(即向下取整)
i++
是先使用i再将i加1,而++i
是先将i加1再使用i。- 由于int型的上限为
(
2
31
−
1
)
(2^{31}-1)
(231−1),因此无穷大的数INF可以设置成
(1<<31)-1
(注意:必须加括号,因为位运算符的优先级没有算术运算级高)。但一般更常用的是 ( 2 30 − 1 ) (2^{30}-1) (230−1),因为他可以避免相加超过int的情况。注意:如果把 ( 2 30 − 1 ) (2^{30}-1) (230−1)写成二进制的形式就是0x3fffffff
,因此下面两式等价const int INF = (1 << 30) - 1; const int INF = 0x3fffffff;
2.输入输出
2.1 使用scanf输入
数据类型 | 格式符 | 举例 |
---|---|---|
int | %d | scanf("%d",&n); |
long long | %lld | scanf("%lld",&n); |
float | %f | scanf("%f",&fl); |
double | %lf | scanf("%lf",&db); |
char | %c | scanf("%c",&c); |
字符串(char数组) | %s | scanf("%s",str); |
- 数组名str前面并没有
&
取地址运算符,这是因为数组名称本身就代表了这个数组第一个元素的地址,所以不需要再加取地址运算符。 - 在scanf中,除了char数组整个输入的情况不加& 之外,其他变量类型都需要加&。
- scanf的双引号内的内容其实就是整个输入,只不过把数据换成它们对应的格式符并把变量的地址按次序写在后面而已。
- 如果要输入“3 4”这种用空格隔开的两个数字,两个
%d
之间可以不加空格:
原因是除了scanf("%d%d", &a,&b);
%c
外,scanf对其他格式符(如%d
)的输入是以空白符(即空格、换行、tab等)为结束标志的,因此除非使用%c
把空格按字符读入,其他情况都会自动跳过空格,另外,字符数组使用%s
读入的时候以空格跟换行为读入结束的标志。
那如果想用scanf()读入带有空格的字符数组该怎么办呢,格式符char str[10]; scanf("%s", str); //输入数据: abcd efg printf("%s",str); //输出结果: abcd
"%[]”
它的作用为扫描字符集合。scanf(“%[^c]”,str)
; 其中 “c” 是一个具体的字符常量(包括控制字符)。当输入字符串时,字符 “c” 将被当作当前输入的结束符char str[10]; scanf(“%[^\n]”,str); //只以换行为结束符,可以接受空格
- 强调一下scanf的
%c
格式是可以读入空格跟换行的,如下例:int a; char c, str[10]; scanf("%d%c%s", &a, &c, str); //输入数据:1 a bcd printf("a=%d,c=%c,str=%s", a, c, str); //输出结果:a=1,c= ,str=a
2.2 使用printf输出
- printf的双引号中的部分和scanf的用法是相同的,但是后面不像scanf那样需要给出变量地址,而是直接跟上变量名称。
- 常见数据类型对应的printf格式符只有一个和scanf不同,对于double类型的变量,其输出格式变成了
%f
,而在scanf中却是%lf
。 - 如果想输出%或\,则需要在前面再加一个%或\,如下例
printf("%%,\\");
下面介绍三种实用的输出格式
%md
可以使不足m位的int型变量以m位进行右对齐输出,其中高位用空格补齐;如果变量本身超过m位,则保持原样。%0md
和%md
唯一的不同点在于,当变量不足m位时,将再前面补足够数量的0而不是空格%.mf
可以让浮点数保留m位小数输出,这个保留使用的是精度的”四舍六入五成双“的规则(如果是四舍五入,那么需要用到后面介绍的round函数)int a = 123, b = 1234567; printf("%5d\n", a); printf("%05d\n", a); printf("%5d\n", b); printf("%05d\n", b); ----输出结果--- 123 1234567 00123 1234567
2.3 使用getchar和puthcar输入/输出字符
- getchar用来输入单个字符,putchar用来输出单个字符。
- getchar可以识别换行符
输入数据abcd,输出结果acd。而如果输入"ab",然后按<Enter>键,再输入’c’,再按<Enter>键,输出结果会是这样,因为c2实际存储的是换行符\nchar c1,c2,c3;\ c1 = getchar(); getchar(); c2 = getchar(); c3 = getchar(); putchar(c1); putchar(c2); putchar(c3);
a c
2.4 cin
-
添加头文件
"#include<iostream>"
和using namespace std;
才能使用。 -
cin采用输入运算符"
>>
"来进行输入,cin的输入不指定格式,也不需要加取地址运算符&,直接写变量名就可以,同时读入多个变量只需要往后面使用>>进行扩展即可。如下面的代码读入了int型变量n、double型变量db、char型变量c、char型数组stu[]cin >> n >> db >> c >> str;
-
如果想要读入一整行,需要使用getlilne函数,下面代码把一整行都读入char型数组str[100]中
char str[100]; cin.getline(str, 100);
-
如果是string容器,需要用下面方式输入
string str; getline(cin, str);
2.5 cout
-
cout使用方法与cin几乎一致,不过使用的时输出运算符
<<
,要注意输出时中间并没有加空格,因此可以在每个变量之前加上空格cout << n <<" "<< db <<" " << c <<" "<< str;
-
cout换行有两种方法:使用
\n
来进行换行或使用endl
来表示换行(endl是endline的缩写)cout << n <<"\n"<< db << endl;
-
如果想要控制double的精度,那么在输出之前加上一些东西并且要加上
#include<iomanip>
头文件。下面代码输出123.46cout << setiosflags(ios::fixed) << setprecision(2) << 123.4567 << endl;
-
考试不推荐使用cin和cout因为他们在输入/输出大量数据的情况下表现得非常糟糕。
2.6 sscanf与sprintf
sscanf(str,"%d",&n);
sprintf(str,"%d",n);
上面sscanf写法的作用是把字符数组str中的内容以"%d"
的格式写到n中(还是从左至右),示例如下
int n;
char str[100] = "123";
sscanf(str, "%d", &n);
printf("%d\n", n); //123
而sprinf写法的作用是把n以"%d"
的格式写到str字符数组中(还是从右至左)
int n = 233;
char str[100] ;
sprintf(str, "%d", n);
printf("%s\n", str); //233
下面代码使用sscanf将字符数组str中的内容按"%d:%lf,%s"
的格式写到三个变量中
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)
3. 数组
3.1 一维数组
- 数组大小必须是整数常量,不可以是变量
- 定义了长度为size的一维数组后,只能访问下标为0 ~ size-1的元素
- 一维数组的初始化需要给出用逗号隔开的从第一个元素开始的若干个元素的初值,并用大括号括住。后面未被赋初值的元素将会由不同的编译器内部实现的不同而被赋以不同的初值(可能是很大的随机数),而一般情况默认初值为0。
- 如果想给整个数组都赋值0,只需要把第一个元素赋为0,或者只用一个大括号来表示。更推荐使用
memset
函数int a[10] = {0}; int a[10] = {};
3.2 二维数组
- 定义为
int a[size1][size2]
的二维数组,其第一维的下标取值只能是0 ~ (size1 - 1),第二维的下标取值只能是0 ~ (size2 - 1) - 二维数组在初始化时被赋初值的元素之外的部分将被默认赋值为0
- 注意:如果数组大小较大(大概 1 0 6 10^6 106级别),则需要将其定义在主函数外面,否则会使程序异常退出,原因是函数内部申请的局部变量来自系统栈,允许申请的空间较小;而函数外部申请的全局变量来自静态存储区,允许申请空间较大。
3.3 字符数组
- 字符数组可以通过直接赋值字符串来初始化(仅限于初始化,程序其他位置不允许这样直接赋值整个字符串)。
char str[15] = "Good Story!";
- 在一维字符数组(或二维字符数组的第二维)的末尾都有一个空字符
\0
,以表示存放的字符串的结尾。空字符\0
在使用gets或scanf输入字符串时会自动添加在输入的字符串的后面,并占用一个字符位,而puts与printf就是通过识别\0作为字符串的结尾来输出的。 - 特别提醒1:int型数组的末尾不需要加\0,只有char型数组需要。还需要注意空格的ASCII码值为32,\0是空字符NULL
- 特别提醒2:如果不是使用scanf函数的
%s
格式或gets函数输入字符串(例如使用getchar),请一定要在输入的每个字符串后加入“\0”,否则printf和puts输出字符串会因无法识别字符串末尾而输出一大串乱码。
下面介绍字符数组的三种输入输出
(1) scanf输入,printf输出
scanf对字符类型有%c
和%s
两种格式(printf同理,下同),其中%c
用来输入单个字符,%s
用来输入一个字符串并存放在字符数组里。%c
格式能够识别空格跟换行并将其输入,而%s
通过空格或换行来识别一个字符串的结束
char str[10];
scanf("%s", str); //输入"TAT TAT TAT"
printf("%s", str);//输出TAT
(2) getchar输入,putchar输出
char str[5][5];
for(int i=0; i<3; i++){
for(int j=0; j<3; j++){
str[i][j] = getchar();
}
getchar();//为了把输入中每行末尾的换行符吸收掉
}
for(int i=0; i<3; i++){
for(int j=0; j<3; j++){
putchar(str[i][j]);
}
putchar('\n');
}
(3) gets输入,puts输出
- gets用来输入一行字符串(注意:gets识别换行符\n作为输入结束,因此scanf完一个整数后,如果要使用gets,需要先用getchar接受整数后的换行符),并将其存放于一维数组(或二维数组的一维中);
- puts用来输出一行字符串,即将一维数组(或二维数组的一维)在界面上输出,并紧跟一个换行。
3.4 memset对数组中每一个元素赋相同的值
给数组中每一个元素赋相同的值有两种方法:memset函数和fill函数。
memset函数的格式为
memset(数组名,值,sizeof(数组名));
使用memset需要在程序开头添加string.h头文件,只建议使用memset赋0或-1(true或false),因为memset使用的是按字节赋值,即对每个字节赋相同的值,这样组成int型的4个字节就会被赋成相同的值。如果要对数组赋其他数字(例如1),请使用fill函数(但memset的执行速度快)。
int a[5] = {1,2,3,4,5};
memset(a,0,sizzeof(a));
3.5 以数组作为函数参数
数组作为函数参数时,参数中数组的第一维不需要填写长度(如果是二维数组,那么第二维需要填写长度),实际调用时也只需要填写数组名。最重要的是,数组作为参数时,在函数中对数组元素的修改就等同于时对原数组元素的修改(这与普通的局部变量不同),示例如下:
void change(int a[], int b[][5]){
a[0] = 1;
a[1] = 3;
a[2] = 5;
b[0][0] = 1;
}
int main(){
int a[3] = {0};
int b[5][5] = {0};
change(a,b);
for(int i = 0; i<3; i++){
printf("%d\n", a[i]);
}
return 0;
}
虽然数组可以作为参数,但是不允许作为返回类型出现。如果想要返回数组,则只能用上面的方法,将想要返回的数组作为参数传入。
4. 指针
- 一个int型的变量的地址就是它占用的4Byte当中第一个字节的地址。
- 只要在变量前面加上
&
,就表示变量的地址。int a = 1; printf("%d, %d\n", &a, a);//输出变量的地址
- 指针是一个unsigned类型的整数,指针指向了内存地址(简单理解指针就是变量的地址)
4.1 指针变量
- 指针变量用来存放指针,在某种数据类型后加星号
*
表示这是一个指针变量,如int* p;double* p;char* p;
,星号*
的位置在数据类型之后或者是变量名之前都是可以的。 - 如果一次有好几个同种类型的指针变量要同时定义,星号只会结合于第一个变量名。
int* p1,p2; //只有p1时int*型的,而p2是int型 int* p1, *p2, *p3;//都是指针变量
- 给指针变量赋值的方式一般是把变量的地址取出来,然后赋给对应类型的指针变量。
注意int a; int* p = &a;
int*
是指针变量的类型,而后面的p才是变量名,用来存储地址,因此地址&a
是赋值给p
而不是*p
的。 *p
可以得到地址p所指的元素,可以把*
视为一把开启房间的钥匙,直接对*p
进行复制,也可以起到改变那个保存的元素的功能int a; int* p = &a; *p = 233; printf("%d, %d\n", *p, a); //233, 233
- 对一个
int*
型的指针变量p来说,p+1是指p所指的int型变量的下一个int型变量地址。这里的”下一个“是跨越了一整个int型(即4Byte) - 指针变量支持自增和自减操作,p++等同于p=p+1。
- 对于指针变量来说,把其存储的地址的类型称为基地址,例如定义为
int* p
的指针变量,int就是它的基类型。基类型必须和指针变量存储的地址类型相同,也就是说上面定义的指针变量p不能够存放double或char型数据的地址,而必须是int型数据的地址。
4.2 指针与数组
- 对int型数据a来说,数组a的首地址为
&a[0]
。 - C语言中数组名称也作为数组的首地址使用
int a[10] = {1}; int* p = a; //a == &a[0] printf("%d\n", *p); // 1
- 指着变量可以进行加减法,可以推出
a+i
等同于&a[i]
,即*(a+i)
等价于a[i]
。由此可以得到一种输入输出数组元素的新写法scanf("%d", a+i); printf("%d", *(a+i));
- 指针变量可以使用自增操作,可以这样枚举数组中的元素
int a[10] = {1,2,3,4,5,6,7,8,9,10} for(int* p = a; p < a + 10; p++){ printf("%d ", *p); }
- 指针变量可以进行加减法,减法的结果就是两个地址偏移的距离,这个距离以int为单位,即两个int型的指针相减等价于在求两个指针之间差了几个int。
4.3 使用指针变量作为函数参数
指针类型也可以作为函数参数的类型,这时视为把变量的地址传入函数。如果在函数中对这个地址的元素进行改变,原先的数据就会确实的被改变。
经典例子:使用指针作为参数,交换两个数:
下面这种写法做不到的两数交换的效果
void swap(int a, int b){
int temp = a;
a = b;
b = temp;
}
int main(){
int a = 1, b = 2;
swap(a,b);
printf("a = %d, b = %d\n", a, b);
return 0;
因为函数接收参数的过程中是单向一次性的值传递,在调用swap(a,b)时只是把a和b的值传进去了,这样相当于产生了一个副本,对这个副本的操作不会影响main函数中a、b的值,接下来介绍使用指针的方法。
指针变量存放的是地址,那么使用指针变量作为参数时传进来的也是地址。只有在获取地址的情况下对元素进行操作,才能真正的修改变量。
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;
}
下面有两种常见的错误写法:
- 错误写法一:
问题出在temp。在定义int*型的指针变量temp时,temp没有被初始化,也就是说,指针变量temp中存放的地址是随机的,如果该随即地址指向的是系统工作区间,那么就会出错(这样的概率特别大),解决方法是给temp赋初值。void swap(int* a, int* b){ int temp; *temp = *a; *a = *b; *b = *temp; }
void swap(int* a, int* b){ int x; int* temp = &x; *temp = *a; *a = *b; *b = *temp; }
- 错误写法二:
这种写法思想在于直接把两个地址交换,认为地址交换后元素就交换了,实际上main函数传给swap函数的”地址“实际上是一个”无符号整型“的数,其本身也跟普通变量一样只是“值传递”,swap函数对地址本身进行修改并不能对main函数里的地址修改,能够使main函数里的数据发生变化的只能是swap函数中对地址指向的数据进行的修改。void swap(int* a, int* b){ int *temp = a; a = b; b = temp; }
4.4 引用
- C++中的引用可以不使用指针,也能达到修改传入参数的目的。引用不产生副本,而是给原变量起了个别名,且对引用变量的操作就是对原变量的操作。
- 使用方法:在函数的参数类型后面加
&
(& 加在int后面或者变量名前面都可以),注意文件要保存为.cpp类型
在change函数的参数int x中加了&,在传入参数时对参数的修改就会对原变量进行修改。void change(int &x){ x = 1; } int main(){ int a = 10; change(a); printf("%d\n", a); return 0 }
- 注意:要把引用的&跟取地址运算符& 区分开来,引用并不是取地址的意思。
- 在5.3节的错误写法二中,试图将传入的地址交换来打到交换两个变量的效果,但是失败了,因为对指针变量本身的修改无法作用到原指针变量上。此处可以通过引用来实现上面的效果,示例如下:
因为指针变量其实是unsigned类型的整数,为了理解上的方便,可以”简单“地把void swap(int* &p1, int* &p2){ int *temp = p1; p1 = p2; p2 = 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; }
int*
型理解成unsigned int
型,而直接交换这样的两个整型变量是需要加引用的。
需要强调的是,由于引用是变量的别名,因此常量不可使用引用,上面代码中不可以写成swap(&a,&b),而必须用指针变量p1和p2存放&a和&b,然后把指针变量作为参数传入。
5. 结构体(struct)的使用
5.1 结构体的定义
定义结构体的基本格式如下
struct Name{
//一些基本的数据结构或者自定义的数据类型
};
例如,想储存一个学生的学号、性别、姓名和专业,可以这样定义:
struct studentInfo{
int id;
char gender;
char name[20];
char major[20];
}Alice,Bob,stu[1000];
其中studentInfo是这个结构体的类型名,内部分别定义了id(学号)、gender(性别)、name(姓名)和major(专业),这些就是单个学生的信息。大括号外定义了studentInfo型的Alice和Bob代表的两个结构体变量,stu[1000]就是当有很多学生时定义的一个结构体数组。
结构体变量和结构体数组还可以按照基本数据类型那样定义
studentInfo Alice;
studentInfo stu[1000];
注意:结构体里面能定义除了自己本身(会引起循环定义的问题)之外的任何数据类型,还可以定义自身类型的指针变量,如
struct node{
node n; //不能定义node型变量
node* next;//可以定义node*型指针变量
}
5.2 访问结构体内的元素
-
访问结构体内的元素有两种方法:”
.
“操作和”->
“操作struct studentInfo{ int id; char name[20]; studentInfo* next; // 指向下一个学生的地址 }stu, *p; //普通变量stu和指针变量p
访问stu中变量的写法
stu.id; stu.name; stu.next;
访问指针变量p中元素的写法(*p).id; (*p).name; (*p).next;
C语言中有一种访问结构体指针变量内元素的更简洁的写法p->id; p->name; p->next;
-
总结:结构体指针变量内元素的访问只需要使用"->"跟上要访问的元素即可,即
p->id
等价于(*p).id
5.3 结构体的初始化
-
最简单的,可以先定义一个studentInfo stu的结构体变量,然后对其中的元素逐一赋值,达到初始化的目的
-
或者在读入时赋值
scanf("%d %c", &stu.id, &stu.gender);
-
使用构造函数来初始化结构体,它的特点是不需要写返回类型且函数名与结构体相同。
对一个普通的结构体,其内部会生成一个默认的构造函数(但不可见)。由于这个构造函数的存在,才可以直接定义studentInfo类型的变量而不进行初始化(因为它没有让用户提供任何初始化参数)
struct studentInfo{ int id; char gender; studentInfo(){} //默认生成的构造函数 };
如果想手动提供id和gender的初始化参数,只要提供初始化参数来对结构体内的变量进行赋值即可
studentInfo(int _id, char _gender){ id = _id; gender = _gender; }
也可以简化为一行
studentInfo(int _id, char _gender): id(_id), gender(_gender) {}
注意:如果自己重新定义了构造函数,则不能不经初始化就定义结构体变量,也就是说默认生成的构造函数此时被覆盖了。只要参数个数和类型不完全相同,就可以定义任意多个构造函数,以适应不同的初始化场合
struct studentInfo{ int id; char gender; //用以不初始化就定义结构体变量 studentInfo(){} //只初始化gender studentInfo(char _gender){ gender = _gender; } //同时初始化id和gender studentInfo(int _id, char _gender){ id = _id; gender = _gender; } };
6. 补充
- 全局变量定义在其之后所有函数之前
- main函数返回0的意义在于告知系统程序正常终止。
6.1 常用math函数(位于math.h中)
fabs(double x)
用于对double变量取绝对值floor(double x),ceil(double x)
分别用于对double变量的向下取整和向上取整,返回类型为double型pow(double r, double p)
用于返回 r p r^p rp,要求r和p都是double型sqrt(double x)
用于返回double型变量的算术平方根log(double x)
用于返回double型变量的以自然对数为底的对数,C语言中没有对任意底数求对数的函数,因此必须使用换底公式来将不是以自然对数为底的对数转换为以e为底的对数,即 l o g a b = l o g e b / l o g e a log_ab=log_eb/log_ea logab=logeb/logeasin(double x),cos(double x),tan(double x)
:分别返回double型变量的正弦值、余弦值和正切值,参数要求是弧度制asin(double x),acos(double x),atan(double x)
:返回double型变量的反正弦值、反余弦值、反正切值- pi可以定义为精确值
acos(-1.0)
,因为cos(pi)=-1
- pi可以定义为精确值
round(double x)
:用于将double型变量x四舍五入,返回类型也是double型,需进行取整。
6.2 string.h头文件
-
strlen()
可以得到字符数组中第一个\0
前的字符的个数,格式如下:strlen(字符数组)
-
strcmp()
返回两个字符串大小的比较结果,比较原则是按字典序,格式如下:strcmp(字符数组1,字符数组2)
- 如果字符数组1 < 字符数组2,则返回一个负整数(不同编译器处理不同,不一定是-1)
- 如果字符数组1 == 字符数组2,则返回0
- 如果字符数组1 > 字符数组2,则返回一个正整数
-
strcpy()
可以把一个字符串复制给另一个字符串,其格式如下:strcmp(字符数组1,字符数组2)
- 注意:是把字符数组2复制给字符数组1,这里的”复制“包括了结束符\0。
-
strcat()
可以把一个字符串接到另一个字符串后面,格式如下strcat(字符数组1,字符数组2)
- 注意:是把字符串2接到字符串1后面
6.3 浮点数的比较
由于计算机采用有限位的二进制编码,因此浮点数在计算机中的存储并不总是精确的。例如在经过大量计算后,一个浮点型的输3.14在计算机中看你存储成3.1400000000001,这种情况会对比较操作带来极大的困扰(C/C++中的“==”操作时完全相同才判定true),于是需要引入一个极小数eps来对这种误差进行修正,经验表明,eps取 1 0 − 8 10^{-8} 10−8是一个合适的数字。
const double eps=1e-8;
const double Pi=acos(-1.0);
#define Equ(a,b) ((fabs((a)-(b)))<(eps))
#define More(a,b) (((a)-(b))>(eps))
#define Less(a,b) (((a)-(b))<(-eps))
#define MoreEqu(a,b) (((a)-(b))>(-eps))
#define LessEqu(a,b) (((a)-(b))<(eps))
7. 黑盒测试
7.1 多点测试的三种输入类型
- scanf函数的返回值为其成功读入的参数的个数。
- 在读取文件时到达文件末尾导致的无法读取现象,会产生读入失败。这个时候,scanf函数会返回-1而不是0,且C语言中使用EOF(即End Of File)来代表-1。
(1) while…EOF型
当题目没有说明有多少数据需要读入时,就可以利用scanf的返回值是否为EOF来判断输入是否结束。
while(scanf("%d", &n) != EOF){
...
}
上面代码含义是:只要scanf的返回值不为EOF(即文件中的数据没有读完),就反复读入n,执行while函数体的内容;当读入失败(到达文件末尾)时,结束while循环。
另外,在黑框里输入数据时,并不会触发EOF状态。因此如果想在黑框里面手动触发EOF,可以按<Ctrl+Z>
组合键,这时会显示一个^Z
,按<Enter>
键就可以结束while了。
如果读入字符串,则有scanf("%s",str)
与gets(str)
两种方式可用
while(scanf("%s",str) != EOF){
...
}
while(gets(str) != NULL){
...
}
(2) while…break型
这种类型是 while…EOF型的延伸,题目要求当输入的数据满足某个条件时停止输入。这种类型有两种写法
- 在while…EOF的内部进行判断,当满足退出条件时中断(break)当前循环
while(scanf("%d%d", &a, &b) != EOF){ if(a == 0 && b == 0) break; ... }
- 把退出条件的判断放到while语句中,令其与scanf用逗号隔开
上面循环条件的含义为,当a和b中有一个不为零时就进行循环(循环条件a||b的全写为a!=0||b!=0)while(scanf("%d%d", &a, &b) , a||b){ ... }
(3)while(T–)型
这种类型中,题目会给出测试数据的组数,然后才给出相应数量组数的输入数据。由于给定了测试数据的组数,因此需要用一个变量T来存储,并在程序开始时读入。
int T;
scanf("%d", &T);
while(T--){
...
}
7.2 三种常见的输出类型
(1) 正常输出
要求每两组输出数据中间没有额外的空行,即输出数据时连续的多行
(2) 每组数据输出之后都额外增加一个空行
只需要在每组输出结束之后额外输出一个换行符\n
即可。
(3) 两组输出数据之间有一个空行,最后一组数据后面没有空行
这一般是在第三种输入类型while(T--)
的情况下,只需要通过判断T是否已经减小到0来判断是否应当输出额外的换行。
while(T--){
...
if(T > 0) printf("\n");
}
与这种要求类似的要求是:输出一行N个整数,每两个整数之间用空格隔开,最后一个整数后面不允许加上空格。
for(int i = 0; i < N; i++){
printf("%d", a[i]);
if(i < N-1) printf("");
else printf("\n");
}
- 注意在多点测试中,每一次循环都要重置一下变量和数组,否则在下一组数组来临的时候变量和数组的状态就不是初始状态了,充值数组一般使用memset函数或fill函数
8. 进制转换
对一个P进制的数,如果要转换为Q进制,需要两步
①将P进制的数x转换为十进制y
int y = 0, product = 1; // product在循环中会不断乘P,得到1、P 、P^2、P^3…
while(x!=0){
y = y + (x % 10) * product; //x % 10为了每次获取x的个位数
x = x / 10; //去掉x的个位
product = product * P
}
如果数 x 是存放在数组S中,且 x 的高位存储在数组S的低位(顺序存储),num存放十进制的结果
int num = 0, len = strlen(S);
for(int i = 0; i < len; i++){
num = num * 26 + (S[i] - 'A'); //十进制转换为26进制
}
②将十进制数y转换为Q进制z
采用“除基取余法”。即每次将待转换数除以Q,然后将得到的余数作为低位存储,而商继续除以Q并进行上面的操作,最后当商为0时,将所有位从高到低输出就可以得到z
int z[40], num = 0; //数组z存放Q进制数y的每一位,num为位数
do{
z[num++] = y % Q; //除基取余
y = y / Q;
}while(y != 0)
这样数组z从高位z[num-1]到低位num[0]即为Q进制z。代码中使用do…while而不是while语句的原因是:如果十进制y恰好等于0,那么使用while语句将使循环直接跳出,导致结果出错(正确结果应该是数组z中存放了z[0] = 0)