转载请注明来源 http://blog.youkuaiyun.com/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( )的返回值 |
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.youkuaiyun.com/imred/article/details/26058847