凡是用过计算机的人都不会对“文件”感到陌生,大多数人都接触过或使用过文件,例如:写好一篇文章把它存放到磁盘上以文件形式保存;编写好一个程序,以文件形式保存在磁盘中;用数码相机拍照,每一张相片就是一个文件;随电子邮件发送的“附件”就是以文件形式保存的信息。需要时就从文件读取信息。在程序中使用文件之前应了解有关文件的基本知识。
什么是文件?
文件有不同的类型,在程序设计中,主要用到两种文件。
程序文件
包括源程序文件(后缀为.c)、目标文件(后缀为.obj)、可执行文件(后缀为.exe)等。这种文件的内容是程序代码。
数据文件
文件的内容不是程序,而是共程序运行时读写的数据,如在程序运行过程中输出到磁盘(或其他外部设备)的数据,或在程序运行过程中供读入的数据。如一批学生的成绩数据、货物交易的数据等。
主要讨论数据文件。
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。文件标识包含三个部分:文件路径、文件名主干、文件后缀。
如:
为方便起见,文件标识常被称为文件名,但应了解此时所称的文件名,实际上包括以上三部分内容,而不仅是文件名主干。
文件类型
根据文件的组织形式,数据文件可分为ASCII文件和二进制文件。数据在内存中是以二进制形式存储的,如果不加转换地输出到外存,就是二进制文件,可以认为它就是存储在内存的数据的映像,所以也称之为映像文件。如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件又称为文本文件,每一个字节放一个字符的ASCII代码。
文件缓冲区
ANSI C标准采用“缓冲文件系统”处理数据文件,所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区。从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据取(给程序变量)。缓冲区的大小有各个具体的C编译系统确定。
文件类型指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为FILE。
例如Visual Studio 2013编译环境提供的stdio.h头文件中有以下的文件类型声明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
不同的C编译系统的FILE类型包含的内容不完全相同,但大同小异。对以上结构体中的成员及其含义可不深究,只需知道其中存放文件的有关信息即可。可以看到:FILE是以上结构体类型的typedef名称,FILE与上面的结构体类型等价。
声明FILE结构体类型的信息包含在<stdio.h>中,在程序中可以直接用FILE类型名定义变量。每一个FILE类型变量对应一个文件的信息区,在其中存放该文件的有关信息。
例如:可以定义以下FILE类型的变量:
FILE f1;
定义了一个结构体变量f1,用它来存放一个文件的有关信息。这些信息是在打开文件时由系统根据文件的情况自动放入的,用户不必过问。
一般不对FILE类型变量命名,也就是不通过变量的名字来引用这些变量,而是设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。这样使用起来方便。
下面定义一个指向文件类型数据的指针变量:
FILE* fp;
定义fp是一个指向FILE类型数据的指针变量。可以使fp指向某一个文件的文件信息去(一个结构体变量),通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
文件操作接口介绍
FILE * fopen ( const char * filename, const char * mode );
参数:
filename:为文件名。
mode:为打开方式。
返回值:指向该文件的一个FILE*类型的指针。
文件打开方式 | 含义 | 如果指定文件不存在 |
---|---|---|
"r"(只读) | 为了输入数据, 打开一个已经存在的文本文件 | 出错 |
"w"(只写) | 为了输出数据, 打开一个文本文件 | 建立一个新的文件 |
"a"(追加) | 向文本文件尾添加数据 | "出错" |
"rb"(只读) | "为了输入数据, 打开一个二进制文件" | 出错 |
"wb"(只写) | 为了输出数据, 打开一个二进制文件 | 建立一个新的文件 |
"ab"(追加) | 向一个二进制文件尾添加数据 | 出错 |
"r+"(读写) | 为了读和写, 打开一个文本文件 | 出错 |
"w+"(读写) | 为了读和写, 新建一个文件 | 建立一个新的文件 |
"a+"(读写) | 打开一个文件, 在文件尾进行读写 | 建立一个新的文件 |
"rb+"(读写) | 为了读和写打开一个二进制文件 | 出错 |
"wb+"(读写) | 为了读和写, 新建一个二进制文件 | 建立一个新的文件 |
"ab+"(读写) | 打开一个二进制文件, 在文件尾进行读和写 | 建立一个新的文件 |
int fclose ( FILE * stream );
参数:
stream:指向FILE对象的指针,指定要关闭的流。
返回值:如果成功关闭,返回值为0。失败时,返回EOF。
注意:对文件的操作完成后要及时关闭,不然会造成句柄的泄漏(资源泄露/文件描述符泄漏)。
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数:
ptr:为一块内存,用来存储读取的内容,即输出。
size:为要读取的元素的大小。
count:为要读取的元素的数量。
stream:为文件指针,即输入。
返回值:为成功读取的元素的个数。
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
参数:
ptr:指向一块内存,即输入。
size:为要写的元素的大小。
count:为要写的元素的数量。
stream:为文件指针,即输出。
返回值:成功写入元素的个数。
文件的顺序读写
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
文件的随机读写
int fseek ( FILE * stream, long int offset, int origin );
参数:
stream:文件指针。
offset:偏移量,二进制文件:从原点偏移的字节数;文本文件:0或者ftell返回的值。
origin:原点。
返回值:如果成功,返回0;失败,返回非0值。
功能:根据文件指针的位置和偏移量来定位文件指针。
long int ftell ( FILE * stream );
功能:返回文件指针相对于起始位置的偏移量。
参数:
stream:文件指针。
返回值:成功时,返回位置指示器的当前值;失败时,返回-1L,并将errno设置为系统特定的正值。
void rewind ( FILE * stream );
参数:
stream:文件指针。
功能:让文件指针回到文件的起始位置。
文件结束判断
int feof ( FILE * stream );
功能:检测流上的文件结束符。
参数:
stream:文件流指针。
返回值:如果文件结束,返回非0值,否则返回0。
注意:该函数用在文件读取结束后,判断是读取失败还是遇到文件尾结束。
不能用在文件读取过程中判断文件读取是否结束。
文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)。
二进制文件的读取结束判断,fread判断返回值是否小于实际要读的个数。
文件结束判断代码演示
文本文件
#include <stdio.h>
#include <stdlib.h>
int main(){
int c;
FILE* fp = fopen("temp.txt", "r");
if(!fp){
perror("file open error");
exit(-1);
}
while((c = fgetc(fp)) != EOF){
putchar(c);
}
if(ferror(fp)){
puts("I/O error when reading");
}
else if(feof(fp)){
puts("End of file reached successful");
}
fclose(fp);
return 0;
}
运行结果
[sss@aliyun file]$ !gcc
gcc text_file_end.c -o text_file_end
[sss@aliyun file]$ ./text_file_end
hello, world!
End of file reached successful
二进制文件
#include <stdio.h>
#include <stdlib.h>
#define N 5
int main(){
double a[N] = {
1.0, 2.0, 3.0, 4.0, 5.0
};
double b[N] = {0.0};
size_t ret = 0;
FILE* fp = fopen("temp.bin", "wb");
fwrite(a, sizeof(*a), N, fp);
fclose(fp);
fp = fopen("temp.bin", "rb");
ret = fread(b, sizeof(*b), N, fp);
if(ret == N){
puts("Array read successfully, contents: \n");
int i = 0;
for(; i < N; ++i){
printf("%f ", b[i]);
}
printf("\n");
}
else{
if(feof(fp)){
printf("Error reading temp.bin: unexpected end of file.\n");
}
else if(ferror(fp)){
perror("Error reading temp.bin.\n");
}
}
fclose(fp);
return 0;
}
运行结果
[sss@aliyun file]$ !gcc
gcc bin_file_end.c -o bin_file_end
[sss@aliyun file]$ ./bin_file_end
Array read successfully, contents:
1.000000 2.000000 3.000000 4.000000 5.000000
文件操作接口代码演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* fp = fopen("file.txt", "w+");
if(!fp){
perror("fopen error");
}
char write_buf[1024] = {0};
printf("Please write: \n");
scanf("%s", write_buf);
fwrite(write_buf, sizeof(char), strlen(write_buf), fp);
fseek(fp, 0, SEEK_SET);
char read_buf[1024] = {0};
fread(read_buf, sizeof(char), 1023, fp);
printf("read: %s\n", read_buf);
fclose(fp);
return 0;
}
运行结果
[sss@aliyun file]$ !gcc
gcc file_operate.c -o file_operate
[sss@aliyun file]$ ./file_operate
Please write:
hello,world!
read: hello,world!