文件简介
文件在程序设计中是一个比较重要的概念,这里所说的文件,是指保存在硬盘、U盘等存储介质上的数据,这些存储介质(简称磁盘)上的数据就是以一个个文件的形式体现,每一个文件有一个对应的名字,称为文件名。操作系统也是以文件为单位对数据进行管理,例如想在磁盘上找数据,需要先按照文件名在该磁盘上找到对应文件,然后把文件中的数据读出来。如果要把数据写到磁盘上,也必须先在磁盘上建立一个文件,然后向这个文件中写入数据。
以往程序执行输出的结果信息都是输出到屏幕上,而在程序执行中输入数据是通过键盘用诸如scanf等函数来输入。随着项目越来越庞大,编写的程序功能越来越复杂,不可避免地会将一些数据放到磁盘上长期存储,以后需要这些数据时再从磁盘上把数据读回到计算机内存中
,而针对磁盘数据的存取操作,就要用到磁盘文件功能了。
文件可以看成字符序列,例如,“abcdefg”是一个字符串,也是一个字符序列,它是由一个一个的字符顺序排列组成,把这些数据存储到磁盘上,就形成一个文件。
文本文件或者二进制文件是对于人类来说的,而对于计算机,并不区分是什么类型的文件,保存的都是二进制数据,所以,当用文本形式打开文件时,系统会多做一个工作,就是把二进制数据转换成人类能看懂的ASCII码数据。
现在,再说一说对二进制文件的理解。看如下代码:
short int a;
int ilen = sizeof(a);//2
a=10000;
printf("断点掐在这里观察\n");
把断点设置在printf所在的行并开始调试跟踪,当断点停到printf行时,用前面章节所讲解的办法查看变量a在内存中的内容,如图12.7所示。
文本文件和二进制文件区别详细解释
根据数据组织形式,把文件分为两种:文本文件(ASCII文件)与二进制文件。
· 文本文件:也称为ASCII文件,文件中的每个字节存放一个ASCII码,代表一个字符,这种文件在打开后能够直接看懂其中的内容。
· 二进制文件:把内存中的数据按照其在内存中的存储形式原样输出到磁盘上存放。这种文件中一般会有很多不可见字符,打开后看到的可能是一堆乱码。
文件的打开
文件在进行读或者写之前,必须要先打开,在读或者写结束之后,必须要关闭,否则会造成资源泄漏或读写失败。文件的打开要调用fopen函数。一般形式如下:
看如下代码:
FILE* fp;
fp = fopen("Al","r"); //打开名字叫"A1"的文件,""表示文件使用方式为只读
这里注意到,fopen函数返回一个指向A1文件的指针,这个指针被赋值给了fp。这样,就可以认为fp指向了A1文件。
FILE结构体:每次用fopen函数打开一个文件,系统都会开辟出一块内存,这块内存大小是sizeof(FILE),这块内存用来存放和文件相关的信息,诸如文件名、文件使用方式、当前文件位置等。
通过调用fopen函数,可以告诉系统三个信息:
1)需要打开的文件名。
2)文件使用方式,如是读还是写,“r”表示读,后面会讲到。
3)让哪个指针变量指向被打开的文件,这里是fp指针变量。
文件使用方式(只包含一部分)如表12.1所示。
表12.1中的内容不必死记硬背,需要时查阅即可。但这里可以记几个比较常用的,如r、w、a。当然,也有一些规律可循:
(1)有w表示往文件中写入。
(2)有r表示从文件中读出。
(3)有b表示二进制文件。
(4)有a表示追加内容到文件末尾。
(5)有“+”表示既能从文件中读出,又能往文件中写入。
此外,还需要知道的是,每个打开的文件都有一个当前位置指针,其实就是保存在FILE结构里的一个char*型的字符指针(不同版本编译器可能细节不同,但道理都相同)。这个位置指针的用途就是代表当前从文件的哪个位置开始读/写数据,对于读来讲,每读出1字节数据,这个位置指针会自动往后移动1字节,以指向下一字节,这样,下次再读时自然就从下一字节开始。
当用fopen函数打开文件时,有可能打开失败,例如文件不存在时一定会打开失败,此时fopen函数就会返回一个空(NULL)。看如下代码:
文件的关闭
文件只有在成功用fopen函数打开之后,才存在文件关闭的问题,否则,不存在关闭的问题。在用fopen函数打开文件成功后,一般都会对文件进行读写操作,读写完毕之后,应该关闭这个文件。为什么要关闭这个文件呢?有两个原因:(1)释放这个文件占用的内存资源,如果资源使用后却不释放,那么,当资源耗尽就会导致程序运行崩溃,所以必须养成资源用完后及时释放的好习惯。
(2)往文件中写数据时不会立即往磁盘上写,系统会把数据写到一个叫“缓冲区”的地方,缓冲区满时系统才往磁盘文件上写
,写完之后把缓冲区清空继续等待用户往文件中写数据。试想,当把数据往文件中写时,假如缓冲区此时没满,那么在没有关闭文件的情况下退出了程序的运行(或者突然停电导致计算机关机),那么缓冲区中的数据就没来得及写到磁盘文件上,造成数据丢失。关闭文件这个动作会触发系统把缓冲区中的数据立即写到磁盘上,这就避免了缓冲区中的数据丢失问题。
所以,打开的文件在不使用时及时关闭,非常有必要。文件的关闭要调用fclose函数,一般形式如下:
if(fp != NULL)
fclose(fp); //fp就是fopen函数的返回值(文件指针)
文件关闭后,fp就不能再被使用(读/写文件),否则程序会报异常。fclose()函数有返回值,一般0表示关闭成功,非0表示关闭失败,但该返回值用处不大(例如如果文件关闭失败,再关闭一次应该也还会失败),所以一般不用去理会这个返回值。
文件的读写
文件读写有许多相关的函数,这里介绍几个常用的。
(1)fputc函数:用于把一个字符写到磁盘文件。调用形式看如下代码——将字符ch输出到fp所指向的文件中:
如果fputc执行成功,返回值就是输出字符的ASCII码值;如果执行失败,返回EOF。EOF是EndOfFile(文件末尾)的缩写,是系统提供的一个宏定义,代表-1。如下:
看看如下范例:
FILE *fp; //FILE是一个结构,fp是指向结构FILE的指针变量(文件指针)
fp = fopen("FTest.txt","w"); //为写而打开文件
if (fp == NULL)
{
printf("文件打开失败\n");
}
else
{
//文件打开成功才走这里
char reco = fputc('a',fp);
if (reco == EOF) //注意fputc失败时,会返回EoF
{
//写失败时的处理代码
}
else
{
reco = fputc('d',fp); //这里并没有判断是否写成功,不建议这样写,不安全
reco = fputc('e',fp); //这里依旧没有判断是否写成功
fclose(fp); //文件打开成功的情况下,应该及时关闭文件
}
}
在上面的范例中有几点说明:
· 当执行到fopen这行代码时,文件写入方式指定为w,表示为写而打开一个文本文件FTest.txt,FTest.txt刚开始是不存在的,因此,如果fopen执行成功,则系统会创建该文件,如果该文件存在,则fopen执行成功时该文件中原始的内容会被覆盖。
· 调用fopen函数时可以指定所要打开的文件路径,但范例中并没有指定路径,系统可能会到当前项目所在的目录(通过Visual Studio来运行时)或该可执行程序目录(双击直接运行可执行程序)寻找FTest.txt文件并尝试打开,如果该文件并不存在,系统会在当前项目所在的目录或该可执行程序所在的目录下创建FTest.txt文件。
(2)fgetc函数:用于从指定文件读入一个字符。调用形式看如下代码——从fp所指向的文件中读入一个字符到reco中:
char reco = fgetc(fp);
如果fgetc执行成功,则返回读入的字符;如果执行失败或者整个文件读到末尾,则返回EOF。看看如下范例:
FILE *fp; //FILE是一个结构,fp是指向结构FILE的指针变量(文件指针)
fp = fopen("FTest.txt","r"); //文件刚打开,文件当前位置指针指向开头
if (fp == NULL)
{
printf("文件打开失败");
}
else
{
//文件打开成功才走这里
char reco = fgetc(fp);
//每读一个字符,文件当前位置指针自动向下走一个字符
while (reco != EOF){ //读入失败或者到文件结束这个条件都成立
putchar(reco); //在屏幕上输出一个字符
reco = fgetc(fp); //再次读一个字符
}
fclose(fp);
}
上面的范例有个弊端,该范例中是用EOF(-1)来判断读入的内容是否到达文件结束。但一旦该文件中真存在一个值为-1的字符(该字符的十六进制是FF,用fgetc读入进来就是-1),那么,用EOF这种判断方式来判断是否读到文件结束,就会出现错误,所以需要换一种范例写法,引入feof函数。
(3)feof函数:用来判断文件是否结束(文件当前位置指针是否指向文件末尾)。调用形式看如下代码:
feof(fp);
如果文件结束,则返回1;如果文件没结束,则返回0。不管使用fopen函数时是以什么样的文件使用方式打开文件(例如用r还是用rb都没关系),feof函数都能够正确地判断文件是否结束。改造一下上面的范例代码,只需要修改范例中的while语句所在行,其他代码行不需要做任何修改。把while语句行修改为如下即可:
while(!feof(fp)) //文件没有结束
上面的范例中,利用fopen函数打开文件时,如果在“文件使用方式”参数中增加b选项(打开一个二进制文件),代码如下:
fp = fopen("FTest.txt","rb");
执行程序后会发现,整个程序的运行结果并没有发生什么改变,也就是说,是否增加b选项看起来并没有什么作用。和文件操作相关的函数有不少,但多数都很少用到,不值得花费太多时间去学习,读者有了上面学习的基础,也就有了进一步研究其他文件相关函数的能力,在需要的时候可以自行研究。
文件读写实战操练
这里将展示一个有实用价值的功能:游戏在线升级功能中的配置文件读取功能。这种读取功能适合于读取各种配置文件,请读者举一反三,达到最好的学习效果。网络游戏中经常用到游戏在线升级功能,该功能的工作原理是:从网络上下载一个游戏最新的配置文件到本地(配置文件一般都是文本文件),读取这个文件,然后跟原来的本地配置文件内容比较,通过发现的差异来决定是否进行游戏的在线升级功能。
如下就是一个配置文件:
该配置文件一共有7行,其中第2行是一个版本信息描述,正是通过这个信息的比较,确定是否进行游戏的在线升级。要进行这样的演示:假定该配置文件已经下载到当前文件夹下,现在需要把这个文件一行一行读出来,显示在屏幕上。因为该文件的每一行就是一条相对完整的信息,看看如何利用文件相关的函数来读取该文件。当前,配置文件内容已经保存到config.txt文件中,以二进制的方式在Visual Studio中打开config.txt并进行观察,如图12.12所示。
未完。。。