《C程序设计语言(第2版·新版)》第7章 输入与输出

  输入与输出并非C语言本身的组成部分。本章讲述标准库,重点讲输入/输出,以及介绍字符串处理、存储管理和数学函数;

  ANSI标准精确定义了这些库函数,所以任何可使用C的系统中都有这些函数的兼容形式;如果程序的系统交互部分仅仅使用了标准库提供的功能,那么就可以不经修改地从一个系统移植到另一个上;
  这些库函数得属性分别在十几个头文件中声明。附录B对标准库进行了详细描述。

7.1 标准输入/输出

  标准库实现了简单的文本输入/输出模式;文本流由一系列行组成,每一行的结尾是一个换行符;(若系统未遵循此模式,标准库会使得该系统在输入端和输出端都适应此模式)
  最简单的输入机制
int getchar(void) //从标准输入(一般为键盘)中读取并返回下一个输入字符或EOF(表明到了文件结尾,定义在<stdio.h>中,一般为-1)
  许多环境中可以使用符号<来实现输入重定向,比如程序prog中使用了getchar,则命令行:
prog <infile
将使得prog从输入文件infile(而非键盘)中读取数据;
  如果输入通过管道机制来自于另一个程序,那么这种输入也是不可见的;例如在某些系统中,下列命令行:
otherprog | prog
将运行两个程序,并且将otherprog的标准输出通过管道重定向到prog的标准输入上;
 
int putchar(int)  //putchar(c)将字符c送至标准输出(默认为屏幕显示),发生错误返回EOF;
prog >outfile  //命令行,将prog中的标准输出重定向到outfile
  函数printf也向标准输出设备上输出数据;在程序中可以交叉调用它俩,输出按其顺序依次产生;
  使用输入/输出库函数的每个源程序文件必须在引用这些函数前包含以下语句:
#include <stdio.h>
  当文件名用一对尖括号括起来,预处理器会在有关位置查找指定的文件(由具体实现定义,例如UNIX系统中文件一般放在目录/user/include中);
  许多程序只读一个输入流、只向一个输出流输出,这种情况只需使用getchar、putchar和printf就足够了;特别是通过重定向连接两个程序,仅仅用这些函数就足够了;
  头文件中的“函数”getchar、putchar及tolower等一般都是宏(8.5节介绍其实现方法),这样就避免了对每个字符都进行函数调用的开销;无论什么机器上,使用这些函数不必了解字符集;

7.2 格式化输出-printf函数

  printf将内部数值转换为字符的形式,以下只讲典型用法;
int printf(char *format, arg1, arg2, ...)
在输出格式format控制下,将参数进行转换与格式化,并在标准输出设备上打印出来,返回值为打印的字符数;
  格式字符串包括两种类型的对象:普通字符和转换说明;普通字符直接复制到输出流;转换说明都由一个%开始,以一个转换字符结束,二者之间可以包含:
·负号         //指定被转换参数按照左对齐形式输出
·数            //指定最小字段宽度,多余字符位置用空格填充
·小数点     //将字段宽度和精度分开
·数            //指定精度,即字符串要打印的最大字符数、整型最少输出的数字数目、浮点数小数点后的位数
·字母h或l  //前者将整数作为short打印,后者作为long
 
  printf函数的基本转换说明(包含所有转换字符):
·int类型可以按照十进制(d,i)、无符号八进制(o)、无符号十六进制(x,X)、无符号十进制(u)、单个字符(c)等形式来输出;
·char *类型(s):顺序打印字符串,直到遇到'\0'或满足精度要求为止;
·double类型可以按照十进制小数(f, 精度默认为6)、科学计数法(e,E, 精度默认为6)、选择格式(g,G, 我取的名字,指数小于-4或>=精度同e,否则同f)等形式来输出;
·void *类型(p):指针,取决于具体实现;
·%:不转换字符,打印一个百分号
  例如,%-15.10s,将产生左对齐的、最少15个字段宽度、最多10个字符;
 
  转换说明中宽度或精度可用星号*来表示,其值通过转换下一参数(必须int)来计算;例如:
printf("%.*s", max, s); // 从字符串中打印最多max个字符
printf函数使用其第一个参数判断后面参数的个数和类型,不对应则产生错误结果;例如:
printf(s); //若字符串s中含有%,输出将出错
printf("%s",s); //正确
 
int sprintf(char *string, char *format, arg1, arg2, ...)
该函数将输出结果存放到string中(而非输出到标准输出);注意string大小必须足够存放输出

7.3 变长参数表

  例子:printf的最简版,来介绍如何以可移植的方式编写可处理变长参数表的函数。
  printf的正确声明方式:
