K&R学习笔记 第七章

这一章讲I/O。
I/O本来是与操作系统高度相关的内容,但是这一章,却从标准库的角度,介绍了如何使用I/O。而把I/O的一些具体实现细节留在了最后一章中。
首先需要注意的是,标准库中的许多“函数”,都是宏,比如getchar、putchar()、tolower()等等。这样做的目的是为了减小函数调用的开销,想想也是,这些“函数”是对单个字符进行处理的,而计算机的输入动辄上万个字符,所以这样设计,还是可以看出大牛们的高瞻远瞩。
然后介绍一下已经用烂了的printf()函数,需要注意的是printf的返回值:打印字符的个数。与printf相似的,还有sprintf,它将格式化的字符串打印到第一个参数指定的字符串中。
函数的特别之处在于变长参数表,(我们以前遇到的函数参数个数、类型都是一定的)书中通过一个例子来说明了这个问题:
#include 
#include 
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是否出错(虽然这种出错的几率很小)。

你可能感兴趣的:(K&R,学习笔记)