概述
输入/输出功能并不是C语言本身的组成部分
程序与环境之间的交互
比我们想象的要复杂的多
ANSI C标准精确地定义了这些库函数,所以,在任何可以使用C语言的系统中都有这些函数的兼容形式。
如果程序的系统交互部分
仅仅使用了标准库提供的功能,则该程序可以不经修改地从一个系统移植到另一个系统中
1、标准输入/输出
标准库实现了简单的文本输入/输出模式
文本流由一系列行组成
,每一行的结尾时一个换行符
。如果系统没有遵循这种模式,则标准库将通过一些措施使得该系统适应这种模式
标准输入
一般为键盘
符号常量EOF在头文件<stdio.h>
中定义,其值一般为-1。程序中应该使用EOF来测试文件是否结束,这样才能保证程序同EOF的特定值无关
在许多环境中,可以使用符号<
来实现输入重定向,它将把键盘输入
替换为文件输入
程序本身并不在意输入方式的改变
(输入重定向的命令字符串并不包含在argv的命令行参数中)
标准输出
一般为屏幕显示
int putchar(int)
函数将字符送至标准输出上,在默认情况下,标准输出为屏幕显示。如果没有发生错误,则函数putchar将返回输出的字符;如果发生了错误,在返回EOF
管道机制
用于程序间的数据重定向,重定向符
用于程序与文件之间的数据重定向
在程序中可以交叉调用函数putchar和printf,输出将按照函数调用的先后顺序依次产生
当文件名用<>
括起来的时候,预处理器将在由具体实现定义的有关位置
中查找指定的文件(UNIX系统中,一般是/usr/include中)
标准库中处理字符的“函数”一般都是宏
,这样就避免了对每个字符都进行函数调用的开销
无论<ctype.h>
中的函数在给定的机器上是如何实现的,使用这些函数的程序都不必了解字符集的知识
2、格式化输出——printf函数
输出函数printf将内部数值转换为字符的形式
int printf(char *format, arg1, arg2, ...)
函数printf在输出格式format的控制下,将其参数进行转换与格式化,并在标准输出设备上打印出来。它的返回值为打印的字符数
格式字符串format
包含两种类型的对象:普通字符
和转换说明
。在输出时,普通字符将原样不动地复制到输出流
中,而转换说明并不直接输出到输出流中,而是用于控制printf中参数的转换和打印。
每个转换说明
都由一个百分号字符(即%)开始,并以一个转换字符
结束。在字符%和转换字符中间可能依次包含负号
、宽度数
、小数点
、精度数
、字母h或l
转换说明中,如果%后面的字符不是一个转换字符,则该行为是未定义的
printf函数的基本的转换字符p135
在转换说明中,宽度或精度可以用*表示,这时,宽度或精度的值通过转换下一参数(必须为int类型)来计算
字符串相关的精度控制
函数printf使用第一个参数判断后面参数的个数及类型
。如果参数的个数不够或者类型错误,则将得到错误的结果
int sprintf(char *string, char *format, arg1, arg2, ...)
sprintf函数执行的转换和函数printf相同,但它将输出保存到字符串string中
,而不是输出到标准输出中。当然,string必须足够大
以存放输出结果
3、变长参数表
本节介绍如何以可移植的方式编写可处理变长参数表的函数
我们的重点在于参数的处理,所以,函数minprintf只处理格式字符串和参数
,格式转换则通过调用函数printf实现
函数printf的正确声明形式为:int printf(char *fmt, ...)
,其中省略号表示参数表中参数的数量和类型
是可变的。省略号只能出现在参数表的尾部
编写函数void minprintf(char *fmt, ...)
的关键在于如何处理一个甚至连名字都没有的参数表
标准头文件<stdarg.h>
中包含一组宏定义
,它们对如何遍历参数
进行了定义。该头文件的实现因不同的机器而不同,但提供的接口是一致的
va_list类型
用于声明一个变量ap(argument pointer)
,该变量将依次引用(指向)各参数。参数表必须至少包含一个有名参数
,宏va_start
将最后一个有名参数作为起点。宏va_start
将ap初始化为指向第一个无名参数的指针(在使用ap之前,该宏必须被调用一次)。
每次调用va_arg
,该函数都将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型名来决定返回的对象类型、指针移动的步长。最后,必须在函数返回之前调用va_end
,以完成一些必要的清理工作
//printf函数的简单实现
#include <stdio.h>
#include <stdarg.h>
void minprintf(char *fmt, ...)
{
va_list ap;
char *p, *sval;
int ival;
double dval;
va_start(ap, fmt); //将最后一个有名参数作为起点
for(p = fmt; *p; p++)
{
if(*p != '%')
{
putchar(*p);
continue;
}
switch(*++p)
{
case 'd':
ival = va_arg(ap, int);
printf("%d", ival);
break;
case 'f':
dval = va_arg(ap, double);
printf("%f", dval);
break;
cast 's':
for(sval = va_arg(ap, char *); *sval; sval++)
putchar(*sval);
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
4、格式化输入——scanf函数
输入函数scanf
提供将字符转换为内部数值的功能
int scanf(char *format, ...)
从标准输入
中读取字符序列,按照format中的格式说明
对字符序列进行解释,并把结果保存到其余的参数中。除format之外所有的参数都必须是指针
,用于指定经格式转换后的相应输入保存的位置
当scanf函数扫描完其格式串
,或者碰到某些输入无法与格式控制说明
匹配的情况时,该函数将终止。成功匹配并赋值的输入项的个数将作为函数值返回
;如果到达文件的结尾,该函数将返回EOF
(EOF和0所代表的意义是不同的)
下一次调用scanf函数将从上一次转换的最后一个字符的下一个字符开始继续搜索(在标准输入中)
int sscanf(char *string, char *format, ...)
用于从一个字符串(而不是标准输入)中读取字符序列
格式串format
中通常都包含转换说明
,用于控制输入的转换。格式串中可能包含下列部分:
空格或制表符
:在处理过程中将被忽略- 普通字符(不包括%):用于匹配输入流中下一个非空白符字符
- 转化说明:依次由一个%、一个可选的
赋值禁止字符*
、一个可选的数值(指定最大字段宽度)、一个可选的h、l或L字符前缀(用于指定目标对象的宽度)以及一个转换字符
组成
转换说明控制下一个输入字段的转换
如果转换说明中有赋值禁止字符*,则跳过该输入字段,不进行赋值(这里不太懂?????)
输入字段
定义为一个不包括空白符的字符串,其边界定义为到下一个空白符或达到指定的字段宽度
scanf函数将越过行边界读取输入
空白符
包括空格符、横向制表符、换行符、回车符、纵向制表符以及换页符
scanf函数的基本转换说明p138
scanf函数忽略格式串
中的空格和制表符
如果要读取格式不固定的输入,最好每次读入一行,然后再用sscanf将合适的格式分离出来读入(建议)
scanf函数可以和其他输入函数混合使用。无论调用哪个输入函数,下一个输入函数的调用将从scanf没有读取的第一个字符处开始读取数据
对于scanf和sscanf函数的除格式串以外的参数是否是指针这种错误,编译器在编译时一般检测不到
5、文件访问
标准输入和标准输出
是操作系统自动提供给程序访问的
如何将用户需要使用的文件的外部名同读取数据的语句关联起来
在读写一个文件之前,必须通过库函数fopen
打开该文件。fopen用文件的外部名与操作系统进行某些必要的连接和通信
,并返回一个随后可以用于文件读写操作的指针
文件指针
,它指向一个包含文件信息的结构,这些信息包括:缓冲区的位置
、缓冲区中当前字符的位置
、文件的读或写状态
、是否出错
或是否已经到达文件结尾
等等
<stdio.h>
中定义了一个包含文件信息的结构FILE
FILE *fopen(char *name, char *mode);
fopen函数返回一个指向结构FILE的指针
(FILE是一个类型名,而不是结构标记)。fopen的第一个参数是一个字符串,它包含文件名
。第二个参数是访问模式
,也是一个字符串,用于指定文件的使用方式。允许的模式包括:读(“r”)、写(“w”)、及追加(“a”)
。某些系统还区分文本文件
和二进制文件
,对后者的访问需要在模式字符串中增加字符“b”
以各种方式读写文件的逻辑结果
如果发生错误,fopen将返回NULL(如果需要更进一步的定位错误类型,需要掌握错误处理函数
的相关知识)
int getc(char *fp)
从文件中返回下一个字符,它需要知道文件指针,以确定对哪个文件执行操作。如果到达文件尾或出现错误,该函数将返回EOF
int putc(int c, FILE *fp)
将字符c写入到fp指向的文件中,并返回写入的字符。如果发生错误,则返回EOF。这两个函数是通过宏定义的
启动一个C语言程序时,操作系统环境负责打开3个文件,并将这3个文件的指针提供给该程序。这3个文件分别是标准输入
、标准输出
和标准错误
,相应的文件指针分别为stdin
、stdout
和stderr
,它们在<stdio.h>
中声明。在大多数环境中,stdin指向键盘,而stdout和stderr指向显示器
stdin和stdout可以被重定向到文件或管道
getchar和putchar函数可以通过getc、putc、stdin及stdout定义
#define getchar() getc(stdin)
#define putchar(c) putc((c), stdout)
对于文件的格式化输入或输出
,可以使用函数fscanf和fprintf
int fscanf(FILE *fp, char *format, ...)
int fprintf(FILE *fp, char *format, ...)
如果有命令行参数,参数将被解释为文件名,并按顺序逐个处理。如果没有参数,则处理标准输入
//cat程序
#include <stdio.h>
int main(int argc, char ** argv)
{
FILE *fp;
void filecopy(FILE *, FILE *);
if(argc == 1)
filecopy(stdin, stdout);
else
while(--argc > 0)
if((fp = fopen(*++argv, "r")) == NULL)
{
printf("cat: can't open %s\n", *argv);
return 1;
}
else
{
filecopy(fp, stdout);
fclose(fp);
}
return 0;
}
void filecopy(FILE *ifp, FILE *ofp)
{
int c;
while((c = getc(ifp)) != EOF)
putc(c, ofp);
}
文件指针stdin、stdout和stderr都是FILE*类型的对象
,但它们是常量
int fclose(FILE *fp)
执行和fopen相反的操作,它断开由fopen函数建立的文件指针和外部名之间的连接
,并释放文件指针
以供其他文件使用。因为大多数操作系统都限制了一个程序可以同时打开的文件数,所以,当文件指针不再需要时就应该释放,这是一个好的编程习惯
执行fclose函数还可以起到刷新相应缓冲区
的作用
当程序正常终止时,程序会自动为每个打开的文件调用fclose函数
如果不需要使用stdin和stdout,可以把它们关闭掉。也可以通过库函数freopen
重新指定它们
6、错误处理——stderr和exit
即使对标准输出进行了重定向,写到stderr
中的输出通常也会显示在屏幕上
//cat程序的错误处理功能完善版本
#include <stdio.h>
int main(int argc, char ** argv)
{
FILE *fp;
void filecopy(FILE *, FILE *);
char *prog = argv[0];
if(argc == 1)
filecopy(stdin, stdout);
else
while(--argc > 0)
if((fp = fopen(*++argv, "r")) == NULL)
{
fprintf(stderr, "%s: can't open %s\n", prog, *argv);
exit(1);
}
else
{
filecopy(fp, stdout);
fclose(fp);
}
if(ferror(stdout))
{
fprintf(stderr, "%s: error writing stdout\n", prog);
exit(2);
}
exit(2);
}
当标准库函数exit
被调用时,它将终止调用程序
的执行。任何调用该程序的进程
都可以获取exit的参数值,因此,可通过另一个将该程序作为子进程的程序来测试该程序的执行是否成功
按照惯例,返回值0表示一切正常
,而非0返回值通常表示出现了异常情况。exit为每个已打开的输出文件调用fclose函数
,以将缓冲区中的所有输出写到相应的文件中(刷新缓冲区)
exit和return等价,但是使用exit函数有一个优点,它可以从其他函数中调用(不懂?????)
int ferror(FILE *fp)
如果流fp出现错误,则函数ferror返回一个非0值
尽管输出错误
很少出现,但还是存在的,因此,成熟的产品程序应该检查这种类型的错误
int feof(FILE *fp)
,如果指定的文件到达文件结尾,该函数将返回一个非0值
对于任何重要的程序来说,都应该让程序返回有意义且有用的值
7、行输入和行输出
char *fgets(char *line, int maxline, FILE *fp)
标准库函数fgets从fp指向的文件中读取下一个输入行
(包括换行符
),并将它存放在字符数组line中,它最多可读maxline-1个字符。读取的行将以'\0'结尾
保存到数组中。如果该函数遇到了文件结尾或发生了错误,则返回NULL
int fputs(char *line, FILE *fp)
标准库函数fputs将一个字符串(不需要包含换行符
)写入到一个文件中。如果发生错误,该函数将返回EOF,否则返回一个非负值
库函数gets和puts
的功能与fgets和fputs类似,但它们是对stdin和stdout
进行操作的。gets函数在读取字符串时将删除结尾的换行符
(’\n’),而puts函数在写入字符串时将在结尾添加一个换行符
//fgets函数
char *fgets(char *s, int n, FILE *iop)
{
register int c;
register char *cs;
cs = s;
while(--n > 0 && (c = getc(iop)) != EOF)
if((*cs++ = c) == '\n')
break;
*cs = '\0';
return (c == EOF && cs == s) ? NULL : s;
}
//fputs函数
int fputs(char *s, FILE *iop)
{
int c;
while(c = *s++)
putc(c, iop);
return ferror(iop) ? EOF : feifuzhi;
}
8、其他函数
字符串操作函数
:在<string.h>
中定义
字符类别测试和转换函数
:在<ctype.h>
中定义
标准库中的int ungetc(int c, FILE *fp)
函数功能比较受限,每个文件只能接收一个写回字符
命令执行函数
:system(char *s)
执行包含在字符串s中的命令,然后继续执行当前程序。s的内容在很大程度上与所用的操作系统有关。该函数返回一个整型的状态值
,其值来自于执行的命令,并同具体系统有关。在UNIX系统中,返回的状态是exit的返回值
存储管理函数
:函数malloc和calloc用于动态地分配存储块
void *malloc(size_t n)
当分配成功时,它返回一个指针,该指针指向n字节长度
的未初始化
的存储空间,否则返回NULL
void *calloc(size_t n, size_t size)
当分配成功时,它返回一个指针,该指针指向的空闲空间足以容纳由n个指定长度的对象
组成的数组,否则返回NULL。该存储空间被初始化为0
根据请求的对象类型,malloc或calloc函数返回的指针满足正确的对齐要求
free(p)
函数释放p指向的存储空间,其中,p是此前通过调用malloc或calloc函数得到的指针。存储空间的释放顺序没有什么限制
,但是,如果释放一个不是通过调用malloc或calloc函数得到的指针所指向的存储空间,将是一个很严重的错误
使用已经释放的存储空间同样是错误的
数学函数
:在<math.h>
中声明
随机数发生器函数
:函数rand()
生成介于0和RAND_MAX
之间的伪随机整数序列
。其中RAND_MAX是在头文件<stdlib.h>
中定义的符号常量
函数srand(unsigned)
设置rand函数的种子数
随机数发生器要符合统计学特性