int printf(char *fmt, ...)
其中的省略号(只能出现在参数表尾部)表示参数表中参数的数量和类型是可变的;
 
  此例关键在于处理一个甚至连名字都没有的参数表:
  标准头文件<stdarg.h>中包含一组宏,它们对如何遍历参数表进行了定义;此例中用其中的va_list类型来声明一个变量ap(是一个“参数指针”),使用ap前必须调用宏va_start(ap, fmt)把ap初始化为指向第一个无名参数的指针;(参数表必须至少包括1个有名参数,va_start将最后一个有名参数作为起点)
  还需调用函数va_arg, 输入ap和一个类型名(如int, double, char *,用来确定返回对象的类型和指针移动长度),它会返回一个参数,并且将ap指向下一个参数;
  必须在函数返回之前调用va_end(ap)以完成一些必要的清理工作;

7.4 格式化输入-scanf函数

  只介绍此函数最有用的一些特征。同样具有变长参数表:
int scanf(char *format, ...)
  scanf从标准输入中读取字符序列,按照format中的格式说明对其进行解释,并把结果保存到其余参数(必须都是指针)中;扫描完格式串,或者碰到输入无法与格式控制说明匹配,函数就会终止;返回值是成功匹配并赋值的输入项的个数或EOF(注意返回它与0不同);下一次调用scanf函数将从上一次转换的最后一个字符的下一个字符开始继续搜索;
 
int sscanf(char *string, char *format, arg1, arg2, ...)
此函数按照format扫描string, 将结果保存到其余参数中;
 
  格式串中可能包含以下部分:
·空格或制表符  //处理中被忽略
·普通字符(不含%) // 匹配输入流中下一个空白字符
·转换说明  // 依次:% 、 *(赋值禁止字符,可选)、数值(指定最大字段宽度,可选)、h/l/L(指定目标对象宽度,可选)、转换字符;
  一般转换结果存放在相应参数指向的变量中,但若转换说明中有*,则跳过该输入字段,不进行赋值;输入字段定义:一个不包括空白符的字符串,边界定义为到下一个空白符或到达指定的字段宽度;所以scanf会越过行边界读取输入(因换行符也是空白符);
  空白符包括:空格符、回车符、换行符、换页符、横向制表符、纵向制表符;
 
  scanf的基本转换说明(输入数据--》参数类型):
·d:十进制整数------------------------》int *;类型
·i:整数--------------------------------》int * 类型,可以是八进制(0开头)或十六进制(0x或0X)
·o:八进制整数(可以0开头也可不)--》int型
·u:无符号十进制整数-----------------》unsigned *类型
·x:十六进制整数(0x或0X类似八)---》int *型
·c:字符-------------------------------》char *型,将后续多个(默认1)输入字符存入指定位置,通常不跳过空白符;如需读入下一个非空白符可使用%1s
·s:字符串(不加引号)---------------》char *类型,指向一个足矣存放该字符串(包括'\0')的字符数组
e,f,g:浮点数(可包括正负号、小数点、指数)--》float *类型
%:字符%-----------------------------》不进行任何赋值操作
 
  d,i,o,u,x前可加h(short)或l(long);e,f,g前可加l(double);
  例如:
double v;
while(scanf("%lf", &v)==1)...
  例如要读入这样日期格式的输入行:25 Dec 1988
int day, year;
char monthname[20];
scanf("%d %s %d", &day, monthname, &year); //数组名本身就是指针
 
  字符字面值也可出现在scanf的格式串中,但它必须与输入中相同的字符匹配;例如,可用以下程序读入形如mm/dd/yy的日期数据:
int day, month, year;
scanf("%d/%d/%d", &day, &month, &year);
  scanf忽略格式串中的空格和制表符;读取输入时它跳过空白符;如果要读取格式不固定的输入(如日期格式可能是上述任意一种),最好每次读入一行(如getline函数),然后用sscanf将合适的格式分离出来读入;
  scanf可以和其他输入函数混合使用;下一次调用总是从没有读取的第一个字符处开始读取数据;
  注意它的所有参数都是指针,这类错误编译器一般检查不到;

7.5 文件访问

  标准输入和输出都是操作系统自动提供给程序访问的;
  例子:写一个访问文件的程序,且它所访问文件还没有连接到该程序;
  写cat程序:把一批命名文件串联后输出到标准输出上;对那些无法通过名字访问文件的程序;来说,它还可以作为通用的输入收集器;比如以下命令行:
