***其他输入输出函数***
首先,C语言输入输出的对象,主要就是标准输出流(stdout)与标准输入流(stdin)。所谓标准输出流通俗地说就是屏幕输出,标准输入流就是键盘输入。除了这两种之外,C语言还可以对磁盘文件和外部设备输入输出。
磁盘输入输出函数
类似于标准输入输出函数,文件输入输出函数包括:
a)单个字符输入输出函数:fgetchar与fputchar(或者getc与putc);
b)单行字符串输入输出函数:fgets与fputs;
c)格式化输入输出函数:fscanf与fprintf;
[例3-12]测试文件输出功能 #include <stdio.h> int main( ) { char s[80]; FILE* pf = fopen("./1.txt", "w"); puts("请输入一行文字:"); //fputs("请输入一行文字:", stdout); scanf("%s", s); //fgets(s, sizeof(s), stdin); fputs(s, pf); fclose(pf); return 0; } |
将以上代码输入到C语言编译器中进行编译、运行并查看打印结果:
a)运行之后根据提示输入一行文字后回车,查看执行文件或者工程目录下是否生成了一个“1.txt”文件;
b)用记事本或其他文本工具打“1.txt”文件,观察fputs和fclose哪个函数执行之后文件的内容发生变化;
c)使用注释中的fputs替换puts函数,使用fgets替换scanf函数,思考两种函数的等价关系。
[例3-13]测试文件输入功能 #include <stdio.h> int main() { int c; FILE* pf = fopen("./1.txt", "r"); if (!pf) { puts("打开文件失败!"); return -1; } while (1) { c = getc(pf); if (c == -1) break; if (c == '\r') continue; putchar(c); //putc(c,stdout); } fclose(pf); return 0; } //linux文本文件的换行符是:\n //windows文本文件的换行符是:\r\n |
将以上代码输入到C语言编译器中进行编译、运行并查看打印结果:
a)运行之前先要拷贝一个1.txt文件(含有多行文字内容),到执行文件所在目录下或者工程目录下;
b)运行之后观察程序输出内容,与原文本文件中的内容是否完全一致;
c)使用注释中的fputc替换putchar函数,思考两种函数的等价关系。
d)最好能在调试模式下单步执行,在监视器内观察getc函数执行后变量的变化,尤其是含有中文的文件。
...........................................................................................................................................................
总结:本节对磁盘文件的读写只做初步认识,更多磁盘文件操作编程,参见后续章节“C语言文件”。在C语言中,把标准输出流定义为一个文件指针stdout,把标准输入流也定义为一个文件指针stdin。因此,就产生了以下的文件输入输出函数的一些等价关系:
a)单个字符输入输出函数:
getchar()与fgetchar(stdin)以及getc(stdin)三者是等价的;
putchar(c)与fputchar(c, stdout)以及putc(c,stdout)三者是等价的;
b)单行字符串输入输出函数:
gets(s)与fgets(s,sizeof(s),stdin)两者是等价的;
puts(s)与fputs(s,stdout)两者是等价的;
c)格式化输入输出函数:
scanf(x,…)与fscanf(stdin,x,…)是等价的;
printf(x,…)与fprintf(stdout,x,…)是等价的;
VS2015安全版本输入输出函数
微软的编译器从VS2005和VS2008开始,对关于字符串输入的相关函数增加了安全版本。例如:scanf_s和gets_s等。这些函数只限于在微软的各个VS版本中使用,目的是增加字符串输入时的安全性,防止用户录入的字符串过长破坏缓冲区意外的内存数据。
在VS2015编译器中,当代码中出现scanf、gets和fopen等函数时,(编译时报错)被强制使用安全版本。错误提示的内容是:“error C4996: 'fopen': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details”。
解决这个问题有两种办法:
a)在代码的最上方增加一行代码,取消安全版本的强制警告:#define _CRT_SECURE_NO_WARNINGS;
b)使用安全版本的函数替代非安全版本函数,例如:scanf_s、gets_s以及fopen_s等。
[例3-14]测试非安全版本函数 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int nNumb = 0; char sName[20]; char cSex = 0; double fSala = 0.0; puts("请输入一个员工的工号、姓名、性别、工资【性别(m代表男、f代表女)】:"); scanf("%d%s %c%lf", &nNumb, sName,&cSex,&fSala); //注意在scanf函数的%c之前要加一个空格 printf("%-6s%10s%6s%10s\n", "工号", "姓名", "性别", "工资"); printf("%-6d%10s%6c%10.2lf\n",nNumb,sName,cSex,fSala); return 0; } |
将以上代码输入到C语言编译器中进行编译、运行并查看打印结果:
a)运行之后输入一组数据(例如:1041 王五 m 5802.51),观察输入之后打印的结果是否正常;
b)去掉%c之前的空格重新编译和运行,观察输入数值后打印的结果是否正常;
c)最好能在调试模式下单步执行,在监视器内观察scanf函数执行后,几个变量内的数据变化情况。
去掉最上面安全警告这行(#define _CRT_SECURE_NO_WARNINGS),并且把代码中的scanf和printf函数都改为安全版本的scanf_s和printf_s函数。编译后出现大量警告,例如:
warning C4477: “scanf_s”: 格式字符串“%s”需要类型“unsigned int”的参数,但可变参数 3 拥有了……;
warning C4477: “scanf_s”: 格式字符串“%c”需要类型“char *”的参数,但可变参数 4 拥有了类型“……”;
warning C4473: “scanf_s”: 没有为格式字符串传递足够的参数;
note: 占位符和其参数预计 6 可变参数,但提供的却是 4 参数……
这些编译器的警告信息描述的问题都是非常准确的。安全版本的输入函数scanf_s,每一处的“%s”或“%c”控制符在参数列表中都要配备两个参数。目的主要是为了防止用户对字符串或字符输入时,超出参数可接受的输入字符的数量。
[例3-15]测试测试安全版本函数 //去除 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int nNumb = 0; char sName[20]; char cSex = 0; double fSala = 0.0; puts("请输入一个员工的工号、姓名、性别、工资【性别(m代表男、f代表女)】:"); scanf_s("%d%s %c%lf", &nNumb, sName,sizeof(sName),&cSex,sizeof(cSex),&fSala); //注意在scanf_s函数的%c之前要加一个空格 printf_s("%-6s%10s%6s%10s\n", "工号", "姓名", "性别", "工资"); printf_s("%-6d%10s%6c%10.2lf\n",nNumb,sName,cSex,fSala); return 0; } |
将以上代码输入到C语言编译器中进行编译、运行并查看打印结果:
a)运行之后输入一组数据(例如:1041 王五 m 5802.51),观察输入之后打印的结果是否正常;
b)去掉%c之前的空格重新编译和运行,观察输入数值后打印的结果是否正常;
c)最好能在调试模式下单步执行,在监视器内观察scanf_s函数执行后,几个变量内的数据变化情况。
d)思考调用printf_s时是否需要像scanf_s那样,为控制符“%s”与“%c”在参数列表中配备两个参数。
getchar失效问题
在初学C语言时经常会遇到这样的问题,在scanf或gets函数执行之后,再执行getchar函数就会被跳过。为什么会出现这种情况呢?
[例3-16]测试getchar函数失效问题 #include <stdio.h> int main() { int nNumb = 0; char sName[20]; char cSex = 0; puts("请输入一个员工的工号、姓名:"); scanf_s("%d%s", &nNumb, sName,sizeof(sName)); printf("请输入该员工的性别(m代表男、f代表女):"); cSex = getchar(); printf_s("%-6s%10s%6s\n", "工号", "姓名", "性别"); printf_s("%-6d%10s%6c\n",nNumb,sName,cSex); return 0; } |
将以上代码输入到C语言编译器中进行编译、运行并查看打印结果:
a)运行之后输入一组数据(例如:1041 王五),观察输入之后程序的运行过程是否正常;
b)最好能在调试模式下单步执行,观察scanf_s函数执行后getchar函数是否正常执行;
c)将getchar函数放在scanf_s函数之前执行,观察getchar函数是否能正常执行。
...........................................................................................................................................................
结论:当scanf、gets以及getchar各种输入函数执行之后,标准输入缓冲区内总是残留了“\n”回车符。如果没有及时清理的情况下,调用getchar或scanf(“%c”…)等单字符输入函数时,得到的字符总是缓冲区内残留的回车符或空格等。
解决getchar失效问题,有两种办法:
a)在调用单字符输入函数getchar或者scanf("%c "…)之前,执行fflush函数清理一下输入缓冲区;
b)使用scanf("\n%c"…)代替getchar函数,可以很好地解决单字符输入失效的问题。
[例3-17]解决getchar函数失效问题 #include <stdio.h> int main() { int nNumb = 0; char sName[20]; char cSex = 0; puts("请输入一个员工的工号、姓名:"); scanf_s("%d%s", &nNumb, sName,sizeof(sName)); printf("请输入该员工的性别(m代表男、f代表女):"); fflush(stdin); cSex = getchar(); printf_s("%-6s%10s%6s\n", "工号", "姓名", "性别"); printf_s("%-6d%10s%6c\n",nNumb,sName,cSex); return 0; } |
将以上代码输入到C语言编译器中进行编译、运行并查看打印结果:
a)运行之后输入一组数据(例如:1041 王五),观察输入之后程序的运行过程是否正常;
b)最好能在调试模式下单步执行,观察scanf_s函数执行后getchar函数是否正常执行;
c)将getchar函数放在scanf_s函数之前执行,观察getchar函数是否能正常执行。
在某些编译器中调用fflush函数之后,getchar函数或scanf("%c",…)等单字节输入函数仍然还仍然失效。
[例3-18]解决getchar函数失效问题 #include <stdio.h> int main() { int nNumb = 0; char sName[20]; char cSex = 0; puts("请输入一个员工的工号、姓名:"); scanf_s("%d%s", &nNumb, sName,sizeof(sName)); printf("请输入该员工的性别(m代表男、f代表女):"); scanf_s("\n%c", &cSex); printf_s("%-6s%10s%6s\n", "工号", "姓名", "性别"); printf_s("%-6d%10s%6c\n",nNumb,sName,cSex); return 0; } |
将以上代码输入到C语言编译器中进行编译、运行并查看打印结果:
a)运行之后输入一组数据(例如:1038 赵六),观察输入之后程序的运行过程是否正常;
b)最好能在调试模式下单步执行,观察前两项数据输入之后性别输入过程是否正常执行;
c)将性别输入函数放在前两项数据输入之前执行,观察单字符输入函数scanf("\n%c"…)是否能正常执行。
无回显的输入函数
什么是无回显的输入函数?有些DOS软件的菜单“1、……2、……”,当键盘刚一触碰就立即显示了结果。而不像getchar函数那样,可以输入几个字符之后再回车。无回显输入函数不是C语言标准函数,很多编译器不提供这类功能的函数。在VS2015中只要包含conio.h的头文件,就可以调用_getchar函数,实现无回显的输入功能。
无回显输入函数常用于软件菜单的选项输入,或者用户登录时的密码输入。
[例3-19]测试无回显的输入函数 #include <stdio.h> #include <conio.h> int main() { puts("请输入密码(回车):"); while (1) { char ch = _getch(); if (ch == '\n' || ch == '\r') break; putchar('*'); } puts(""); return 0; } |
将以上代码输入到C语言编译器中进行编译、运行并查看打印结果:
a)运行之后输入输入一串密码,观察每个字符输入之后是否都被显示为*符号;
b)最好能在调试模式下单步执行,观察_getch函数返回的单个字符是否与输入的字符一致;
c)思考如何将输入的密码记录到一个字符串数组内,并且用于之后的密码验证程序。
在Linux操作系统中,gcc编译器没有提供无回显的输入函数。因此我们需要自制无回显的输入函数。
[例3-20]Linux操作系统中自制无回显输入函数 #include <stdlib.h> #include <stdio.h> #include <termios.h> #include <string.h> static struct termios stored_settings1; static struct termios stored_settings2; void echo_off(void) { struct termios new_settings; tcgetattr(0, &stored_settings1); new_settings = stored_settings1; new_settings.c_lflag &= (~ECHO); tcsetattr(0, TCSANOW, &new_settings); return; } void echo_on(void) { tcsetattr(0, TCSANOW, &stored_settings1); return; } void set_keypress(void) { struct termios new_settings; tcgetattr(0, &stored_settings2); new_settings = stored_settings2; /*Disable canornical mode, and set buffer size to 1 byte */ new_settings.c_lflag &= (~ICANON); new_settings.c_cc[VTIME] = 0; new_settings.c_cc[VMIN] = 1; tcsetattr(0, TCSANOW, &new_settings); return; } void reset_keypress(void) { tcsetattr(0, TCSANOW, &stored_settings2); return; } int _getch() { echo_off(); set_keypress(); int nRet = getchar(); reset_keypress(); echo_on(); return nRet; } int main() { puts("Please input password(Enter):"); char ch; while ((ch = _getch()) != '\n') putchar('*'); puts(""); return 0; } |
将以上代码输入到Linux操作系统中,使用gcc编译器进行编译、运行并查看打印结果:
a)运行之后输入输入一串密码,观察每个字符输入之后是否都被显示为*符号;
b)最好能在调试模式下单步执行,观察_getch函数返回的单个字符是否与输入的字符一致;
接下来小结一下:
一、单字符输入输出:
getchar函数: c=getchar();等价于scanf("%c",&c) ;
putchar函数:putchar(c);等价于printf("%c",c);
getc函数:c=getchar();等价于getc(stdin);
putc函数:putchar(c);等价于putc(c,stdout);
二、单行输入输出:
gets函数:gets(str);等价于scanf("%s",str);
puts函数:puts(str);等价于printf("%s\n",str);
三、格式化输入输出:
printf包括:%d、%u、%x、%c、%s等;
scanf包括: %d、%u、%x、%c、%s等;
四、占位控制:
对于整数和字符串在%和控制符之间的数字代表占位,负号代表左对齐;
对于浮点数除了用数字代表占位,负号代表左对齐,还有小数位数的管理。
BY 白巾-子木