1.文件的基本概念
在编程语言中,文件(File)是用来存储和处理数据的一种抽象概念。它可以是磁盘上的实际文件,也可以是内存中的虚拟文件。文件可以包含文本、图片、音频、视频等各种类型的信息。在编程中,可以使用文件来保存程序运行时的状态、持久化数据、读取输入、输出结果等。
文件名
每个文件都有唯一的文件标识
文件名包含3部分:文件路径 + 文件名主干 + 文件后缀
例如:c:\code\test.txt
方便起见,文件标识常称为文件名
在程序设计中,从文件功能的角度分类:程序文件 和 数据文件
程序文件包括源文件(.c) ,目标文件(windows环境 后缀.obj), 可执行程序(windows环境 后缀.exe)
数据文件的文件内容是程序运行时读写的数据(如:程序运行时需要从中读取数据的文件,输出内容的文件)
根据数据的组织形式,数据文件被称为文本文件或者二进制文件
二进制文件:数据在内存中以二进制的形式存储,不加转换的输出到外存的文件中
文本文件:以ASCII字符的形式存储的文件(要求在外存上以ASCII码的形式存储,需要在存储前转换)
>> 数据在文件中的存储
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储
例:整数10000 ( VS 2019 )
测试代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 10000;
FILE* p = fopen("file.txt", "wb");
fwrite(&a, 4, 1, p);
fclose(p);
p = NULL;
return 0;
}
运行结果:
注意 : 文件名的后缀可以自己随意设定,只是有些软件规定了(如:.doc就是word文件)
2. 文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如:文件的名字,文件状态以及文件的当前位置)这些信息保存在一个结构体变量中,该结构体类型由系统申明,取名FLIE
在VS2013编译环境提供的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结构的变量,并且填充其中的信息
一般通过一个FILE指针维护这个FILE结构的变量,方便使用
FILE* p;// 文件指针变量
定义p是一个指向FILE类型数据的指针变量,使p指向某个文件的文件信息区(一个结构体变量),通过文件信息区的信息可以访问该文件。换言之,通过文件指针可以间接找到与它关联的文件
3.文件的打开和关闭
文件在读写之前要先打开文件,结束之后应该关闭文件
编写程序的时候,在打开文件的同时,会返回一个FILE*的指针变量指向该文件,相当于建立了指针和文件的关系
ASCI规定使用fopen函数打开文件,fclose关闭文件
// 打开文件
FILE* fopen( const char* filename,const char* mode );
//关闭文件
FILE* fclose( FILE* stream );
其中,mode表示文件的打开方式 ,以下都是文件的打开方式:
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
" r " (只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错,返回NULL |
" w " (只写) | 为了输入数据,打开一个文本文件 | 创建一个新文件 |
" a " (追加) | 向文本文件尾添加数据 | 创建一个新文件 |
" rb " (只读 ) | 为了输入数据,打开一个二进制文件 | 出错 |
” wb " (只写) | 为了输入数据,打开一个二进制文件 | 创建一个新文件 |
" ab "(追加) | 向二进制文件尾添加数据 | 创建一个新文件 |
" r+ " (读写) | 为了读和写,打开一个文本文件 | 出错 |
" w+ "(读写) | 为了读和写,创建一个新的文件 | 创建一个新文件 |
" a+ " (读写) | 打开一个文件,在文件尾进行读写 | 创建一个新文件 |
“ rb+ " (读写) | 为了读和写打开一个二进制文件 | 出错 |
" wb+ " (读写) | 为了读和写,创建一个新的二进制文件 | 创建一个新文件 |
" ab+ " (读写) | 打开一个二进制文件,在文件尾进行读和写 | 创建一个新文件 |
注意: " r " 如果打开成功,返回文件信息区指针;如果同名文件 / 文件存在,文件内容会被清空
#include <stdio.h>
int main()
{
FILE* p;
p = fopen("mufile.txt", "w");
if (p != NULL)
{
fputs("fopen example", p);
fclose(p); // 关闭文件,文件信息区被回收
}
return 0;
}
4. 文件的读写
4.1 流 与 标准流
4.1.1 流
程序的数据需要输出到各种外部设备,也需要从外部设备获取数据。不同的外部设备的输入输出操作各不相同。为了方便程序员对各种设备的操作,抽象出 流 的概念(可以将 流 想象成流淌着各种数据的河)
C语言针对文件、画面、键盘等的数据的输入输出操作都是同流操作
4.1.2 标准流
C语言程序在启动的时候默认打开了三个流:
stdin -- 标准输入流 | 在大多数的坏境下从键盘输入 scanf 读取 |
stdout -- 标准输出流 | 在大多数的坏境下输出至显示器界面 printf 输出 |
stderr -- 标准错误流 | 在大多数的坏境下输出至显示器界面 |
所以使用scanf ,printf 等函数可以直接进行输入输出操作
stdin、stdout、stderr三个流的类型 FILE*
C语言中,通过FILE* 的文件指针来维护流的各种操作
4.2 文件的顺序读写
4.2.1 顺序读写函数
函数名 | 功能 | 适用于 |
---|---|---|
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
对比:
scanf | 针对标准输入流(stdin)的格式化输入函数 |
pritnf | 针对标准输出流(stdout)的格式化输出函数 |
fscanf | 针对所有的输入流的格式化输入函数 |
fprintf | 针对所有的输出流的格式化输出函数 |
sscanf | 将格式化的字符串转换为数值 |
sprintf | 将格式化的数据转换成字符串 |
4.2.2 文件的随机读写
4.2.2.1 fseek
根据文件指针的位置和偏移量来定位文件指针
int fssek(FLIE* stream, long int offset, int origin);
例:
#include <stdio.h>
int main()
{
FILE* p;
p = fopen("example.txt", "wb");
fputs("this is an apple.",p);
fseek(p, 9, SEEK_SET); //SEEK_SET 是C语言标准库中的宏常量,用于文件定位操作中的参数。它表示文件的起始位置。
fputs(" sam", p);
fclose(p);
return 0;
}
4.2.2.2 ftell
返回文件指针相对于起始位置的偏移量
long int ftell(FILE* strream);
例:
#include <stdio.h>
int main()
{
FILE* p;
long size;
p = fopen("myfile.txt", "rb");
if (p == NULL)
{
perror("Error.opening file");
}
else
{
fseek(p, 0, SEEK_END);
size = ftell(p);
fclose(p);
printf("Size of myfile.txt is %ld bytes.\n", size);
}
return 0;
}
4.2.2.3 rewind
让文件指针的位置回到文件的起始位置
void rewind(FILE* stream);
例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int n;
FILE* p;
char buffer[27];
p = fopen("file.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
{
fputc(n, p);
}
rewind(p);
fread(buffer, 1, 26, p);
fclose(p);
buffer[26] = '\0';
printf(buffer);
return 0;
}
5. 文件读取结束的判断
5.1 feof
feof 是C语言标准库中的函数,用于检测文件的结束标志。定义在 <stdio.h>
头文件中,返回值为非零值(true)表示已经到达文件末尾,否则返回0(false)。
然而,不能仅依靠 feof
来判断文件是否结束是因为 feof
函数是在文件读取操作之后才能给出准确结果。换句话说,只有在读取操作尝试从文件中读取数据时,才会将 EOF
标志设置为当前位置是否已达到文件末尾。因此,在使用 feof
之前,通常需要进行一次读取操作
5.2 判断文件读取是否结束
(1) 文本文件
1. fgetc 判断是否为EOF
2. fgets 判断返回值是否为NULL
(2) 二进制文件
fread 判断 返回值是否小于实际要读的数
>> 例:文本文件
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int c;
FILE* p = fopen("text.txt", "r");
if (!p)
{
perroe("File opening failed");
return EXIT_FAILURE;
}
// fgets 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(p)) != EOF) // 标准C I/O 读取文件循环
putchar(c);
//判断什么原因导致文件读取结束
if (ferror(p))
puts("/O error when reading");
else if (feof(p))
puts("End of file reached successfully");
fclose(p);
return 0;
}
>> 例:二进制文件
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
enum{SIZE = 5};
int main()
{
double arr[SIZE] = { 1.,2.,3.,4.,5. };
FILE* p = fopen("test.bin", "wb");//用二进制形式
fwrite(arr, sizeof * arr, SIZE, p);
fclose(p);
double brr[SIZE];
p = fopen("test.bin", "rb");
size_t ret_code = fread(brr, sizeof * brr, SIZE, p);
if (ret_code == SIZE)
{
puts("successfully read :");
for (int n = 0; n < SIZE; ++n) printf("%f", brr[n]); //循环语句
putchar('\n');
}
else
{
if (feor(p))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(p))
perror("Error reading test.bin");
}
fclose(p);
return 0;
}
6. 文件缓冲区
ASCIC标准采用 缓冲文件系统 处理数据文件。缓冲文件系统指的是系统自动的在内存中为程序中每一个证字啊使用的文件开辟一块 文件缓冲区 。从内存向磁盘输出数据会先输送到内存中的缓冲区,装满缓冲区之后才一起送到磁盘。如果从磁盘向计算机读入数据,则磁盘文件中读取数据输入到内存缓冲区,然后再从缓冲区逐个将数据送到程序数据库(程序变量等)。缓冲区的大小由C编译系统决定
证明:因为缓冲区的存在,C语言操作文件的时候,需要刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能造成读写文件的问题
证明如下: 代码
#include <stdio.h>
#include <windows.h>
//VS2019 win11 环境测试
int main()
{
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
//注:fflush 在⾼版本的VS上不能使⽤了
printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭⽂件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}