第7章 输入与输出
输入与输出
输入/输出功能并不是C语言本身的组成部分,所以到目前为止,我们并没有过多的强调他们。但是,程序与环境之间的交换比我们在前面部分中描述的情况要复杂很多,本章将讲述标准库。
ANSI标准精确的定义了这些库函数,所以,在任何可以使用C语言的系统中都有这些函数的兼容性是。如果程序的系统交换部分仅仅使用了标准提供的功能,则可以不经修改地从一个系统一直到另一个系统中。
7.1 标准输入/输出
标准库可以在输入端将回车符和换行符都转换为换行符,而在输入端进行反向转换。
最简单的输入机制是使用getcchar函数从标准输入中(一般为键盘)一次读取一个字符:int getchar(void)
getchar函数在每次被调用时返回夏一个输入字符。遇到文件结尾,返回EOF。符号常量EOF在头文件<stdio.h>中定义,其值一般为-1,但程序中应该使用EOF来测试文件是否结束,这样才能保证程序同EOF的特定值无关。
在许多环境中,可以使用符号<来实现输入重定向, 它将把键盘输入替换为文件输入:如果程序prog中使用了函数getchar,则命令行 prog < infile 将使得程序prog从输入文件infile(而不是从键盘)中读取字符。实际上,程序prog本身并不在意输入方式的改变,并且,字符串"<infile"也并不高含在argv的命令行参数中。如果输入通过管道机制来自于另一个程序,那么这种输入切换也是不可见的。比如,在某些系统中,下列命令行: otherprog | prog 将运行两个程序 otherorog 和 prog,并将程序 otherprog 的标准输出通过管道重定向到程序prog的标准输入上。
函数 int putchar(int) 用于输入数据。putchar(c)将字符 c 送至标准输出上,在默认情况下,标准输出为屏幕显示。如果发生错误,则函数putchar将返回同输出的字符;如果发生了错误,则返回EOF。同样,通常情况下,也可以使用”>输出文件名"的格式将输出重定向到某个文件中。例如,如果程序prog调用了函数putchar,那么命令是 prog > 输出文件名 将把程序prog的输出从标准输出设备重定向到文件中。如果系统支持管道,那么命令行 prog | anotherprog 将把程序prog的输出通过管道重定向到程序anotherprog的标准输入中。
函数pringf也向标准输出设备上输出数据。我们在程序中可以交叉调用函数putchar和printf,输出将按照函数条用的先后顺序依次产生。
使用输入/输出库函数的每个源程序文件必须在医用这些函数之前包含 下列语句 #include <stdio.h>
当文件名用一对尖括号时,预处理器将在由具体实现定义的有关位置中查找指定的文件(例如,在UNIX系统中,文件/usr/include中。
许多程序只从一个输入流中读取数据,并且只向一个输出流中输出数据。对于这样的程序,只需要使用函数getchar、putchar和prrintf事项输入/速出即可,并且对程序来说已经足够了。特别是,如果通过重定向将一个程序的输出连接到另一个程序的输入,仅仅这些函数就足够了。考虑程序lower,将输入转换为小写字母的形式:
1 /* 2 编写一个程序,根据它自身被调用是存放在argv[0]中的名字,实现将大写字母转换为小写字母或 3 小写字母转换为大写字母功能。 4 */ 5 6 #include <stdio.h> 7 #include <string.h> 8 #include <ctype.h> 9 10 11 //lower converts upper case to lower case 12 //upper converts lower case to upper case 13 14 15 int main(int argc, char *argv[]) 16 { 17 int c; 18 //printf("%s",argv[1]); 19 if (strcmp(argv[1],"lower") == 0) 20 while ((c = getchar()) != EOF) 21 putchar(tolower(c)); 22 else 23 while ((c = getchar()) != EOF) 24 putchar(toupper(c)); 25 return 0; 26 }
7.2 格式化输出--printf函数
函数printf在输出格式format的控制下,将其参数进行转换与格式化,并在标准输出设备上打印出来。它的返回值为打印字符数。
格式字符串包含两种类型的对象:普通字符和转化说明,每个转换说明由一个百分号字符% 开始,并以一个转换字符结束。在字符%和转换字符中间可能一次包含下列组成部分:
负号,用于指定被转换的参数按照左对齐的形式输出。
数,用于指定最小字段宽度。转换后的参数将打印不小于最小字段宽度的字段。如果由必要,字段左边(如果使用左对齐的方式,则为右边)多余的字符位置用空格填充以保证最小字段宽。
小数点,用于将字段宽度和精度分开。
数,用于指定精度,即指定字符串中要打印的最大字符数、浮点数小数点后的位数、整型最少输出的数字数目。
字母h或l,字母h表示不将整数作为short类型打印,字母l表示将整数作为long类型打印。

在转换说明中,宽度或精度可以用*号表示,这时,宽度或精度的值通过转换下以参数(必须为int类型)来计算。例如,为了从字符串打印最多max个字符,可以使用下列语句:
printf("%.*s", max, s);
1 /* 2 编写一个程序,以合理的方式打印任何输入.该程序至少能够根据用户的习惯 3 以八进制或十六进制打印非图形字符,并截断长文本行. 4 */ 5 6 #include <stdio.h> 7 #include <ctype.h> 8 9 10 #define MAXLINE 100 //max number of chars in one line 11 #define OCTLEN 6 //length of an octal value 8进制长度 12 13 14 //print arbitrary input in a sensible way 以合理的方式打印任意输入 15 main() 16 { 17 int c, pos; 18 int inc(int pos, int n); 19 20 pos = 0; //position in the line 在每一行中的定位 21 while ((c = getchar()) != EOF) 22 if (iscntrl(c) || c == ' '){ //函数检查所传的字符是否是控制字符 non-graphic or blank 23 pos = inc(pos, OCTLEN); 24 printf(" \\%03o ", c); //newline character ? 25 if (c == '\n') { 26 pos = 0; 27 putchar('\n'); 28 } 29 } else { //graphic character 30 pos = inc(pos, 1); 31 putchar(c); 32 } 33 return 0; 34 } 35 36 // inc: increment position counter for output 37 int inc(int pos, int n) 38 { 39 if (pos + n < MAXLINE) 40 return pos+n; 41 else{ 42 putchar('\n'); 43 return n; 44 } 45 } 46 47 /* 48 每个输出行最多能容纳MAXLINE个字符。宏iscntrl来自头文件<ctype.h>,用于勋章非显示字符--删除 49 控制符(八进制0177)和普通控制符(小于八进制040)。空格也被看作是一个非显示字符。非显示字符将打印 50 为长度为OCTLEN个字符的八进制数字(数字前有一个空格和反斜杠\,数字后又有一个空格)。在遇到换行符时, 51 变量pos重新设置为0,如下所示: 52 if (c == '\n'){ 53 pos = 0; 54 putchar('\n'); 55 } 56 inc函数用来对输出行上的写位置(pos+n)进行计算,如果输出行上剩余空间不足n个字符,就在当前位置 57 插入一个换行符。 58 */
7.3. 变长参数表
1 #include <stdarg.h> 2 3 /* 4 函数minprintf的关键在于如何处理一个甚至连名字都没有的参数表。标准头文件<stdarg.h>中包含一组 5 宏定义,他们对如何遍历参数进行了定义。该头文件的实现因不同的机器而不同,但提供的结构是一致的。 6 */ 7 //minprintf: minimal printf with variable argument list 带有变量参数列表的最小输出 8 void minprintf(char *fmt, ...) //省略号代表参数的数量和类型是可变的,省略号只能出现在参数尾部 9 { 10 va_list ap; //points to each unnamed arg in turn 依次指向每个为命名的参数 11 /* 12 va_list类型用于声明一个变量,该变量将一次引用各参数。在函数minprintf中,我们将该变量称为ap, 13 意思是“参数指针”。 14 */ 15 char *p, *sval; 16 int ival; 17 double dval; 18 19 va_start(ap, fmt); //make ap point to 1st unnamed arg 为第一个无名参数制作指针 20 /* 21 宏va_start将ap初始化为指向第一个无名参数的指针。在使用ap之前,该宏必须调用一次。参数表必须 22 至少包括一个有名参数,va_start将最后一个有名参数作为起点。 23 */ 24 for (p = fmt; *p; p++) { 25 if (*p != '%'){ 26 putchar(*p); 27 continue; 28 } 29 switch (*++p) 30 { 31 case 'd': 32 ival = va_arg(ap, int); 33 /* 每次调用va_arg,该函数都将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型 34 名来决定返回的对象类型、指针移动的步长。 35 */ 36 printf("%d", ival); 37 break; 38 case 'f': 39 dval = va_arg(ap, double); 40 printf("%f", dval); 41 break; 42 case 's': 43 for (sval = va_arg(ap, char *); *sval; sval++) 44 putchar(*sval); 45 break; 46 default: 47 putchar(*p); 48 break; 49 } 50 } 51 va_end(ap); //clear up when done 结束清理 52 //最后,必须在函数返回之前调用va_end,以完成一些必要的清理工作。 53 }
练习7-3
1 //改写minprintf函数,使它能完成printf函数的更多功能。 2 3 4 #include <stdio.h> 5 #include <stdarg.h> 6 #include <ctype.h> 7 8 9 #define LOCALFMT 100 10 11 void minprintf(char *fmt, ...); 12 main() 13 { 14 15 minprintf("a%s","bbb"); 16 return 0; 17 } 18 19 20 //minprintf: minimal printf with variable argument list 带参数列表的最小化打印函数 21 void minprintf(char *fmt, ...) 22 { 23 va_list ap; // points to each unnamed arg 指向每个没有名字的参数 24 char *p, *sval; 25 char localfmt[LOCALFMT]; 26 int i, ival; 27 unsigned uval; 28 double dval; 29 30 va_start(ap, fmt); // make ap point to 1st unnamed arg 31 for (p = fmt; *p; p++) { 32 if (*p != '%') { 33 putchar(*p); 34 continue; 35 } 36 i = 0; 37 localfmt[i++] = '%'; //start local fmt 38 while (*(p+1) && !isalpha(*(p+1))) 39 localfmt[i++] = *++p; // collect chars 40 localfmt[i++] = *(p+1); // format letter 41 localfmt[i] = '\0'; 42 switch (*++p) 43 { 44 case 'd': 45 case 'i': 46 ival = va_arg(ap, int); 47 printf(localfmt, ival); 48 break; 49 case 'x': 50 case 'X': 51 case 'u': 52 case 'o': 53 uval = va_arg(ap, unsigned); 54 printf(localfmt, uval); 55 break; 56 case 'f': 57 dval = va_arg(ap, double); 58 printf(localfmt, dval); 59 break; 60 case 's': 61 sval = va_arg(ap, char *); 62 printf(localfmt, sval); 63 break; 64 default: 65 printf(localfmt); 66 break; 67 } 68 } 69 va_end(ap); // clean up 70 } 71 72 /* 73 minprintf函数对自己的输入参数表加以分析,然后调用printf来完成具体的打印输出功能。 74 为了更多的实现一些printf函数的功能。我们使用了一个字符数组localfmt来保存从minprintf函数的 75 输入参数表中分析出来的%字符和其他字符,直到遇到一个字母字符(即输出格式控制符)位置,最终得到的 76 localfmt将被用作printf格式参数. 77 */
7.4 格式化输入--scanf函数
输入函数scanf对应于输出函数printf,它在与后者相反的方向上提供同样的转换功能。
int scanf(char *format, ...)
scanf函数从标准输入中读取字符序列,按照format中的格式说明对字符序列进行解释,并把结果保存到其余的参数中。其他所有参数必须是指针,用于指定经格式转换后的相应输入保存的位置。和上述讲述printf一样。
当scanf函数扫描完其格式串,或者碰到某些输入无法与格式控制说明匹配的情况时,函数将终止,同时,成功匹配并赋值的输入项的个数将作为函数值返回,所以,该函数的返回值可以用来确定已匹配的输入项的个数。如果到达文件的结尾,该函数将返回EOF。注意,返回EOF与0是不同的,0表示夏一个输入字符与格式串中的第一个格式说明不匹配。下一次调用scanf函数将从上一次转换的最后一个字符的夏一个字符开始继续搜索。
还有一个输入函数sscanf,它用于从一个字符串(而不是标准输入)中读取字符
int sscanf(char *string, char *format, arg1, arg2, ...)
它安装格式参数format中规定的格式扫描字符串string,并把结果分别保存到arg1、arg2、...这些参数中。这些参数必须是指针。
格式串通常包含转换说明,用于控制输入的转换。
* 空格或制表符,在处理过程城中将被忽略。
* 普通字符(不包括%),用于匹配输入流中下一个非空白字符。
* 转换说明,依次由一个%、一个可选的赋值禁止字符*、一个可选的数值(指定最大字段宽度)、一个可选的h、l或L字符(指定目标对象的宽度)以及一个转换字符组成。
转换说明控制下一个输入字段的转换。一般来说,转换结果存放在相应的参数指向的变量中。但是,如果转换说明中有赋值禁止字符*,则跳过该输入字段,不进行赋值。输入字段定义为一个不包括空白字符的字符串,其边界定义为到下一个空白符或达到指定字段宽度。这表明scanf函数将越过行边界读取输入,因为换行符也是空白符。(空白符包括空格符、横向制表符、换行符、回车符、纵向制表符以及换页符)。

转换说明d、i、o、u及x的前面可以加上字符h或l。前缀h表明参数表的相应参数是一个指向short类型而非int类型的指针,前缀l表明参数是一个指向long类型的指针,类似的,转换说明e、f和g的前面有可以加l,表明参数的相应参数是一个指向double类型而非float类型的指针。
1 //练习7-4编写scanf函数的一个简化版本。 2 3 #include <stdio.h> 4 #include <stdarg.h> 5 #include <ctype.h> 6 7 8 #define LOCALFMT 100 9 10 void minscanf(char *fmt, ...); 11 main() 12 { 13 minscanf("asd%s","ccc"); 14 } 15 16 //minscanf: minimal scanf with variable argument list 简单版本的多参数scanf函数 17 void minscanf(char *fmt, ...) 18 { 19 va_list ap; //points to each unnamed arg 20 char *p, *sval; 21 char localfmt[LOCALFMT]; 22 int c, i, *ival; 23 unsigned *uval; 24 double *dval; 25 26 i = 0; // index for localfmt array 数组索引 27 va_start(ap, fmt); //make ap point to 1st unnamed arg 制作指针指向未命名参数第一个 28 for (p = fmt; *p; p++) { 29 if (*p != '%') { 30 localfmt[i++] = *p; //collect chars 31 continue; 32 } 33 localfmt[i++] = '%'; //start format 34 while (*(p+1) && !isalpha(*(p+1))) 35 localfmt[i++] = *++p; //collect chars 36 localfmt[i++] = *(p+1); 37 localfmt[i] = '\0'; 38 switch(*++p) { 39 case 'd': 40 case 'i': 41 ival = va_arg(ap, int *); 42 scanf(localfmt, ival); 43 break; 44 case 'x': 45 case 'X': 46 case 'u': 47 case 'o': 48 uval = va_arg(ap, unsigned *); 49 scanf(localfmt, uval); 50 break; 51 case 'f': 52 dval = va_arg(ap, double *); 53 scanf(localfmt, dval); 54 break; 55 case 's': 56 sval = va_arg(ap, char *); 57 scanf(localfmt, sval); 58 //printf("%s",sval); 59 break; 60 default: 61 scanf(localfmt); 62 break; 63 } 64 i = 0; //reset index 65 } 66 va_end(ap); //clean up 67 } 68 69 /* 70 minscanf函数在很多地方模仿了minprintf函数。它先对输入的格式字符串中的字符进行性收集直到在% 71 找到一个字母字符。然后在把字符数组localfmt和有关指针传递给scanf函数。 72 scanf函数的参数必须是指针:一个指针指向格式字符串,另一个指针则指向用于接收scanf输入值的变量。 73 我们先通过va_arg函数得到指针的值,在把它复制为一个局部指针,最后通过调用scanf把输入值读到用户指定 74 的变量中去。 75 */
1 //练习7-5 改写第四章中后缀计算器程序,用scanf函数和(或)sscanf函数事项输入以及数的转换。 2 3 #include <stdio.h> 4 #include <ctype.h> 5 6 7 #define NUMBER '0' //signal that a number was found 关于是否找到数字的信号 8 9 int getop(char s[]); 10 int getop1(char s[]); 11 12 main() 13 { 14 getop1("a"); 15 } 16 17 //getop: get next opeater or numeric operand 获得下一个操作数或运算符 18 int getop(char s[]) 19 { 20 int i, rc; 21 char c; 22 static char lastc[] = " "; //有两个元素的静态数组,用于记录最后读入的字符。 23 24 sscanf(lastc, "%c", &c); //把lastc[0]中的字符读到变量c中。或者使用 c=lastc[0] 25 lastc[0] = ' '; //clear last character 清空最后一个字符 26 while ((s[0] = c) == ' ' || c == '\t') 27 if (scanf("%c", &c) == EOF) //scanf的返回值是成功地匹配的个数 28 /* 29 scanf("%d %d", &a, &b); 30 函数返回值为int型。如果a和b都被成功度如,返回值为2,如果只读入一个,返回值为1,否则为0. 31 */ 32 c = EOF; 33 s[1] = '\0'; 34 if (!isdigit(c) && c != '.') 35 return c; //not a number 36 i = 0; 37 if (isdigit(c)) // collect integer part 38 do { 39 rc = scanf("%c", &c); 40 if (!isdigit(s[++i] = c)) //调用scanf读取字符,把字符复制给字符串s,在测试是不是数字 41 break; 42 } while (rc != EOF); //当scanf读到EOF时,不会改变变量c的值 43 if (c == '.') // collect fraction part 44 do { 45 rc = scanf("%c", &c); 46 if (!isdigit(s[++i] = c)) 47 break; 48 } while (rc != EOF); 49 s[i] = '\0'; 50 if (rc != EOF) 51 lastc[0] = c; 52 return NUMBER; 53 54 } 55 56 57 //另一种解答 58 59 int getop1(char s[]) 60 { 61 int rc; 62 char c; 63 float f; 64 65 while ((rc = scanf("%c", &c)) != EOF) 66 if ((s[0] = c) != ' ' && c != '\t') 67 break; 68 69 s[1] = '\0'; 70 if (rc == EOF) 71 return EOF; 72 else if (!isdigit(c) && c != '.') 73 return c; 74 ungetc(c, stdin); //如果读到的是一个数组字符或者一个小数点,使用ungetc重新压入缓冲区, 75 scanf("%f", &f); //然后完整的读入整个数组 76 sprintf(s, "%f", f); //把变量f中的浮点数值转换为变量s中的一个字符串。 77 return NUMBER; 78 }
7.5 访问文件
程序cat把一批命名文件串联后输出到标准输出上。用来打印文件,对于那些无法通过名字访问文件的程序来说。它还可以用作通用的输入收集器。例如 cat x.c y.c ,会在标准输出上打印文件x.c和y.c。的内容。
fopen用类似于x.c或y.c这样的外部名与操作系统进行某些必要的连接和通信,并返回一个随后可以用于文件读写操作的指针。该指针称为文件指针,它指向一个包含文件信息的机构,这些信息包括:缓冲区的位置、缓冲区中当前字符的位置、文件的读或写状态、是否出错或是否已经到达文件结尾。在<stdio.h>中定义了一个包含这些信息的结构FILE。在程序中只需按照下列方式声明一个文件指针。
FILE *fp;
FILE *fopen(char *name,cchar *mode);
fp是一个指向结构FILE的指针,并且,fopen函数返回一个指向结构FILE的指针。FILE像int一样是一个类型名,而不是结构标记。它通过typedef定义。
fp = fopen(name,mode); 其中第一个参数是字符串,包含文件名,第二个参数是访问模式,也是一个字符串,用于指定文件的使用方式。模式有:读 r ,写 w, 追加 a。某些系统还区分文本文件和二进制文件,对后者的访问需要在模式字符串中增加字符“b"。如果文件存在,那么会创建文件,当以写方式打开一个已存在的文件时,该文件原来内容将被覆盖。追加时,文件原来内容不变。读一个不存在的文件会导致错误,其他一些操作也可能导致错误,比如试图读取一个无权限的文件。如果发生错误,fopen返回NULL。
文件打开后,可以使用getc和putc函数最为简单。getc从文件中返回下一个字符,它需要知道文件指针,以确定对哪个文件执行操作:
int getc(FILE *fp)
getc函数返回fp指向的输入流中的下一个字符。如果到达文件结尾或出现错误,该函数将返回EOF,putc是一个输出函数
int putc(int c, FILE *fp) 该函数将字符c写入到fp指向的文件中,并返回写入的字符。如果发生错误,则返回EOF。getc和putc是宏不是函数。
启动一个C语言程序时,操作系统环境负责打开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。他们与 scanf 和 printf 函数的区别仅仅在于他们的第一个参数是一个指向所要读写的文件的指针,第二个参数是格式串。
int fascanf(FILE *fp, cchar *format, ...)
int fprintf(FILE *fp, char *format, ...)
1 #include <stdio.h> 2 3 4 //cat: concatenate files, version 1 连接文件,版本1 5 6 main(int argc, char *argv[]) 7 { 8 FILE *fp; 9 void filecopy(FILE *, FILE *); 10 11 if (argc == 1) // no args; copy standard input 没有参数,复制标准输入 12 filecopy(stdin, stdout); //stdin与stdout都是FILE *类型的对象。他们是常量,而非变量。不能赋值 13 else 14 while (--argc > 0) 15 if ((fp = fopen(*++argv, "r")) == NULL) { 16 printf("cat: can't open %s\n", *argv); 17 return 1; 18 } else { 19 filecopy(fp, stdout); 20 fclose(fp); /*该函数断开fopen函数建立的文件指针和外部名之间的连接,并释放文件指针以供其他文件使用。 21 大多数操作系统都限制了一个程序可以同时打开文件数,当文件指针不在需要时就应该释放。 22 对输出文件执行fclose还有另外一个原因:它将把缓冲区中有putc函数正在收集的输入写到文件中。 23 当程序正常终止时,程序自动为每个打开的文件调用fclose函数。(如果不需要使用stdin和stdout,可以 24 把他们关闭掉。也可以通过库函数freopen重新指定它们。)*/ 25 } 26 return 0; 27 } 28 29 30 // filecopy: copy file ifp to file ofp 复制文件 31 void filecopy(FILE *ifp, FILE *ofp) 32 { 33 int c; 34 35 while ((c = getc(ifp)) != EOF) 36 putc(c, ofp); 37 }
7.6 错误处理--stderr和exit
1 #include <stdio.h> 2 3 //cat: concateate files, version 2 4 5 int main(int argc, char const *argv[]) 6 { 7 FILE *fp; 8 void filecopy(FILE *, FILE *); 9 char *prog = argv[0]; //program name for errors 10 11 if (argc == 1) //no args; copy standard input 12 filecopy(stdin, stdout); 13 else 14 while (--argc > 0) 15 { 16 if ((fp = fopen(*++argv, "r")) == NULL) { 17 fprintf(stderr, "%s: can't open %s\n", prog, *argv); 18 //fprintf函数产生的诊断信息输出到stderr上,因此诊断信息将会显示在屏幕上 19 //而不是仅仅输出到管道或输出文件中。诊断信息中包含argv[0]中的程序名, 20 //因此,该程序和其他此程序一起运行时,可以识别错误的来源。 21 exit(1); 22 /* 函数exit被调用时,将终止调用程序的执行。任何调用该程序的进程都可以获取exit的参数值, 23 因此,可通过另一个将该程序做为子进程的程序来测试该此程序执行是否成功。返回值为0表示正常, 24 而非0返回值通常为异常。exit为每个已打开的输出文件调用fclose函数,以将缓冲区中所有输出写 25 到相应的文件中。*/ 26 27 } else { 28 filecopy(fp, stdout); 29 fclose(fp); 30 } 31 } 32 if (ferror(stdout)) { //如果fp中出现错误,函数返回非0值。 33 fprintf(stderr, "%s: error writing stdout\n", prog); 34 exit(2); 35 } 36 exit(0); 37 return 0; 38 }
7.7 行输入和行输出
1 //标准库中fgets和fputs函数的代码 2 #include <stdio.h> 3 4 //fgets: get at most n chars from iop 从文件中获取多行 5 /* 标准库函数fgets从fp指向的文件中读取下一个输入行(包括换行符),并将它存放在字符数组line中, 6 最多可读取maxline-1个字符。读取的行将以'\0'结尾保存到数组中。通常情况下,fgets返回line,如果 7 遇到文件结尾或发生错误,则返回NULL*/ 8 char *fgets(char *s, int n, FILE *iop) 9 { 10 register int c; 11 register char *cs; 12 13 cs = s; 14 while (--n > 0 && (c = getc(iop)) != EOF) 15 if ((*cs++ = c) == '\n') 16 break; 17 *cs = '\0'; 18 return (c == EOF && cs == s) ? NULL : s; 19 } 20 21 //fputs: put string s on file iop 把字符串写进文件 22 23 int fputs(char *s, FILE *iop) 24 { 25 int c; 26 27 while (c = *s++) 28 putc(c, iop); 29 return ferror(iop) ? EOF : 0; /*ANSI标准规定,ferror发生错我时返回非0值,而fputs发生错误返回EOF 30 其他情况返回一个非负值。*/ 31 } 32 /* 输入函数fputs将一个字符串写入到一个文件中,如果发生错误,返回EOF,负责返回一个非赋值。 33 库函数gets和puts的功能与fgets和fputs函数类似,但它们是对stdin和stdout进行操作。有点我们需要注意, 34 gets函数在读取字符串时将删除结尾的换行符('\n'),而puts函数在写入字符串时将在结尾添加一个换行符。 35 */ 36 37 38 //getline: read a line, return length 返回行的长度,它为0时意味着已经到达文件的结尾。 39 int getline(char *line, int max) 40 { 41 if (fgets(line, max, stdin) == NULL) 42 return 0; 43 else 44 return strlen(line); 45 }
1 // 练习7-6 编写一个程序,比较两个文件并打印他们第一个不相同的行。 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 7 #define MAXLINE 100 8 9 //comp: compare two files, printing first different line 10 main(int argc, char *argv[]) 11 { 12 FILE *fp1, *fp2; 13 void filecomp(FILE *fp1, FILE *fp2); 14 15 if (argc != 3) { //incorrect number of arguments 不正确的参数 16 fprintf(stderr, "comp: need two file names\n"); 17 exit(1); 18 } else { 19 if ((fp1 = fopen(*++argv, "r")) == NULL) { 20 fprintf(stderr, "comp: can't open %s\n", *argv); 21 exit(1); 22 } else if ((fp2 = fopen(*++argv, "r")) == NULL) { 23 fprintf(stderr, "comp: can't open %s\n", *argv); 24 exit(1); 25 } else { //found and opend files to be compared 26 filecomp(fp1, fp2); 27 fclose(fp1); 28 fclose(fp2); 29 exit(0); 30 } 31 } 32 } 33 34 //filecomp: compare two files -a line at time 比较文件每次一行 35 void filecomp(FILE *fp1, FILE *fp2) 36 { 37 char line1[MAXLINE], line2[MAXLINE]; 38 char *lp1, *lp2; 39 40 do { 41 lp1 = fgets(line1, MAXLINE, fp1); //获取行,返回指针,指向行 每次fp1会指向下一个行 42 lp2 = fgets(line2, MAXLINE, fp2); 43 if (lp1 == line1 && lp2 == line2) { 44 if (strcmp(line1, line2) != 0){ 45 printf("first diffeence in line\n%s\n", line1); 46 lp1 = lp2 = NULL; 47 } 48 }else if (lp1 != line1 && lp2 == line2) //如果文件1读完了,文件2还有 49 printf("end of first file at line\n%s\n", line2); 50 else if (lp1 == line1 && lp2 != line2) 51 printf("end of second file at line\n%s\n", line1); 52 }while (lp1 == line1 && lp2 == line2); 53 }
/* 修改第五章模式查找程序,使它从一个命名文件的集合中读取输入(有文件名参数时),如果没有文件名 参数,则从标准输入中读取输入。当发现一个匹配行时,是否应该将相应的文件名打印出来? */ #include <stdio.h> #include <string.h> #include <stdlib.h> #define MAXLINE 1000 //find: print lines that match pattern from 1st argument 打印第一个匹配到的参数的行 int main(int argc, char *argv[]) { char pattern[MAXLINE]; int c, except = 0, number = 0; FILE *fp; void fpat(FILE *FP, char *fname, char *pattern, int except, int number); //第一个参数指针,指向文件或者输入,第二个文件名,第三个是要寻找的参数,第四,五个是信号 while (--argc > 0 && (*++argv)[0] == '-') //参数带-号 while (c = *++argv[0]) switch (c) { //挑选参数 case 'x': //除匹配到的那一行除外,不显示匹配到的那一行 except = 1; break; case 'n': number = 1; //输出行号标记 break; default: printf("find: illegal option %c\n", c); argc = 0; break; } if (argc >= 1) strcpy(pattern, *argv); else { printf("Usage: find [-x] [-n] pattern [file ....]\n"); exit(1); } if (argc == 1) //read standard input fpat( stdin, "", pattern, except, number); //如果没文件名,那么直接从你的输入中查找。 else while (--argc > 0) //get a named file 如果还有参数 if ((fp = fopen(*++argv, "r")) == NULL) { fprintf(stderr, "find: can't open %s\n", *argv); exit(1); } else { //named file has been opened fpat(fp, *argv, pattern, except, number); fclose(fp); } return 0; } //fpat: find pattern void fpat(FILE *fp, char *fname, char *pattern, int except, int number) { char line[MAXLINE]; long lineno = 0; while (fgets(line, MAXLINE, fp) != NULL) { //获取文件中的每一行 ++lineno; //用于记录哪一行 if ((strstr(line, pattern) != NULL) != except) { //在字符串line中查找第一次出现字符串pattern的位置,不包含终止符'\0',返回字符串中首次出现的地址。 if (*fname) //如果存在文件名则输出 printf("%s - ", fname); if (number) //print line number 输出行号 printf("%ld: ", lineno); printf("%s", line); //打印文件行 } } }
1 // 练习7-8编写一个程序,以打印一个文件集合,每个文件从新的一页开始打印,并且打印每个文件相应的标题和页数。 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 6 7 #define MAXBDT 3 //maximum # lines at bottom page 页面底部最大的行数 8 #define MAXHDR 5 //maximum # lines at head of page 页面顶部最大的行数 9 #define MAXLINE 30 //maximum size of one line 每行最多字符数 10 #define MAXPAGE 13 //maximum # lines on one page 最大页数 11 12 //print: print files - each new one on new page 打印文件中新的行在新的页面上 13 main(int argc, char *argv[]) 14 { 15 FILE *fp; 16 void fileprint(FILE *fp, char *fname); 17 18 if (argc == 1) //no args: print standard input 19 fileprint(stdin, " "); 20 else 21 while (--argc > 0) 22 if ((fp = fopen(*++argv,"r")) == NULL) { 23 fprintf(stderr, "print: can't open %s\n", *argv); 24 exit(1); 25 } else { 26 fileprint(fp, *argv); 27 fclose(fp); 28 } 29 return 0; 30 } 31 32 //fileprint: print file fname 33 void fileprint(FILE *fp, char *fname) //一个参数指向打开的文件,另一个指向文件的文件名字符串 34 { //函数fileprint读入文本行并打印输出。 35 int lineno, pageno = 1; //变量lineno记录着已经在某页纸上打印了多少行。页面最大长度是MAXPAGE行。 36 //每当lineno变为1时,fileprint将输出一个换页符,在新的页面上打印标题,然后重置lineno 37 char line[MAXLINE]; 38 int heading(char *fname, int pageno); 39 lineno = heading(fname, pageno++); 40 41 42 while (fgets(line, MAXLINE, fp) != NULL) { 43 if (lineno == 1) { 44 fprintf(stdout, "\f"); //字符\f是换页符。 45 lineno = heading(fname, pageno++); 46 } 47 fputs(line, stdout); 48 if (++lineno > MAXPAGE - MAXBDT) 49 lineno = 1; 50 } 51 fprintf(stdout,"\f"); //page eject after the file 52 } 53 54 //函数heading先打印文件名和页码,然后用足够的换行符在每页的开头流出总共MAXHDR行。MAXBOT是为每页末尾保留的空白行数。 55 //heading: put heading and enough blank lines 56 int heading(char *fname, int pageno) 57 { 58 int ln = 3; 59 60 fprintf(stdout,"\n\n"); 61 fprintf(stdout,"%s page %d\n", fname, pageno); 62 while (ln++ < MAXHDR) 63 fprintf(stdout, "\n"); 64 return ln; 65 }
7.8 其他函数
7.8.1 字符串操作函数
函数strlen、strcpy、strcat 和 strcmp,他们都在头文件<string.h>中定义。在下面各个函数中,s与t为char *类型,c与n为int类型。
7.8.4 函数system(char* s)执行包含在这字符串s中的命令,然后继续执行当前程序。s的内容在很大程度上与所用的操作系统有关。
system("date"); 执行程序date,标准输出打印当天的日期和时间。system函数返回一个整型的状态值,其值来自于执行的命令,并同具体新系统有关。UNIX中,返回的状态是exit的返回值。
7.8.5 存储管理函数
函数malloc和calloc用于动态的分配存储块。
void *malloc(size_t n); 当分配成功时,返回一个指针,指针指向N字节长度的末尾初始化的存储空间,否则返回NULL
void *calloc(size_t n, size_t size); 分配成功时,返回指针,该指针指向的空闲空间足以容纳由n个指定长度的对象组成的数组,否则返回NULL。该存储空间被初始化为0.
free(p)函数释放p指向的存储空间,其中,p是此前通过调用malloc或calloc函数得到的指针。存储空间的释放顺序没有什么限制,但是,如果释放一个不是通过调用malloc或calloc函数得到的指针所指向的存储空间, 将是一个严重的错误。
7.8.7 随机数发生器函数
函数rand()生成介于0和RAND_MAX之间的伪随机整数序列。其中RAND_MAX实在头文件<stdlib.g>中定义的符号常量。下面是一种生成大于等于0但小于11的随机浮点方法。
#define frand() ((double) rand() / (RAND_MAX+10))
函数srand(unsigned)设置rand函数的种子数。
1 // 练习7-9 类似于isupper这样的函数可以通过某种方式实现以达到节省空间或时间的目的。考虑节省空间或时间的实现方式。 2 3 //isupper: return 1 (true) if c is upper case letter 如果c是大写 返回真 4 5 6 int isupper(char c) 7 { 8 if (c >= 'A' && c <= 'Z') 9 return 1; 10 else 11 return 0; 12 } 13 14 #define isupper(c) ((c) >= 'A' && (c) <= 'Z') ? 1 : 0 //这个版本isupper函数时间效率较高,但要使用较多的空间 15 //节约时间是因为它没有函数调用方面的开销, 占用空间是因为它是宏,每次执行都要展开。 16 17 //使用宏版本存在的问题 18 // char *p = "this is a string"; 19 // if (isupper(*p++)) 20 // ... 21 //将被展开为((*p++) >= 'A' && (*p++) <= 'Z) ? 1:0

浙公网安备 33010602011771号