(十五)文件 -- 3. 语言中文件的使用

3.字符I/O

文件处理的最简便的方法是逐字符地遍历该文件。

stdio.c接口定义了getc(infile)函数,该函数从某一文件中读取下一个字符,并将其返回给调用函数。

同时还定义了getchar()函数,该函数从标准输入文件中进行读取,因此与getc(stdin)作用相当。


getc的的函数原型如下所示:

int getc(FILE *infile);

初看之下,函数返回值的类型是很奇怪的。函数原型指定getc返回一个整型值,虽然该函数在概念上返回的是一个字符。

这样设计的原因是因为返回一个字符会使得程序无法识别文件结束标记。

字符编码一共只有256个,且一个数据文件中可能包含其中的任意值。因此没有一个值(至少没有char类型的值)可以用作文件结束标记。

扩展定义,使得getc返回一个整型值,这样的实现可以返回一个合法字符数据以外的值作为文件结束标记。

通常在stdio.h中这个值被称为EOFEOF有值-1。


常见错误:
getc返回一个整型值,而不是一个字符型的值。
如果用字符型的变量存储getc的结果,程序就检测不到文件结束标记。


如果要写入一个单独的字符,可以用函数putc(ch, outfile),它将第一个参数写入指定的输出文件中。

stdio.c还包括函数putchar(ch),其定义与putc(ch, stdout)相同。


作为getcputc用法的一个例子,可以用下文所示的copy file.c程序将一个文件拷贝到另一个文件中:

#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
#include "strlib.h"
#include "string.h"
#include "simpio.h"


/* Function Prototype */
static FILE *OpenUserFile(string prompt, string mode);
static void CopyFile(FILE *infile, FILE *outfile);


/* Main Program */
main() {
    FILE *infile, *outfile;
    infile = OpenUserFile("Old file: ", "r");
    outfile = OpenUserFile("New file: ", "w");
    CopyFile(infile, outfile);
    fclose(infile);
    fclose(outfile);

}


/* Function */
static FILE *OpenUserFile(string prompt, string mode) {
    char filename[20];
    FILE *result;

    while (true) {
        printf("%s", prompt);
        fgets(filename, 20, stdin);
        if (filename[strlen(filename)-1] == '\n') filename[strlen(filename)-1] = '\0';

        result = fopen(filename, mode);
        if (result != NULL) break;
        printf("Can't open the file %s \n", filename);
    }
    return result;
}

static void CopyFile(FILE *infile, FILE *outfile) {
    int ch;
    while ((ch=getc(infile))!=EOF) {
        putc(ch, outfile);
    }
}

copyFile中的while循环非常常用。

while循环的判断表达式通过嵌入式赋值语句将读入字符和检测文件结束标记的操作结合起来。



3.1 文件更新

上文的copyfile.c程序精确地用另一个文件名创建了一个文件的副本。

如果不是完全复制一个文件,也可以用同样的基本结构写一个程序,在读的同时对字符进行转换。

例如,以下循环将数据从infile复制到outfile,并将所有字符转化为大写形式:

while (true) {
    if ((ch=getc(infile))!=EOF) {
        putc(toupper(ch), outfile);
    }
}

但很多时候我们并不需要一个新的文件,我们只需要修改现有文件。

对现有文件进行修改的过程称为更新(update)该文件,但这个过程并不简单。

对大多数系统而言, 如果一个文件已经为进行输入而打开,就不允许再为输出打开。

根据不同系统上的不同文件执行方式,调用fopen会导致调用失败或毁坏原文件的内容。


更新一个文件最常用的办法是将新数据写入一个临时文件,在写好更新文件的所有内容后用这个临时文件替换原文件。

因此,如果要写一个程序将一个文件的字符全部转换为大写,该程序需要执行如下步骤:
(1) 打开原文件,以便输入。
(2) 打开一个临时文件,以便输出,临时文件名不能与原文件相同。
(3) 将输入文件复制到临时文件,并将所有小写字符用大写替换。
(4) 关闭这两个文件。
(5) 删除原文件。
(6) 用原文件的名字重新命名临时文件。


在编写实现这一策略的代码时,需要使用三个定义在stdio.h头文件中的函数——tmpnamremoverename

stdio.h接口中包括的函数tmpnam可以为临时文件自动生成文件名。文件命名的习惯因不同机器而异。

