C语言入门小册子
控制台和文件I/O
这一章涵盖了控制台(键盘/显示器)和文件输入输出(I/O)。你已经见过一个控制台I/O函数——printf(),还有其他一些类似的函数。C语言有两种独立的文件I/O方式,一种基于类似控制台I/O的库函数,另一种使用“系统调用”。这些话题将在下文详细讨论。
控制台I/O
控制台I/O通常指的是与计算机的键盘和显示器进行通信。然而,在大多数现代操作系统中,键盘和显示器只是默认的输入和输出设备,用户可以轻松地将输入重定向到文件或其他程序,输出重定向到例如串口I/O端口:
type infile > myprog > com
程序本身,“myprog”,并不会知道输入输出的区别。程序使用控制台I/O来读取它的“标准输入(stdin)”,这可能是键盘输入、文件内容,或其他程序的输出,并打印到它的“标准输出(stdout)”,这可能是显示器、打印机、另一个程序或文件。程序本身并不关心或知道具体的输入输出设备。
控制台I/O函数
控制台I/O要求声明:
#include <stdio.h>
常用的函数包括:
printf(): 将格式化的字符串打印到标准输出。scanf(): 从标准输入读取格式化的数据。putchar(): 将单个字符打印到标准输出。getchar(): 从标准输入读取单个字符。puts(): 打印字符串到标准输出。gets(): 从标准输入读取一行。
在Windows系统的编译器中,还可以使用一组替代的控制台I/O函数。此时需要声明:
#include <conio.h>
三个最常用的Windows控制台I/O函数是:
getch(): 从键盘获取字符(不需要按回车键)。getche(): 从键盘获取字符并回显。kbhit(): 检查是否有键被按下。
printf()函数
如前所述,printf()函数可以打印包含格式化数据的字符串:
printf("This is a test!\n");
它也可以包括变量的内容:
printf("Value1: %d Value2: %f\n", intval, floatval);
printf()使用的格式代码包括:
%d: 十进制整数%ld: 长十进制整数%c: 字符%s: 字符串%e: 浮点数的指数形式%f: 浮点数的十进制形式%g: 根据情况使用%e或%f,选择较短的表示%u: 无符号十进制整数%o: 无符号八进制整数%x: 无符号十六进制整数
使用错误的格式代码可能会导致奇怪的输出。通过修改格式代码可以获得更多控制,例如,可以指定最小字段宽度:
%10d
这表示字段宽度至少为10个字符。如果字段宽度过小,printf()会自动使用更宽的字段。添加负号后:
%-10d
会使文本左对齐。
可以指定数字精度:
%6.3f
这表示在宽度为6个字符的字段中保留3位小数。
还可以指定字符串的精度,表示最多打印字符数,例如:
/* 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 >
printf()字符串中的特殊字符
在第2章中列出的特殊字符也可以嵌入在printf字符串中:
'\a': 警告(蜂鸣)字符'\b': 退格'\f': 换页'\n': 换行'\r': 回车'\t': 水平制表符'\v': 垂直制表符'\\': 反斜杠'\?': 问号'\'': 单引号'\"': 双引号'%%': 百分号'\0NN': 八进制字符编码'\xNN': 十六进制字符编码'\0': 空字符
scanf()函数
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;
}
在scanf()中,name不需要加&,因为字符串的名称本身就是一个指针。输入字段由空白(空格、制表符或换行符)分隔,不过可以使用如%10d这样的格式说明符来定义特定的字段宽度。
scanf()的格式代码与printf()相同,唯一的不同是:
- 没有
%g格式代码。 %f和%e格式代码工作方式相同。- 有一个
%h格式代码用于读取短整型(short)整数。
如果格式代码中包含字符,scanf()会读取这些字符并丢弃它们。例如,如果将上面的示例修改为:
scanf("%d,%s", &val, name);
那么,scanf()会假定两个输入值由逗号分隔,并在遇到逗号时将其丢弃。
如果格式代码前面加上星号(*),则数据会被读取并丢弃。例如,如果修改为:
scanf("%d%*c%s", &val, name);
那么,如果两个字段由冒号(:)分隔,scanf()会读取并丢弃冒号字符。
当scanf()的输入被终止时,它会返回EOF(一个整数),EOF在stdio.h中被定义。
putchar()和getchar()函数
putchar()和getchar()函数用于单个字符的输入输出。例如,下面的程序从标准输入一个一个地接受字符:
/* inout.c */
#include <stdio.h>
int main(void)
{
unsigned int ch;
while ((ch = getchar()) != EOF)
{
putchar(ch);
}
return 0;
}
getchar()函数返回一个整数,并且也会在遇到EOF时终止。注意,C语言允许程序在同一个表达式中获取值并进行测试,这是处理循环时特别有用的功能。
关于单字符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()函数
puts()函数类似于简化版的printf(),它没有格式化代码。它打印一个字符串,并自动以换行符结束:
puts("Hello world!");
fgets()函数
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。strcmp函数将在下一章中详细讨论。
这些函数可以用于实现处理文本行而非单字符的过滤器。下面是这样一个过滤器的核心程序:
/* lfilter.c */
#include <stdio.h>
int main(void)
{
char b[256];
while ((fgets(b, 256, stdin)) != NULL)
{
puts(b);
}
return 0;
}
fgets()函数在输入终止或出现错误时会返回NULL,NULL在stdio.h中定义。
Windows下的控制台I/O函数
基于Windows的控制台I/O函数getch()和getche()的操作方式与getchar()类似,只是getche()会自动回显字符。
kbhit()函数与其他不同,它仅表示是否有键被按下。如果有键被按下,它返回非零值;如果没有键被按下,它返回零。这允许程序轮询键盘输入,而不是在键盘输入时阻塞,等待某些操作。正如前面所提到的,这些函数需要conio.h头文件,而不是stdio.h头文件。