为什么使用文件?
如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。
什么是文件?
磁盘上的文件是文件。
但是在程序设计中,我们⼀般谈的文件有两种:程序文件、数据文件(从文件功能的⻆度来分类的)。
程序文件
程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,⽐如程序运行需要从中读取数据的文件,或者输出内容的文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到
显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
文件名
⼀个文件要有一个唯一的文件标识,以便用户识别和引用。
⽂件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
其中c:\code
为文件路径,\test
为文件名的主干,.txt
为文件后缀
为了方便起见,文件标识常被称为文件名
二进制文件和文本文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
- 数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
- 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文
本文件。
文件的打开和关闭
流和标准流
流
程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,抽象出了流的概念,我们可以把流想象成流淌着字符的河。
C程序针对文件、画⾯、键盘等的数据输入输出操作都是同流操作的。
⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。
标准流
stdin
- 标准输入流,在大多数的环境中从键盘输入。stdout
- 标准输出流,大多数的环境中输出至显示器界面。stderr
- 标准错误流,大多数环境中输出到显示器界面。
stdin、stdout、stderr三个流的类型是: FILE*
,通常称为文件指针。
C语言中,就是通过 FILE*
的文件指针来维护流的各种操作的。
文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名FILE
。例如:
FILE* pf;//文件指针变量
定义pf
是⼀个指向FILE
类型数据的指针变量。可以使pf
指向某个文件的文件信息区(是⼀个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件。
文件的打开和关闭
基本操作:
- 打开文件
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
注意:若文件打开失败,指针将返回空指针,因此在打开文件后可进行判断,从而知晓文件是否打开成功
- 读写文件
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r”(只读) | 为了输⼊数据,打开⼀个已经存在的文本文件 | 出错,返回NULL |
“w”(只写) | 为了输出数据,打开⼀个文本文件 | 建立一个新的文件 |
“a”(追加) | 向⽂本⽂件尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输⼊数据,打开⼀个⼆进制⽂件 | 出错 |
“wb”(只写) | 为了输出数据,打开⼀个⼆进制⽂件 | 建立一个新的文件 |
“ab”(追加) | 向⼀个⼆进制⽂件尾添加数据 | 建立一个新的文件 |
“r+”(读写) | 为了读和写,打开⼀个⽂本⽂件 | 出错 |
“w+”(读写) | 为了读和写,建立⼀个新的⽂件 | 建立一个新的文件 |
“a+”(读写) | 打开⼀个⽂件,在⽂件尾进⾏读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开⼀个⼆进制⽂件 | 出错 |
“wb+”(读写) | 为了读和写,新建⼀个新的⼆进制⽂件 | 建立一个新的文件 |
“ab+”(读写) | 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 | 建立一个新的文件 |
注意:“w”中若文件存在,则把原文件删除,新建一个空文件。
3. 关闭文件
//关闭⽂件
int fclose ( FILE * stream );
注意:将指针置空,否则指针将成为野指针
关于输入输出数据的理解:
例子:
#include <stdio.h>
int main()
{
FILE * pFile;
//打开⽂件
pFile = fopen("myfile.txt", "w");
//⽂件操作
if (pFile != NULL)
{
fputs("fopen example", pFile);
//关闭⽂件
fclose(pFile);
pFile = NULL;
}
return 0;
}
打开文件中的第一个参数又分为绝对路径和相对路径:
- 绝对路径:例如
c:\\users\\test.txt
- 相对路径:
.
表示当前路径;..
表示上一级路径
文件的顺序读写
顺序读写函数介绍
功能 | 函数 | 适用于 |
---|---|---|
fgetc | 字符输⼊函数(一次读取一个字符) | 所有输⼊流 |
fputc | 字符输出函数(一次写入一个字符) | 所有输出流 |
fgets | ⽂本⾏输⼊函数(一次读取一行字符) | 所有输⼊流 |
fputs | ⽂本⾏输出函数(一次写入一行字符) | 所有输出流 |
fscanf | 格式化输⼊函数 | 所有输⼊流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | ⼆进制输⼊ | ⽂件 |
fwrite | ⼆进制输出 | ⽂件 |
fgetc:
int fgetc ( FILE * stream );
- 如果调用时流位于文件末尾,函数返回
EOF
并为流设置(feof)
的文件结束指示器。 - 如果发生读取错误,该函数将返回
EOF
并为流设置错误指示器(ferror)
。
fputc:
int fputc ( int character, FILE * stream );
如果发生写入错误,则返回 EOF
并设置错误指示器(ferror)
。
fgets:
char * fgets ( char * str, int num, FILE * stream );
- 从流中读取字符并将其作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件末尾,以先发生者为准。
- 换行符使
fgets
停止读取,但它被函数视为有效字符,并包含在复制到 str 的字符串中。 - 终止空字符会自动附加到复制到 str 的字符之后。
- 请注意,fgets 与 get 完全不同:fgets 不仅接受流参数,还允许指定 str 的最大大小,并在字符串中包含任何结束换行符。
- 如果在尝试读取字符时遇到文件末尾,则设置 eof 指示器
(feof)
。如果在读取任何字符之前发生这种情况,则返回的指针为空指针(str 的内容保持不变)。 - 如果发生读取错误,则设置错误指示器
(ferror)
,并返回空指针(但str指向的内容可能已更改)。
fputs:
int fputs ( const char * str, FILE * stream );
- 该函数从指定的地址
(str)
开始复制,直到到达终止空字符('\0')
。此终止空字符不会复制到流中。 - 请注意,
fputs
与put
的不同之处不仅在于可以指定目标流,而且fputs
不会写入其他字符,而fputs
会自动在末尾附加换行符。 - 成功时,将返回非负值。出错时,该函数返回
EOF
并设置错误指示器(ferror)
。
fscanf:
int fscanf ( FILE * stream, const char * format, … );
如果发生读取错误或在读取时到达文件末尾,则会设置正确的指示器(feof 或 ferror)
。并且,如果在成功读取任何数据之前发生任一情况,则返回 EOF
。
fprintf:
int fprintf ( FILE * stream, const char * format, … );
- 成功后,将返回写入的字符总数。
- 如果发生写入错误,则设置错误指示器
(ferror)
并返回负数。
fread:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
如果成功,则读取的总字节数。
fwrite:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
返回成功,写入的元素总数。
对比一组函数
scanf/fscanf/sscanf
printf/fprintf/sprintf
scanf
与printf
:针对标准输入流/输出流的格式化输入/输出
fscanf
与fprintf
:针对所有输入流/输出流的格式化输入/输出
sscanf
:字符串中读取格式化的数据
sprintf
:把格式化的数据转换成字符串
文件的随机读写
fseek
根据文件指针的位置和偏移量来定位文件指针。
int fseek ( FILE * stream, long int offset, int origin );
关于第三个参数:
SEEK_CUR:文件当前位置
SEEK_END:末尾位置
SEEK_SET:起始位置
例子:
#include <stdio.h>
int main()
{
FILE * pFile;
pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET);
fputs(" sam", pFile);
fclose(pFile);
pFile = NULL;
return 0;
}
ftell
返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );
例子:
#include <stdio.h>
int main()
{
FILE * pFile;
long size;
pFile = fopen("myfile.txt", "rb");
if (pFile == NULL)
perror("Error opening file");
else
{
fseek(pFile, 0, SEEK_END); // non-portable
size = ftell(pFile);
fclose(pFile);
pFile = NULL;
printf("Size of myfile.txt: %ld bytes.\n", size);
}
return 0;
}
rewind
让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream );
#include <stdio.h>
int main()
{
int n;
FILE * pFile;
char buffer[27];
pFile = fopen("myfile.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
fputc(n, pFile);
rewind(pFile);
fread(buffer, 1, 26, pFile);
fclose(pFile);
pFile = NULL;
buffer[26] = '\0';
printf(buffer);
return 0;
}
文件读取结束的判定
被错误使用的 feof
牢记:在文件读取过程中,不能用feof
函数的返回值直接来判断文件的是否结束。
feof
的作用是:当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。
- 文本文件读取是否结束,判断返回值是否为
EOF ( fgetc )
,或者NULL ( fgets )
例如:
- fgetc 判断是否为 EOF .
- fgets 判断返回值是否为 NULL .
- ⼆进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
• fread判断返回值是否小于实际要读的个数。
文本文件的例⼦:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int c; // 注意:int,⾮char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if (!fp)
{
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
fp = NULL;
return 0;
}
二进制文件例子:
#include <stdio.h>
enum { SIZE = 5 };
int main()
{
double a[SIZE] = { 1., 2., 3., 4., 5. };
FILE *fp = fopen("test.bin", "wb"); // 必须⽤⼆进制模式
fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组
if (ret_code == SIZE)
{
puts("Array read successfully, contents: ");
for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
putchar('\n');
}
else
{ // error handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp))
{
perror("Error reading test.bin");
}
}
fclose(fp);
fp = NULL;
return 0;
}
文件缓冲区
ANSIC 标准采⽤“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每⼀个正在使⽤的文件开辟⼀块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的