调用函数tmpnam(NULL) 会返回一个字符串,它的值适合作为该台机器上临时文件的文件名。

因此,创建并打开一个新的临时文件,代码实现如下:

temp = tmpnam(NULL);
infile = fopen(temp, "w");

删除一个文件只需调用函数remove(name)即可,此处的name是一文件名。

重命名一个文件,可以通过调用函数rename(old name, new name)完成。

和ANSI库中的很多其他函数一样,removerename在调用成功后返回0,调用失败后返回一个非零值。


这三个函数提供了编写程序ucfile.c所需的内容,该程序可将一个文件中的字符转换为大写字符。代码示例如下:

#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
#include "strlib.h"
#include "string.h"
#include "simpio.h"


/* Function Prototype */
static void UpperCaseCopy(FILE *infile, FILE *outfile);


/* Main Program */
main() {
    string filename, tmpname;
    FILE *infile, *outfile;

    printf("This program convers file to upper case.\n");

    while (true) {
        printf("File name: ");
        fgets(filename, 20, stdin);
        if (filename[strlen(filename)-1] == '\n') filename[strlen(filename)-1] = '\0';
        infile = fopen(filename, "r");
        if (infile != NULL) break;
        printf("File %s not found, try again.\n", filename);
    }

    tmpname = tmpnam(NULL);
    outfile = fopen(tmpname, "w");
    if (outfile == NULL) Error("Can't open temporary file.");

    UpperCaseCopy(infile, outfile);
    fclose(infile);
    fclose(outfile);

    if ((remove(filename)!=0) || (rename(tmpname, filename)!=0)) Error("Can't rename temporary file.");
}


/* Function */
static void UpperCaseCopy(FILE *infile, FILE *outfile) {
    int ch;
    while ((ch=getc(infile))!=EOF) {
        putc(toupper(ch), outfile);
    }
}


3.2 在输入文件中重新读取字符

从一个输入文件中读取数据时,我们经常会遇到这样的问题,即直到读取了多余的字符后才发现早就应该停止读取了。

例如,假设从一个文件读取字符,以找出一个由十进制数组成的数字。

因此,不断读取字符直到非数字字符出现的循环如下所示(使用ctype.h中的isdigit函数):

while (isdigit(ch=getc(infile)))...

当读到第一个非数字字符时我们才发现已经找到了一个十进制数。

这时该非数字字符就成为循环结束的标志,但它也很可能是下一次读文件时所需的值。

通过调用getc,已经将该字符读到变量ch中,并已经将它从输入流中取出了。


C语言提供了函数ungetc(ch, infile),该函数的作用是将字符ch“推回”到原来的输入流中,作为下一次调用getc的返回值。

为了理解函数ungetc是如何使用的,假设需要编写一个程序:
将程序从一个文件复制到另一个文件,在此过程中删除所有的注释语句。

在C语言中,一条注释语句以字符序列“/*”开始,以序列"*/”结束的。

删除注释语句的程序必须能在检测到开始标记“/*”前复制字符,在检测到后逐一读字符但不进行复制,直至检测到结束标记“*/”为止。

这个问题的难点在于注释标记是由两个字符组成的。

每次从文件中复制一个字符,当遇到“/”时,只有读入下一个字符后我们才能进行判断。

如果下一个字符是“*”,那么将两个字符都忽略掉,并标识出已经进入注释语句。

如果不是“*”,则需将其推回到输入流中,留待下一个循环周期中再复制它。

函数CopyRemovingComments的代码实现如下:

#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
#include "strlib.h"
#include "string.h"
#include "simpio.h"


/* Function */
static void CopyRemovingComments(FILE *infile, FILE *outfile) {
    int ch, nch;
    bool CommentFlag;

    CommentFlag = FALSE;
    
    while ((ch=getc(infile))!=EOF) {
        if (CommentFlag) {
            if (ch=='*') {
                nch = getc(infile);
                if (nch=='/') {
                     CommentFlag = FALSE;
                } else {
                    ungetc(nch, infile);
                }
            }
        } else {
            if (ch=='/') {
                nch = getc(infile);
                if (nch == '*') {
                    CommentFlag = TRUE;
                } else {
                    ungetc(nch, infile);
                }
            }
        }
        if (!CommentFlag) putc(ch, outfile);
    }
}




参考

《C语言的科学和艺术》 —— 15 文件

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值