关于C语言程序设计中输入输出方式的总结
本文主要面向正在准备西安交通大学程序设计基础课程期末考试的同学
零、概念解释
空白字符
空白字符指空格、回车等图像为空白的字符。
格式占位符(仅包括考试需要用的)
- %d 读入一个整数(int类型)
- %lld 读入一个长整数(long long类型,当程序运行过程中有数字大小超过\(10^9\)时推荐使用,当数字大小超过\(2^{31}-1=2147483647\)时必须使用)
- %f 读入一个浮点数(float类型,精度较低,考试时不建议使用)
- %lf 读入一个双精度浮点数(double类型,有效数字位数比float类型多)
- %c 读入一个字符
- %s 读入一个字符串(遇空白字符结束)
一、输入
最常用的输入函数是:scanf()
,由于该函数可以满足期末考试时所有(我见过的)题目的输入要求,故本文不介绍其他输入函数。
有些同学可能听说或使用过
scanf_s()
这个函数,但此函数在考试时没有使用的必要,故本文不展开讨论。
读入单个数据(不包括字符串)
- 定义一个与待读入数据类型相符的变量,如读入整数时应该定义一个int类型变量(
int a;
)。 - 使用语句
scanf("%x",&a);
进行读入,其中%x
应更换为合适的格式占位符,a
应更换为存储该数据的变量。
例:读入一个浮点数,存储到变量x中
double x;
scanf("%lf",&x);
读入多个数据(不包括字符、字符串)
常用方法
- 数据数量较少且固定时,可以使用一个
scanf
语句读入,例如scanf("%d%d%d",&a,&b,&c);
。 - 数据数量较多,或数量同样需要读入时,可以使用for循环语句读入,例如
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
。 - 前两种方法也可以同时使用,如读入\(n\)个三角形的边长时,
for(int i=1;i<=n;i++) scanf("%d%d%d",a[i],b[i],c[i]);
。 - 当数量未知,要求读到某个特殊数据(如-1)为止时,可以使用while循环语句读入,例如
scanf("%d",&x); while(x!=-1) scanf("%d",&x);
。 - 当数量未知,要求读完整个输入文件的数据时,需要借助
scanf
函数返回值来实现(后面会写)。
scanf
函数需要的参数
一般格式为scanf("xxxxxx",&a,&b,&c);
,其中a
,b
,c
只需按输入顺序替换为相应变量即可。
当待读入数据间只由空格、回车隔开时,xxxxxx
直接替换为待读入数据相应的格式占位符即可(不要在格式占位符之间加空格、回车)。
例如想要读入这样的三个数据:
3 4.5
6
代码可以写成scanf("%d%lf%d",&n,&x,&m)
。
当待读入数据间有逗号或其他字符时,xxxxxx
中格式占位符间也要有完全对应的逗号或其他字符(但仍然应该注意不要有空格、回车)。
例如想要读入这样的三个数据:
3.14,1
2.72
代码可以写成scanf("%lf,%d%lf",&pi,&n,&e);
。
读入字符
使用scanf("%c",&x)
即可读入字符,但实际应用时会遇到各种各样的问题。
本节无法覆盖所有考试中可能出现的情况,但会尽可能使读者理解scanf
读入字符的原理,以便读者拥有在考场上改正bug的能力。
读入字符与读入其他类型数据最大的不同是不会忽略空白字符,于是常常出现这种情况:
要求:读入两个用空格隔开的小写字母,并输出(用逗号隔开)。
样例输入:a b
样例输出:a,b
错误代码:
#include<stdio.h>
int main(){
char a,b;
scanf("%c%c",&a,&b);
printf("%c,%c",a,b);
return 0;
}
如上代码的输出为:a,
。此代码的错误就是将两个字母之间的空格读入到了b
中,正确代码是scanf("%c %c",&a,&b);
。
更具有通用性的写法是
scanf("%c",&a);
scanf("%*c");
scanf("%c",&b);
其中,%*c
是%c
的变体,它同样可以用来读入一个字符,但被读入的字符不会被存入任何变量中。也就是说,前面的代码相当于scanf("%c%*c%c",&a,&b);
与前一个错误类似,但更常见的场景是:
要求:读入n个字母,倒序输出
样例输入:
5
abcde
样例输出:
edcba
错误代码:
#include<stdio.h>
int main(){
int n;
char c[100];
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%c",&c[i]);
for(int i=n;i>=1;i--)
printf("%c",c[i]);
return 0;
}
如上代码的输出为:
dcba
此代码的错误是将5
和a
之间的回车读入到了c[1]
中,只需添加一个scanf("%*c");
把回车提前读入即可。完整代码:
#include<stdio.h>
int main(){
int n;
char c[100];
scanf("%d",&n);
scanf("%*c");
for(int i=1;i<=n;i++)
scanf("%c",&c[i]);
for(int i=n;i>=1;i--)
printf("%c",c[i]);
return 0;
}
读入字符串
C语言中使用一维字符数组储存字符串,并要求必须用'\0'来标记字符串的结尾('\0'是不可见字符,与空白字符不同,更不是空格),例如,字符串"abcd"
相当于一维字符数组{'a','b','c','d','\0'}
。
读入字符串的格式为scanf("%s",a)
,其中a
是一维字符数组的数组名。例如,
char a[100];
scanf("%s",a);
char b[100][100];
scanf("%s",b[3]);
其中,b
是二维数组,故b[3]
是一维字符数组。
值得注意的是,读入字符串时,一维字符数组名前不需要加&
,这与其他所有数据类型都不同。
指针相关内容掌握较好的同学会知道
scanf
需要的参数是变量的地址,故一般变量前需要添加取地址符&
以得到其地址,而数组名本身就是地址,故不再添加取地址符。
不太了解指针相关概念的同学可以不关心具体原理,只需要牢记读入字符串时不加取地址符&
即可。
缓冲区是学习读入字符串时需要理解的重要概念。当运行scanf
函数时,程序并不直接与键盘交互,而是从缓冲区中读入数据;同样地,当用户敲击键盘时,数据也不直接传给scanf
函数,而是存入缓冲区。用户输入的数据会存入缓冲区的末尾,而scanf
会从缓冲区的开头读取数据,如果scanf
发现缓冲区中没有数据,则会等待用户像缓冲区中传入数据。
在moodle等平台的在线评测中,缓冲区中的元素不来自于键盘,而是来自于文件。其本质上和键盘输入类似,但为了防止scanf
在读完整个文件后等待用户像缓冲区传入新数据,文件会像缓冲区中传入一个文件结束符EOF(End Of File)。
直接使用scanf("%s",a)
读入
scanf
会首先删除掉缓冲区开头所有的空白字符,直到遇到第一个非空白字符时开始读入字符串,再次遇到空白字符时停止读入,并将此空白字符留在缓冲区。
此方法适用于字符串中间没有空格等空白字符的情况。
下面是一种可能出现的错误:
要求:读入一个字符串和一个字符
样例输入:abcd a
错误代码:scanf("%s%c",a,&b);
此代码相当于scanf("%s",a); scanf("%c",&b);
,读入字符串时,scanf
遇到字符串和字符之间的空格停止读入,但把空格留在了缓冲区。于是读入字符时会读到空格,而不是后面的字母。可以改为`scanf("%s%*c%c",a,&b);
使用scanf("%[^\n]",a)
读入
scanf("%[xxxxxx]",a)
被称作scanf
的正则用法,而方括号内的^\n
表示遇到\n
(换行符)就停止读入,并将换行符留在缓冲区。
有关正则表达式的更多资料可以自行百度,只需记住
%[^\n]
就足够应付考试了
此方法用于读入一整行数据作为一个字符串。
下面是一种常见错误:
要求:读入n行字符串
样例输入:
5
abcde
abc123
liuyike
lalala
icpc+q526350936
错误代码:
#include<stdio.h>
int main(){
int n;
char s[100][100];
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%[^\n]",s[i]);
}
return 0;
}
由于5
和abcde
之间有一个换行符,故读入第一行字符串时,scanf
遇到换行符立即终止,并仍把换行符留在缓冲区。此后的每个scanf
都会在最开始就遇到这个换行符并立刻终止。
正确代码应该为
#include<stdio.h>
int main(){
int n;
char s[100][100];
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%*c%[^\n]",s[i]);
}
return 0;
}
在每次读取字符串前,把多余的换行符从缓冲区读走。
scanf
函数的返回值
scanf
函数的返回值为int类型。如果成功读入,scanf
返回成功读入数据的个数;如果到达文件末尾,则返回 EOF。这里,EOF相当于int类型数据“-1”。
大多数时候,我们都不会用到scanf
函数的返回值,但当我们想要读入整个文件的数据并且无法预知文件中数据个数时,就可以借助其返回值来实现。
要求:输入若干个整数,输出它们的和
样例输入:1 1 4 5 1 4 19 19 810
样例输出:864
正确代码:
#include<stdio.h>
int main(){
int sum=0,x;
while(scanf("%d",&x)!=-1){
sum+=x;
}
printf("%d",sum);
return 0;
}
或者可以写成另一种便于理解的方式:
#include<stdio.h>
int main(){
int sum=0,x;
int res=scanf("%d",&x);
while(res!=-1){//res!=-1说明没有遇到文件末尾
sum+=x;
res=scanf("%d",&x);
}
printf("%d",sum);
return 0;
}
当提交moodle等在线平台评测时,scanf
读到文件末尾时返回-1
,停止读入。
当用户在自己电脑上测试代码时,如果不特别把输入方式更改为文件输入,会发现程序无法终止,这是因为键盘输入时并不会自动向缓冲区中添加文件结束标志,可以通过按住Ctrl+C
来手动输入文件结束标志。
二、输出
printf()
是最常用的输出函数,该函数可以满足考试中所有输出要求,故本文不再介绍其他输出函数。
简单文本的输出
将想要输出的字符串作为printf
的(唯一的)参数即可直接输出该字符串,例如printf("Hello, world!")
即可输出Hello, world!
。
但此方法并不总是行得通,例如想要输出
Hi!
Hello!
时,如果写
printf("Hi!
Hello!");
会发现,换行将字符串分开了,而C语言并不允许有这样跨行的字符串(而且很明显这使代码很不美观)。
使用转义字符即可解决该问题:printf("Hi!\nHello!")
。转义字符的相关知识应该在学习字符串或字符型变量时作为重点,与输入输出的关系不大,故不多赘述。
输出变量
如果想要输出"Today is Thursday, v me 50"
,其中"Thursday"
和50
是两个变量,当变量取其他值的时候输出可能会变为"Today is Sunday, v me 500"
,这时,前面提到的方法就无法根据变量的不同取值输出不同的内容。
想要达成此效果,只需把两个变量对应的格式占位符放在字符串里相应位置(即"Today is %s, v me %d"
),并按顺序把两个变量放在该字符串后作为printf
的参数(用逗号隔开)即可(与scanf
不同的是,变量前不需要加&
)。代码:printf("Today is %s, v me %d",s,x)
。
更多时候,我们可能无须把变量放在一个字符串中输出,而是输出单独的一个变量,此时字符串可以只含有格式占位符而不含其他普通字符,例如printf("%d",a);
改变输出变量的格式
只需对格式占位符进行更改即可。
固定宽度
- 右对齐,左边用空格补齐
在%
后面加一个整数表示总宽度,例如%5d,%3lf,%11d
。 - 右对齐,左边用0补齐
在%
后面加0
再加一个整数表示总宽度,例如%05d,%03lf,%011d
。 - 左对齐,右边用空格补齐
在右对齐的基础上,%
后面加-
,例如%-5d
。
浮点数的精度
在%
后面加.
再加一个整数表示保留小数点后的位数,例如%.6lf,%.2f
。
如果要同时控制输出宽度和精度,应该先写宽度再写精度,例如%5.2lf
表示保留两位小数,总宽度为5,右对齐。
三、其他易错点
读入前不要输出题目没有要求的任何字符
使用scanf函数时,每个待读入的变量前加&
,字符串前不加&
;每个变量之间用逗号隔开。
正确代码:
int a;
char s[100];
scanf("%d",&a);
scanf("%s",s);
错误代码:
scanf("%d" a);
scanf("%d" &a);
scanf("%d"&a);
scanf("%d",a);
scanf("%d",&a)
scanf("%s",&s);