一个C语言初学者写一个查找替换程序的历程

转载请注明来源 http://blog.csdn.net/imred/article/details/26058847

        本人大一,正式接触C语言刚刚三个月,基础不牢,在写一道作业时走了许多弯路,也有许多收获,分享给大家。

        作业题目如下“编写一个程序repl,它用命令行指定的字符串替换命令行指定文件中的单词。例如,命令行:repl file.txt you they  将用you替换file.txt文件中所有单词they。”

        此时我们刚刚学到“文件的输入与输出”一章,老师说这章内容许多要自学,主要是stdio.h中的一些函数,我草草看了看,就开始胡乱的敲起了我的代码。

        我首先想到的方法如下(哈哈,不要笑话我啊,原来真的没怎么学过C语言):我感觉创建临时文件的方法比较麻烦(其实并不麻烦),就想着在原文本文件中直接进行查找替换(自然不大可能),先用一个find函数查找要被替换的字符串所在位置,然后返回一个文件指针,再用一个repl函数完成后续的替换操作。这种方法的最终成品如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#define DEBUG
FILE *find(FILE *fp,char *s,int m);     /*m为被替换字符串字符数*/
FILE *repl(FILE *fp,char *s,int m,int n);     /*m为被替换字符串字符数,n为替换字符串字符数*/
int main(int argc,char *argv[])
{
    if(argc!=4)
    {
        printf("Argument error!\n");
        exit(-1);
    }
    FILE *fp,*tmpfp;
    if((fp=fopen(argv[1],"rb+"))==NULL)
        exit(-1);
    for(;;)
    {
        if((find(fp,tmpfp,argv[3],sizeof(argv[3])))==NULL)
            break;
        repl(fp,tmpfp,argv[2],sizeof(argv[3]),sizeof(argv[2]));
    }
    fclose(fp);
    return 0;
}
FILE *find(FILE *fp,char *s,int m)
{
    char ch[m];
    long i;
    for(i=1;feof(fp)==0;)
    {
#ifdef DEBUG
        printf("find %d %d %#p\n",feof(fp),i,fp);
#endif // DEBUG
        if(feof(fp)!=0)
            return NULL;
        fpos_t pos,*ptr=&pos;
        fgetpos(fp,ptr);
        fread(ch,1,m-1,fp);
        ch[m-1]=0;
        fsetpos(fp,ptr);
        if(strcmp(ch,s)==0)
            return fp;
        else
            fseek(fp,i++,SEEK_SET);
        if(fgetc(fp)==EOF)
            return NULL;
    }
    return NULL;
}
FILE *repl(FILE *fp,char *s,int m,int n)
{
#ifdef DEBUG
    printf("repl\n");
#endif // DEBUG
    char empty[1]="\b";
    int i;
    fpos_t pos,*ptr=&pos;
    fgetpos(fp,ptr);
    for(i=1;i<=m-1;i++)
    {
        fwrite(empty,1,1,fp);
        fflush(fp);
    }
    fsetpos(fp,ptr);
    fputs(s,fp);
    fseek(fp,n-1,SEEK_CUR);
    return fp;
}
        虽然这代码什么也干不了,但我为完成这个作业所用的四分之三的时间都用在了这个上面。弯路都走完了,就找着正路了。由于codeblocks貌似无法调试带参数的main函数(我自己尝试,Set Program's Arguments中的语句貌似只在程序运行中有效,和网上说的不太一样),我就用#ifdef与#endif之间的语句来大致了解程序运行的流程。

        我走过以下几处弯路:

        1、第15行我对文本文件的打开方式一开始是“r+”,是以文本模式打开的,这在使用fseek函数时就遇到了问题:文件读写指针无法偏移(一会你会知道,这是一个歪打正着的结论),《C Primer Plus》中有如下解释:在文本模式中,只要求保证如下这些调用有效:

fseek (file, 0L, SEEK_SET) 到文件开始
fseek (file, 0L, SEEK_CUR) 在当前位置不动
fseek (file, 0L, SEEK_END) 到文件结尾
fseek (file, ftell-pos, SEEK_SET)  到距文件开始处ftell-pos字节的位置,ftell-pos是ftell( )的返回值
        2、为什么说我上面那个结论是歪打正着呢,原因是这样的,我看到我的程序跑不好,我就增加了第35行的语句来进行跟踪,令人惊奇的是每次输出的的fp指向的地址都是相同的(当然这是正常的)。其实是我把文件指针和文件读写指针搞混了,fseek函数实际上是使文件读写指针发生了移动,并没有使文件指针发生移动。我输出fp指向的地址,自然不会有变化了。

        3、认为用空字符覆盖字符相当于删除字符操作,事实上在用记事本打开文件时,空字符为一种不可显示字符,显示效果和空格一样(使用WinHex打开可以看出来,notepad++也能看出,会将空字符显示为“NULL”)。

        4、认为在“rb+”打开方式下,fputs函数输出的内容不会覆盖后面的内容,只会插入,fwrite才会覆盖(真不知道当时怎么想的)。

        在动过无数次小手术没有成功后,我决定必须得动大手术了,就用创建临时文件的方法,把代码完全重写了(我一开始想建个大字符数组把字符全部读进去来着,后来觉着行不通,万一有个上GiB的文本文件呢),成品代码(其实这不是最终结果,最终结果我是用“wb”方式打开临时文件的,代码也有所变化,主要是不用理会'\r'的问题了)如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
FILE *copy(FILE *fp,FILE *tmpfp,char *s,int m);          /*m为被替换字符串字符数*/
FILE *insert(FILE *fp,FILE *tmpfp,char *s,int n);          /*m为被替换字符串字符数,n为替换字符串字符数*/
//#define DEBUG
int main(int argc,char *argv[])
{
    if(argc!=4)
    {
        printf("Argument error!\n");
        exit(-1);
    }

    FILE *fp,*tmpfp;
    if((fp=fopen(argv[1],"rb"))==NULL)
        exit(-1);
    if((tmpfp=fopen("tmp.txt","w"))==NULL)
        exit(-1);

    for(;;)
    {
        if(copy(fp,tmpfp,argv[3],strlen(argv[3]))==NULL)
            break;
        insert(fp,tmpfp,argv[2],strlen(argv[3]));
    }
    fclose(fp);
    fclose(tmpfp);
    remove(argv[1]);
    rename("tmp.txt",argv[1]);
    return 0;
}
FILE *copy(FILE *fp,FILE *tmpfp,char *s,int m)
{
#ifdef DEBUG
    printf("copy\n");
#endif // DEBUG
    char ch[m+1];
    for(;;)
    {
#ifdef DEBUG
        printf("for\n");
#endif // DEBUG
        fpos_t pos,*ptr=&pos;
        fgetpos(fp,ptr);
        fread(ch,1,m,fp);
        ch[m]=0;
        fsetpos(fp,ptr);
        fgetpos(fp,ptr);
        if(fgetc(fp)==EOF)
            return NULL;
        fsetpos(fp,ptr);
        if(strcmp(ch,s)==0)
            return fp;
        else
        {
#ifdef DEBUG
        printf("fprintf\n");
#endif // DEBUG
            if(ch[0]!='\r')
                fprintf(tmpfp,"%c",ch[0]);          /*由于windows平台文本模式打开方式输出'\n'时在前面自动添加一个'\r',此处输出'\r'的话会使'\r'多余*/
            fseek(fp,1L,SEEK_CUR);
        }
    }
}
FILE *insert(FILE *fp,FILE *tmpfp,char *s,int m)
{
#ifdef DEBUG
    printf("insert\n");
#endif // DEBUG
    fputs(s,tmpfp);
    fseek(fp,m,SEEK_CUR);
    return fp;
}
        最大的收获体现在第60、61行,我是这样发现的,在用程序处理完一个文本文件后,我想在每行的末尾添加一些字符继续测试,神奇的发现,用记事本打开添加后保存时行与行之间还不是一行,再次打开后吊诡的事情出现了:文本连成了一行!

        百思不得其解,我就用WinHex打开程序处理过的文件,很奇怪的,我用程序连续处理同一文本文件每处理一次,换行处 0A 前就多出一个 0D 不可显示字符,也就是字符‘\r', 0A 即为’\n',一旦换行处 0D 超过两个,在换行处用记事本打开再次添加字符就会使 0D 0A统统消失,导致的结果是文本连成了一行。于是我在程序中加入了60、61两行,作业完成啦!后来查阅其他书籍,得到了61行注释中的结论。

        这些收获虽然都是书本上有的,但当自己真正的体验到时,才能真正的感觉到书上所说的那些应该注意的地方究竟是什么意思。

        (第一次在这里写博文,错误的地方希望得到大家指正!)

转载请注明来源 http://blog.csdn.net/imred/article/details/26058847

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值