K&R学习笔记 第七章

来源:互联网 发布:步进电机控制器编程 编辑:程序博客网 时间:2024/05/16 14:15
这一章讲I/O。
I/O本来是与操作系统高度相关的内容,但是这一章,却从标准库的角度,介绍了如何使用I/O。而把I/O的一些具体实现细节留在了最后一章中。
首先需要注意的是,标准库中的许多“函数”,都是宏,比如getchar、putchar()、tolower()等等。这样做的目的是为了减小函数调用的开销,想想也是,这些“函数”是对单个字符进行处理的,而计算机的输入动辄上万个字符,所以这样设计,还是可以看出大牛们的高瞻远瞩。
然后介绍一下已经用烂了的printf()函数,需要注意的是printf的返回值:打印字符的个数。与printf相似的,还有sprintf,它将格式化的字符串打印到第一个参数指定的字符串中。
函数的特别之处在于变长参数表,(我们以前遇到的函数参数个数、类型都是一定的)书中通过一个例子来说明了这个问题:
#include <stdio.h>#include <stdarg.h>void minprintf(char *fmt,...);int main(int argc, char* argv[]){int a = 10;minprintf("I have %d books",a);return 0;}void minprintf(char *fmt,...){va_list ap;//依次指向无名参数va_start(ap,fmt);char* p;char* sval;int ival;int dval;for(p = fmt;*p;++p){if(*p != '%'){putchar(*p);continue;}switch(*++p){case 'd':ival = va_arg(ap,int);//调用va_arg,返回一个参数printf("%d",ival);break;case 'f':dval = va_arg(ap,double);printf("%f",dval);break;case 's':for(sval = va_arg(ap,char*);*sval;sval++)putchar(*sval);break;default:putchar(*p);break;}}va_end(ap);//结束调用清理}
对于没有名字的参数表,stdarg.h中的一组宏定义了何如使用它们。
首先,使用va_list类型声明一个变量,称为参数指针。这个变量将依次引用各个参数。使用时通过va_start初始化,将最后一个有名参数作为ap的起点;
通过调用va_arg,将返回一个参数,并将ap指向下一个参数。va_arg需要一个参数来确定返回对象的类型以及指针移动的步长。
结束时通过va_end宏释放资源。


与printff相对应的scanf。它接受一个格式的字符串输入,并按照格式将它保存最对应的参数中。当扫描完所有字符或者遇到格式不匹配时,scanf将停止工作,将返回成功匹配并赋值的输入项个数。如果遇到EOF,则返回EOF;如果全部匹配,则返回匹配的变量的个数。注意scanf将忽略空格符和制表符,在读入输入值时,会跳过各种空白符(空格、制表、换行)


除了跟标准IO交互以外,其实更多的时候,我们需要跟文件交互,这时就需要使用FILE类型的指针和fopen函数了。
fopen比较简单,参数是打开文件的位置和访问模式:读("r")、写("w")、追加("a")。需要注意的是,如果打开一个不存在的文件,或者用不正确的模式打开文件(比如有的文件时只读的,但你想写或者追加),那么fopen返回NULL;如果以w方式打开一个文件,原有的内容将被覆盖;如果以a方式打开,那么原有内容将保持不变。
其中FILE类型中包含了很多内容,包括缓冲区的位置、缓冲区中当前的位置、文件的读或写状态,是否出错,或者是否达到文件结尾等等。因为有了缓冲区中当前的位置,我们才可以使用getc函数从文件中返回下一个字符。同理,我们也可以使用fscanf或者fprintf来向文件格式化输入或者输出。


需要注意的是,当启动一个C程序时,系统已经自动打开了3个文件,stdin、stdout和stderr,其中stdin指向键盘;而后面两个指向显示器。下面通过一个将多个文件的字符连着打印出来来说明这个问题:

int main(int argc, char* argv[]){FILE *fp;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))//如果流出错,将会返回非0值{fprintf(stderr,"%s: error writing stdout\n",prog);exit(2);}exit(0);}void filecopy(FILE *ifp, FILE *ofp){int c;while((c = getc(ifp)) != EOF)putc(c,ofp);}

注意到,我们使用了fprintf将错误信息输出到屏幕上,而不是输出到文件或者管道中;在程序末尾,通过ferror(stdout)来检查stdout是否出错(虽然这种出错的几率很小)。