目录
前言
在前面的学习当中,C语言的基本语法已经基本学习完毕,但是大家都会发现一个问题,我们的代码编写似乎一直是表现为一个黑框框,这与我们生活中所看到的软件似乎有着很大的区别。当然,编程语言的作用不可能仅局限于编译器的黑框框,今天我们就来看看C语言的文件操作吧!
一、文件的作用与相关知识
1. 文件的作用
当我们编写一个程序的时候,程序中的数据会在代码相应的位置产生,并在文件结束时销毁。这是由于程序中的数据实际上是储存在内存上的,一但程序结束,内存空间被释放,数据也就丢失了。若需要在程序外将数据保存起来,则需要将数据储存在文件中。这是因为,文件并不是存储在内存中,而是存储在计算机的硬盘中。
2. 文件的定义
磁盘上的文件都是文件。 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。
程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境 后缀为.exe)。
数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件。
本篇主要讨论数据文件。
3.文件名
文件名由文件路径+名称+后缀组成,路径为文件在磁盘中的存放位置,可帮助用户找到该文件(类似于地址),后缀则确定了文件类型,例如”.txt“为文本文件后缀,”.c“为C语言程序文件后缀。
二、文件操作相关知识
1. 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名为FILE。
FILE* pf;
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
2. 数据流
数据流顾名思义,就是数据形成的流,简称流,内存中的数据均是从流中获取,并可输出至流中的。当然,流是可以打开和关闭的,以上操作只可在被打开的流中进行。
在一个C语言程序的编译过程中会默认打开三个流,分别是:stdin(标准输入流)、stdout(标准输出流)和stderr(标准错误流)。其中标准输入流即内存从键盘上获取数据的流,而标准输出流则是内存将数据输出到屏幕上的流。其它文件的流需要通过fopen函数手动打开。数据流其实本质上为FILE*类型的指针。
3. 读写
读写,即读取和写入。在文件操作中,我们以内存为主体,读指的是内存从流中读取数据,例如内存从标准输入流中读取键盘上输入的数据、从手动打开的文件流中读取文件中的数据。而写只的是内存向流中写入数据,例如内存向标准输出流中写入的数据展现在屏幕上。
4. 文本文件与二进制文件
文本文件中,数据以字符串格式存储,它的优点是文件的可视化程度高,可直观地向用户展示数据内容。而二进制文件则将数据以二进制格式存储,无法直观地看出数据内容,其优点是占用空间较少。例如,当存储一个5位数"10000"时,文本文件需要占用5个字节的空间,储存为在字符串"10000",而二进制文件仅需占用4个字节,储存为二进制数"0010011100010000"转换为16进制表示为0x10270000(小端存储)。
三、文件操作方法
1. 文件的打开和关闭
当我们需要使用或修改一个文件中的内容时,我们需要打开这个文件的流,也就常说的打开文件。这时我们需要用到一个函数——fopen函数。
FILE * fopen ( const char * filename, const char * mode );
fopen函数的第一个参数为所需打开的文件的名称,第二个参数为打开方式,两个参数均为字符串。打开完毕后,若成功返回文件的地址,不成功则返回空指针。每种打开方式所对应的字符串需要查表,在这里我列举几个使用频率较高的。
打开方式 | 含义 |
"w"(只读) | 为了输入数据,打开一个已经存在的文本文件 |
"r"(只写) | 为了输出数据,打开一个文本文件 |
"a"(追加) | 向文本文件尾添加数据 |
"wb"(只读) | 为了输入数据,打开一个二进制文件 |
"rb"(只写) | 为了输出数据,打开一个二进制文件 |
"ab"(追加) | 向一个二进制文件尾添加数据 |
当我们使用完一个文件后,最好使用fclose函数将其关闭,以防止一些无意间的操作导致文件内容损坏。
int fclose ( FILE * stream );
fclose的参数为FILE*类型的指针,即文件的地址,若关闭成功则返回0,否则返回EOF。与动态内存管理一样,文件关闭后需要将文件指针置空。下面为一段完整的文件打开与关闭的代码。
int main()
{
FILE* pf = fopen("test.txt","w");
if(pf == NULL)
{
perror("fopen");
return 1;
}
//此时可进行数据读写操作
fclose(pf);
pf = NULL;
return 0;
}
2. 文件的顺序读写
这里以在字符读写为例,其它主要的读写方式与字符读写的方式相似,仅讲其特点。
2.1 字符读写
int fputc ( int character, FILE * stream );
int fgetc ( FILE * stream );
字符读写使用上方两个函数,fputc函数用来将字符(参数character)写入文件,fgetc函数用来读取文件中的字符。参数stream为文件地址。操作完成后,若操作成功,两个函数均返回其所操作的字符,若失败则返回EOF(文件结束标志,数值为-1)。另外,调用这两个函数均会使指针stream向后偏移一位。
下图为使用fputc函数将字符'a'写入文本文件"test.txt"的代码。图中使用fopen成功打开"test.txt"文件并用fputc函数将字符写入。可以看到运行的程序中并无任何输出,而文本文件"test.txt"中则出现了字符'a'。
我们再使用fgetc函数读取刚刚输入的字符'a',如下图。注意,需要将文件打开方式改为"r"。读取完成后使用printf函数打印结果为'a'。
这就是文件顺序读写的基本操作方式了,接下来我来介绍几种比较常用的读写方式。
2.2 文本行读写
int fputs ( const char * str, FILE * stream );
char * fgets ( char * str, int num, FILE * stream );
以上这一对函数为文本行读写函数,可用来读写以行为单位的字符串数据,以换行符'\n'为每次操作的终止符。其中fgets函数的参数num为内存从文件中读取字符个数的最大值,即当使用fgets从文件中读取字符串时,该字符串的长度不能超过num。实际应用效果如下图。
2.3 格式化读写
int fprintf ( FILE * stream, const char * format, ... );
int fscanf ( FILE * stream, const char * format, ... );
fprintf函数可将格式化数据转化为字符串形式写入文本,而fscanf函数则可将文本中的字符数据格式化并存储至变量中。实际应用效果如下图。
2.4 二进制读写
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
这一组函数与上面三组函数的适用范围稍有不同,这一组函数用来进行二进制读写,且只可读写文件数据流,不可读取标准流。而前三组均可读写所有数据流。
参数方面,ptr为数据起始地址。size为数据大小,count为数据个数。实际应用效果如下。注意,二进制写入时应以"wb" 方式打开,读取时应以"rb"方式打开。
3. 文件的随机读写
3.1 fseek
int fseek ( FILE * stream, long int offset, int origin );
由于在使用上述顺序读写函数时,文件指针所指向的位置会发生偏移,因此当我们对文件中位置靠前的数据进行操作后就难以再次找到它们了。此时,我们可以使用fseek函数来调整文件指针的位置。
参数中的offset为偏移量,即以文件指针当前位置为零偏移点,偏移offset个单位。若offset的值为正则向后偏移,若为负则向前偏移。参数origin用来确定零偏移点的位置,若输入SEEK_SET(一个常量)则以文件初始位置为零偏移点;若输入SEEK_CUR则以指针当前位置为零偏移点;若输入SEEK_END则以文件末尾为零偏移点。以下图代码为例(test.txt文件初始数据为"abcdef")。
循环调用三次fgetc函数,偏移量为3,并读取前三个数据"abc",使用fseek函数从当前位置偏移-2,所得位置偏移量为1,即第二个数据'b',故打印”abcb“。
3.2 ftell
long int ftell ( FILE * stream );
当我们想要使用fseek函数重新指定文件指针的位置时,有时可能并不确定文件指针当前的位置,这时我们可以使用ftell函数。ftell函数会返回文本文件中文件指针距离文件初始位置的偏移量。以下图代码为例。
调用fgetc函数三次,故偏移量为3,ftell函数返回值为3。
3.3 rewind
void rewind ( FILE * stream );
rewind函数用于将文件指针归于初始位置。用法较为简单,直接看代码。
这里在上方介绍ftell的代码上添加了一段rewind函数,并重新使用ftell函数计算偏移量,结果为0。
结束语
文件操作可以更好地保存数据,可以说是C语言作为一门高级编程语言为适应现代计算机程序的需求必须具有的功能,也是C语言程序员必须掌握的一项技术。