cat x.c y.c   //在标准输出上打印文件x.c和y.c的内容
  如何将用户需要使用的文件的外部名跟读取数据的语句关联起来呢?库函数fopen
  fopen用类似x.c这样的外部名与操作系统进行一些必要的连接和通信(不必关心其细节),并返回一个随后可用于文件读写操作的“文件指针”;
  文件指针指向一个包含文件信息的结构;文件信息包括:缓冲区的位置、缓冲区中当前字符的位置、文件的读或写状态、是否出错或是否到达文件结尾等,不必关心这些细节,因<stdio.h>中已定义此结构  FILE;程序中只需如下声明一个文件指针即可:
FILE *fp;  //FILE像int一样是个类型名,而非结构标记,它通过typedef定义
FILE *fopen(char *name, char *mode);
  调用:
fp=fopen(name, mode);
其中第一个参数是字符串,包含文件名;第二个参数是访问模式,也是字符串,用于指定文件的使用方式(允许的模式:("r")、("w")、追加("a"), 区分文本文件和二进制文件的系统中访问后者需在串中增加字符"b");
  写一个已存在的文件,原内容将被覆盖;追加一个已存在文件,原内容保持不变;写或追加一个不存在的文件,该文件将会被创建(若可能);读不存在的文件会导致错误;其他操作也可能导致错误,例如读一个无读取权限的文件;发生错误,fopen会返回NULL;
 
  文件打开后,考虑读写,有多种方法,最简单的是:
int getc(FILE *fp); //返回文件指针fp指向的输入流中的下一个字符;出错或到文件结尾则返回EOF
int putc(int c, FILE *fp); //将字符c写入到fp指向的文件中,并返回写入的字符;出错则返回EOF
  类似于getchar和putchar,getc和putc也是宏而非函数;
 
  启动一个C程序时,操作系统负责打开3个文件(标准输入、标准输出、标准错误)并且将它们的文件指针(stdin, stdout, stderr, 在<stdio.h>中被声明)提供给程序;大多数环境中stdin指向键盘,stdout和stderr指向显示器;stdin和stdout可以被重定向到文件或管道;
getchar和putchar可以如下定义:
#define getchar() getc(stdin)
#define putchar(c) putc((c), stdout)
 
  文件的格式化输入输出--fscanf和fprinf: 与scanf和printf的区别仅仅是在格式串参数之前加了一个文件指针:
int fscanf(FILE *fp, char *format, ...)
int fprintf(FILE *fp, char *format, ...)
 
  文件指针stdin和stdout都是FILE *类型的常量,不可赋值;
 
  cat程序中:
  通过getc和putc的循环可实现filecopy函数;
  循环读入argc和argv对应元素, 打开对应文件名,判断以及filecopy;
  函数
int flose(FILE *fp)
执行和fopen相反的操作,它断开由fopen建立的文件指针和外部名之间的连接,并释放文件指针以供其他文件使用;
  当文件指针不再需要时就应释放,这是一个好的编程习惯;对输出文件执行fclose还有另外一个原因:它将把缓冲区中由putc函数正在收集的输出写到文件里;
  如果不需要使用stdin与stdout,可以把它们也关闭掉,也可以通过库函数freeopen重新指定它们;当程序正常终止时,程序会自动为每个打开的文件调用 fclose函数;

7.6 错误处理

  cat程序的错误处理功能不完善:某处连接因某种原因打不开文件时,相应诊断信息要在该连接的输出末尾才打印出来;输出到屏幕,这种处理方法尚可接受,若输出到另一个文件或通过管道输出到另一个程序,就无法接受了;
  改进方法:
  1)将另一个输出流stderr以同于stdin和stdout的方式分配给程序;即使stdout被重定向,写到stderr中的输出通常也会显示在屏幕上;
  2)使用标准库函数exit,调用它时它将终止调用程序的执行;任何调用该程序的进程都可以获取exit的参数值,因此可通过另一个将该程序作为子进程的程序来测试该程序的执行是否成功;(按照惯例,返回值0表示一切正常,非0则表示出现异常)
  exit为每个已打开的输出文件调用fclose函数,以将缓冲区中的所有输出写到相应的文件中;
  在主程序main中,语句return expr等价于exit(expr);但是,使用exit有一个优点:??它可以从其他函数中调用,并且可以用类似于第5章中描述的模式查找程序查找这些调用;
 
int ferror(FILE *fp)  //如果流fp中出现错误,则该函数返回一个非0值
  此例中该函数被用来检查流stdout是否出错;尽管输出错误很少出现,但也存在(比如磁盘满时),因此成熟的产品程序应该检查这种类型的错误;
 
int feof(FILE *fp) //与ferror类似,当指定文件到达文件结尾,此函数将返回一个非0值
 
  对任何重要的程序来说,都应该让程序返回有意义且有用的值;

