1.为什么使用文件
我们写的程序数据是存放在内存中的,当程序运行结束,释放内存后,数据就会丢失。
为了持久化的保存数据,就需要将数据存储在文件中。
2.什么是文件
在程序设计中,我们一般把文件称为程序文件和数据文件
2.1程序文件
程序文件包括
源程序文件(后缀为.c的文件)
目标文件(Windows环境下后缀.obj的文件)
可执行程序(Windows环境下后缀为.exe的文件)
2.2数据文件
文件的内容不一定是程序,也可以是程序运行时读写的数据。
2.3文件名(或者称为文件标识)
一个文件需要有一个唯一的文件标识(文件名),方便用户识别和引用。
文件名包括三部分:文件路径+文件主干+文件后缀
例如:c:\code\test.txt
3.二进制文件和文本文件
什么是 二进制文件 和 文本文件 ?
二进制文件:数据在内存中以二进制的形式存储,不加转换的输出到外存的文件中就是二进制文件。
文本文件:数据在内存中以二进制的形式存储,将数据转换成ASCII字符输出到外存的文件中就是文本文件。
字符一律以ASCII形式存储。
数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
因为数值型数据根据数值的大小,存放在文件中的内存大小不一样。
例如:10000
如果以 ASCII形式 输出到磁盘中,则会占用磁盘中的五个字节。
如果以 二进制形式 输出到磁盘中,则会占用磁盘中的四个字节。
4.文件的打开和关闭
4.1流和标准流
4.1.1流
C程序针对文件、画面、键盘等的数据输入输出操作都是通过流来完成的。
类似于所有数据输入输出前会经过的一个中点站。
一般情况下,我们都需要打开流,来进行输入输出的操作。
4.1.2标准流
那么为什么平常在输入输出时我们没有打开流呢?
这是因为C语言程序在启动时,会默认打开三个流
stdin 标准输入流
在大多数的环境中从键盘输入,scanf 函数就是在标准输入流中读取数据。
stdout 标准输出流
大多数的环境中输出至显示器界面,printf 函数就是将信息输出到标准输出流中。
stderr 标准错物流
大多数环境中将错误信息输出到显示器。
三个流的类型是:FILE*,通常称为文件指针。
C语言中就是通过 FILE* 的文件指针来维护流的各种操作。
4.2文件指针的
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相应信息(如文件的名字、文件的状态、文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体是由系统声明的,取名FILE.
不同的C语言编译器中 FILE 类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个 FILE 结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个 FILE 类型的指针来维护这个FILE结构的变量,这样使用起来更方便。
#include<stdio.h>
FILE* pf;
定义pf是一个指向 FILE 类型数据的指针变量,可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。
也就是说,通过文件指针变量能够间接找到与它关联的文件。
4.3文件的打开和关闭
文件在进行读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序时,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC 规定使用 fopen 函数来打开文件,fclose 来关闭文件
//打开文件
FILE* fopen(const char* filename,const char* mode);
//关闭文件
int fclose(FILE* stream);
mode 表示文件的打开模式,下面都是文件的打开模式:
实例代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
FILE* p;
//打开文件
p = fopen("Hello.txt", "w");
//文件操作
if (p != NULL)
{
fputs("fopen example", p);
//关闭文件
fclose(p);
}
return 0;
}
运行结果:
5.文件的顺序读写
5.1 顺序读写函数介绍
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
5.2 对比一组函数(暂未完成)
scanf / fscanf / sscanf
printf / fprintf / sprintf
6.文件的随机读写
6.1 fseek---根据文件指针的位置和偏移量来定位文件指针
#include<stdio.h>
//fseek的example
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);
return 0;
}
运行结果:
6.2 ftell---返回文件指针相对于起始位置的偏移量
#include<stdio.h>
/* ftell example : getting size of a file */
#include <stdio.h>
long int ftell(FILE* stream);
int main()
{
FILE* pFile;
long size;
pFile = fopen("Hello.txt", "rb");
if (pFile == NULL)
perror("Error opening file");
else
{
fseek(pFile, 0, SEEK_END); // non-portable
size = ftell(pFile);
fclose(pFile);
printf("Size of myfile.txt: %ld bytes.\n", size);
}
return 0;
}
运行结果:
6.3 rewind---让文件指针的位置回到文件的起始位置
/* rewind example */
#include <stdio.h>
void rewind(FILE* stream);
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);
buffer[26] = '\0';
printf(buffer);
return 0;
}
运行结果:
7.文件读取结束的判定
7.1 被错误使用的 feof
feof 的作用时:当文件读取结束的时候,判断读取结束的原因是否是:遇到文件尾结束。
不能用 feof 函数的返回值直接来判断文件是否结束。
7.1.1文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ) , 或者 NULL ( fgets )
1. fgetc 判断是否为 EOF.
2. fgets 判断返回值是否为 NULL.
例如:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("Hello.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);
}
运行结果:
7.1.2 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
例如:
#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
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);
}
运行结果:
8. 文件缓冲区
ANSIC 标准采用“缓冲文件系统” 处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
所以,因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件(fclose)。
如果不做,可能导致读写文件的问题。