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
头文件。