7.7 行输入和行输出

  标准库提供了一个类似于getline的输入函数fgets:
char *fgets(char *line, int maxline, FILE *fp)
  该函数从流fp中读取下一个输入行(包括换行符),并将它存放(以'\0'结尾)在字符数组line中,最多可读取maxline-1个字符;通常情况下fgets返回line,如果与到文件结尾或出错,则返回NULL;(自编的getline是返回行的长度)
  输出函数fputs:
int fputs(char *line, FILE *fp)
  该函数将一个字符串(不需包括换行符)写入流fp中;发生错误会返回EOF,否则返回一个非负值(这不同于ferror);
  库函数gets和puts的功能与fgets和fputs类似,但它们是对stdin和stdout进行操作;不过gets和puts在读取或写入字符串时将在结尾删除或添加一个换行符;

7.8 其他函数

  介绍一些标准库中特别有用的函数,详细信息和其他函数在附录B中;
 
  字符串操作函数,在头文件<string.h>中有(s,t为char *类型,c,n为int类型):
strcat(s,t);//后一个连接到前一个末尾
strncat(s, t, n);//后一个前n个字符连接到前一个末尾
strcmp(s, t);//根据比较结果返回负整数、0或正整数
strncmp(s, t, n);//比较前n个
strcpy(s, t);//将后一个复制到前一个的位置
strncpy(s, t);//后一个前n个字符复制到前一个的位置
strlen(s);//返回字符串长度
strchr(s, c);//在前一个字符串中查找后一个,返回第一次出现位置的指针或EOF
strrchr(s, c);//在前一个字符串中查找后一个,返回最后一次出现位置的指针或EOF
 
  字符类别测试和转换函数(定义在头文件<ctype.h>中):
isalpha(c);//是字母则返回非0
isupper(c);//是大写则返回非0
islower(c);//是小写则返回非0
isdigit(c);//是数字则返回非0
isalnum(c);//是字母或数字返回非0
isspace(c);//是空白符则返回非0
toupper(c);//返回c的大写形式
tolower(c);//返回c的小写形式
 
  ungetc函数(标准库提供,相比自编ungetch,功能更受限制):
int ungetc(int c, FILE *fp)
  该函数将字符c写回到流fp中;如果成功则返回c,否则返回EOF;每个文件只能接收一个字符;unget可以和任何一个输入函数一起使用,比如scanf, getc, getchar;
 
  命令执行函数
  函数system(char*)执行包含在字符串s中的命令。然后继续执行当前程序;s内容很大程度与所用操作系统有关;以下是UNIX下的小例子:
system("date"); //执行程序date,它在标准输出上打印当天的日期和时间,并返回一个整型的状态值(来自于执行的命令,与系统有关,如UNIX下返回的是exit的返回值)
 
  存储管理函数
  函数malloc和calloc用于动态地分配存储块
void *malloc(size_t n)
分配成功时该函数返回一个指向n字节长度的未初始化的存储空间的指针;否则返回NULL; 
void *calloc(size_t n, size_t size)
分配成功则返回一个指向空闲空间的指针,该空闲空间(被初始化为0)足以容纳由n个指定长度的对象组成的数组;否则返回NULL;
 
  根据请求的对象类型,malloc和calloc返回的指针满足正确的对齐要求;下例进行类型转换:
int *ip;
ip=(int *) calloc(n, sizeof(int));
  free(p)释放p指向的存储空间,其中p是调用malloc或calloc得到的指针;存储空间释放顺序并无限制;但是如果释放一个并非调用malloc或calloc得到的指针所指向的空间,那会是一个严重错误;
malloc分配的存储块可以任意顺序释放;
 
  数学函数(<math.h>声明了20多个):
  每个函数带有1个或2个double类型的参数,并返回一个double型;
sin(x)  //x用弧度
cos(x)
atan2(y, x) //y/x的反正切
exp(x)
log(x) //自然对数
log10(x)
pow(x, y) //计算x^y
sqrt(x)
fabs(x)
 
  随机数发生器函数
  函数rand()生成介于0和RAND_MAX(符号常量,定义在<stdlib.h>中)之间的伪随机整数序列;以下方法可以生成0和1之间的随机浮点数:
#define frand() ((double)rand()/(RAND_MAX+1.0))
  如果所用函数库中已经提供了一个生成随机浮点数的函数,那它可能比上述函数具有更好的统计学特征;
  函数srand(unsigned)设置rand的种子数;2.7节中给出了遵循标准的rand和srand的可移植的实现;

 

posted @ 2015-09-20 00:06  fFaXzz  阅读(376)  评论(0编辑  收藏  举报