还记得以前学习C语言时,做结业的课程设计,做的是学生学籍管理系统什么的。当时有一条要求是,最后将结果以文件形式保存。可我硬是没弄明白“文件”形式到底是什么。哈哈,是的,当时我就那么傻。谭浩强的书,还没翻到“文件”那个地方,一学期就结束了。
的确,对于大多数教材,都将大量的精力放在基本语法的讲解上,而对我们实际非常关心的文件说得并不是那么详细。C语言的文件是什么呢?其实没有什么特别的,我们硬盘里的躺着的那么多txt、mp3、doc、avi的,不就是文件吗?~对,C语言里面说的文件和这个其实是一回事。
为了让大家相信,你可以编写一个这样的小程序。它将在程序当前目录下建立一个"example.txt"的文本文件。
#include <stdio.h>
int main()
{
FILE *fp=fopen("example.txt","w"); //文件名别忘了后缀啊~~~
if(!fp)return -1; //若打开失败的话……
fprintf(fp,"这是一个文本文件!/n");
fclose(fp);
return 0;
}
在Dev-C++下编译然后运行(其他编译器应该也可以,不过我没有试过。以后各程序,如果没有特殊说明的话,我都是使用Dev-C++编译的)。你可以看见一个命令提示符的“黑框框”一闪而灭。这是程序运行过的标志。不过我们在这里并不需要这个“闪现”的黑框。所以我们可以在工具>>编译选项>>代码生成/优化>>连接器 里找“不产生控制台窗口”。将后面设置为“Yes”。好了,重新编译运行。我们可以发现,这次黑框不见了~
回过头来,我们可以发现,在程序所在的目录下,多了一个叫做"example.txt”的文本文件。双击打开看看。“这是一个文本文件!”恩,对了。我想大家已经把C语言里的文件和硬盘里躺着的文件联系到一起了吧?
好了,扫误区就到这里。现在要说的是病毒的自我复制功能的实现。其实这个问题,可以简化为二进制文件的复制问题。那么如何实现二进制文件的复制呢?如果大家学过谭浩强的“文件”的话,就知道w、r是以ASC方式读写字符,而wb、rb就是以二进制方式读写。这个就是我们需要的。至于思路,很简单:
以rb方式打开欲复制的文件A,再以wb方式新建一个目的文件B。然后,从A中读取字符,写入B文件就好了。还有一点,C语言的文件的文件名其实是可以包含路径的,由于‘/’被作为转义字符,所以需要使用‘//’才能用来描述文件的路径。比如,一个C:/WINDOWS/system32的explorer.exe(资源管理器),我们需要用这样的字符串表示来表示它:const char *path="C://WINDOWS//system32//explorer.exe"。
光说不练假把式。下面我们来看一个例子。看完大家就明白了。下面这个代码我们将名字命名为selfcopy.c,也就是说,编译后将产生一个名为selfcopy.exe的可执行程序。程序的执行,是将这个程序自身复制到C:/WINDOWS/文件夹下。
#include <stdio.h>
const char *path_name="C://WINDOWS//selfcopy.exe";
const char *self="selfcopy.exe";
int main()
{
char tmp;
FILE *from,*to;
if(!(from=fopen(self,"rb")))return -1; //以二进制只读方式打开原文件
if(!(to=fopen(path_name,"wb")))return -1; //以二进制只写方式新建目的文件
while(!feof(from))
{
tmp=fgetc(from); //从原文件读一个字符到内存
fputc(tmp,to); //将内存中的字符写入到目的文件
}
fclose(from);
fclose(to);
return 0;
}
怎么样?够简单吧?你可能会说,我怎么知道复制过去的就是本身这个程序呢?你可以将C/WINDOWS/下的selfcopy.exe剪切下来,覆盖原来的编译产生的程序,看看结果是不是还是一样呢?我想你可以得到一个满意的答案。
当然,这里的文件如果过大的话,这样一个字符一个字符的读写,其实是一个比较耗时的做法。原因是因为读取一次硬盘是一个非常复杂的过程,硬盘寻道也非常耗时。值得庆幸的是,硬盘寻道寻找到的是一个扇区,而一个扇区一般存储空间是512字节。这就意味着,如果我们需要节省时间的话,我们没必要一个字节一个字节的读写内容,而是以512字节为基本单位。那么有人也许会问,如果这个文件不是512字节的整数倍,那么最后一次读入的时候会发生什么情况呢?呵呵,细心的孩子~ 不过这个问题其实C语言已经很好的解决了。我们需要使用的是两个函数:fread()和fwrite()。fread将实际读入的字符数作为返回值返回,我们接收到这个返回值,就可以确定我们应该用fwrite实际写入多少个字符。于是,上面那个程序的改良版如下:
#include <stdio.h>
const char *path_name="C://WINDOWS//selfcopy.exe";
const char *self="selfcopy.exe";
int main()
{
char tmp[512]; //作为缓冲区
unsigned int count; //用于计数实际读入的字符数
FILE *from,*to;
if(!(from=fopen(self,"rb")))return -1;
if(!(to=fopen(path_name,"wb")))return -1;
while(!feof(from))
{
count=fread(tmp,1,512,from); //计划读入一个扇区
fwrite(tmp,1,count,to); //写入实际读入的字符数
}
fclose(from);
fclose(to);
return 0;
}