C 语言入门/C 控制台 I/O
本章涵盖控制台(键盘/显示器)和文件 I/O。您已经看到了一个控制台 I/O 函数“printf()”,还有其他几个。C 语言对文件 I/O 有两种不同的方法,一种基于类似于控制台 I/O 的库函数,另一种使用“系统调用”。这些主题将在下面详细讨论。
通常情况下,控制台 I/O 指的是与计算机的键盘和显示器的通信。然而,在大多数现代操作系统中,键盘和显示器只是默认的输入和输出设备,用户可以轻松地将输入重定向到例如文件或其他程序,并将输出重定向到例如串行 I/O 端口。
type infile > myprog > com
程序本身“myprog”不知道其中的区别。该程序使用控制台 I/O 来简单地读取其“标准输入 (stdin)” - 这可能是键盘、文件转储或其他程序的输出 - 并在其“标准输出 (stdout)”上打印 - 这可能是显示器、打印机或其他程序或文件。程序本身既不知道也不在乎。
控制台 I/O 需要声明
#include <stdio.h>
有用的函数包括
printf() Print a formatted string to stdout. scanf() Read formatted data from stdin. putchar() Print a single character to stdout. getchar() Read a single character from stdin. puts() Print a string to stdout. gets() Read a line from stdin.
基于 Windows 的编译器还具有一个可选的控制台 I/O 函数库。这些函数需要声明
#include <conio.h>
三个最常用的 Windows 控制台 I/O 函数是
getch() Get a character from the keyboard (no need to press Enter). getche() Get a character from the keyboard and echo it. kbhit() Check to see if a key has been pressed.
正如之前解释的那样,“printf()”函数打印一个包含格式化数据的字符串
printf( "This is a test!\n" );
- 这可以包含变量的内容
printf( "Value1: %d Value2: %f\n", intval, floatval );
可用的格式代码是
%d decimal integer %ld long decimal integer %c character %s string %e floating-point number in exponential notation %f floating-point number in decimal notation %g use %e and %f, whichever is shorter %u unsigned decimal integer %o unsigned octal integer %x unsigned hex integer
对特定数据类型使用错误的格式代码会导致奇怪的输出。可以使用修饰符代码获得更多控制;例如,可以包含一个数字前缀来指定最小字段宽度
%10d
这指定了最小字段宽度为十个字符。如果字段宽度太小,将使用更宽的字段。添加一个减号
%-10d
- 会导致文本左对齐。
还可以指定一个数字精度
%6.3f
这指定了六个字符宽的字段中三位精度。
也可以指定一个字符串精度,以指示要打印的最大字符数。例如
/* prtint.c */ #include <stdio.h> int main(void) { printf( "<%d>\n", 336 ); printf( "<%2d>\n", 336 ); printf( "<%10d>\n", 336 ); printf( "<%-10d>\n", 336 ); return 0; }
这将打印
<336> <336> < 336> <336 >
类似地
/* prtfloat.c */ #include <stdio.h> int main(void) { printf( "<%f>\n", 1234.56 ); printf( "<%e>\n", 1234.56 ); printf( "<%4.2f>\n", 1234.56 ); printf( "<%3.1f>\n", 1234.56 ); printf( "<%10.3f>\n", 1234.56 ); printf( "<%10.3e>\n", 1234.56 ); return 0; }
- 打印
<1234.560000> <1.234560e+03> <1234.56> <1234.6> < 1234.560> < 1.234e+03>
最后
/* prtstr.c */ #include <stdio.h> int main(void) { printf( "<%2s>\n", "Barney must die!" ); printf( "<%22s>\n", "Barney must die!" ); printf( "<%22.5s>\n", "Barney must die!" ); printf( "<%-22.5s>\n", "Barney must die!" ); return 0; }
- 打印
<Barney must die!> < Barney must die!> < Barne> <Barne >
为了方便起见,第 2 章中列出的特殊字符表在此重复。这些字符可以嵌入到“printf”字符串中
'\a' alarm (beep) character '\p' backspace '\b' backspace '\f' formfeed '\n' newline '\r' carriage return '\t' horizontal tab '\v' vertical tab '\\' backslash '\?' question mark '\'' single quote '\"' double quote '%%' percentage '\0NN' character code in octal '\xNN' character code in hex '\0' null character
“scanf()”函数使用类似于“printf”的语法读取格式化数据,只是它需要指针作为参数,因为它必须返回值。例如
/* cscanf.c */ #include <stdio.h> int main(void) { int val; char name[256]; printf( "Enter your age and name.\n" ); scanf( "%d %s", &val, name ); printf( "Your name is: %s -- and your age is: %d\n", name, val ); return 0; }
由于字符串的名称本身就是一个指针,“name”前面没有“&”。输入字段由空格(空格、制表符或换行符)分隔,尽管可以包含一个计数,例如“%10d”,来定义特定的字段宽度。格式代码与“printf()”相同,除了
- 没有“%g”格式代码。
- “%f”和“%e”格式代码的工作方式相同。
- 有一个“%h”格式代码用于读取短整数。
如果格式代码中包含字符,“scanf()”将读取这些字符并丢弃它们。例如,如果上面的示例修改如下
scanf( "%d,%s", &val, name );
- 那么“scanf()”将假定两个输入值是逗号分隔的,并在遇到逗号时将其吞掉。
如果一个格式代码前面有一个星号,数据将被读取并丢弃。例如,如果将示例更改为
scanf( "%d%*c%s", &val, name );
- 那么如果两个字段由“:”分隔,该字符将被读取并丢弃。
当输入终止时,“scanf()”函数将返回 EOF(一个“int”,在“stdio.h”中定义)。
“putchar()”和“getchar()”函数处理单个字符 I/O。例如,以下程序一次接受来自标准输入的字符
/* inout.c */ #include <stdio.h> int main (void) { unsigned int ch; while ((ch = getchar()) != EOF) { putchar( ch ); } return 0; }
“getchar”函数返回一个“int”,并在 EOF 时终止。请注意,C 语言允许程序以一种简洁的方式获取值并在同一表达式中对其进行测试,这是处理循环的特别有用的特性。
关于单个字符 I/O 的一个警告:如果程序正在从键盘读取字符,大多数操作系统不会在用户按下“Enter”键之前将字符发送给程序,这意味着无法以这种方式执行单个字符键盘 I/O。
上面的简短程序是字符模式文本“过滤器”的核心,它是一个可以在标准输入和标准输出之间执行一些转换的程序。这样的过滤器可以用作构建更复杂应用程序的元素
type file.txt > filter1 | filter2 > outfile.txt
以下过滤器将输入中每个单词的首字母大写。该程序作为一个“状态机”运行,使用一个可以设置为不同值或“状态”的变量来控制其运行模式。它有两个状态:SEEK,它在寻找第一个字符,和 REPLACE,它在寻找一个单词的结尾。
在 SEEK 状态下,它扫描空格(空格、制表符或换行符),并回显字符。如果它找到一个打印字符,它将把它转换为大写,并进入 REPLACE 状态。在 REPLACE 状态下,它将字符转换为小写,直到遇到空格,然后返回 SEEK 状态。
该程序使用“tolower()”和“toupper()”函数进行大小写转换。这两个函数将在下一章中讨论。
/* caps.c */ #include <stdio.h> #include <ctype.h> #define SEEK 0 #define REPLACE 1 int main(void) { int ch, state = SEEK; while(( ch = getchar() ) != EOF ) { switch( state ) { case REPLACE: switch( ch ) { case ' ': case '\t': case '\n': state = SEEK; break; default: ch = tolower( ch ); break; } break; case SEEK: switch( ch ) { case ' ': case '\t': case '\n': break; default: ch = toupper( ch ); state = REPLACE; break; } } putchar( ch ); } return 0; }
“puts()”函数类似于简化的“printf()”,没有格式代码。它打印一个自动以换行符结尾的字符串
puts( "Hello world!" );
“fgets()”函数特别有用:它读取一个以换行符结尾的文本行。它对输入的限制比“scanf()”少得多
/* cgets.c */ #include <stdio.h> #include <string.h> #include <stdlib.h> int main(void) { char word[256], *guess = "blue\n"; int i, n = 0; puts( "Guess a color (use lower case please):" ); while( fgets(word, 256, stdin) != NULL ) { if( strcmp( word, guess ) == 0 ) { puts( "You win!" ); break; } else { puts( "No, try again." ); } } return 0; }
该程序包含“strcmp”函数,该函数执行字符串比较,并在匹配时返回 0。这个函数将在下一章中更详细地描述。
这些函数可以用来实现对文本行而不是字符进行操作的过滤器。以下是对这些过滤器的核心程序
/* lfilter.c */ #include <stdio.h> int main (void) { char b[256]; while (( fgets(b, 256, stdin) ) != NULL ) { puts( b ); } return 0; }
当输入终止或出错时,“fgets()”函数返回“stdio.h”中定义的 NULL。
基于 Windows 的控制台 I/O 函数“getch()”和“getche()”的工作方式与“getchar()”非常类似,只是“getche()”会自动回显字符。
“kbhit()”函数非常不同,因为它只指示是否按下了键。如果按下了键,它将返回一个非零值,如果没有按下,它将返回零。这允许程序轮询键盘以获取输入,而不是挂起键盘输入并等待事件发生。如前所述,这些函数需要“conio.h”头文件,而不是“stdio.h”头文件。