前言:
通过前面篇章的知识,这篇将对C语言文件操作,进行深入学习和理解。
引言:
可以根据前面篇章的通讯录出现的问题进行描述,当程序结束后通讯录的数据就跟着丢失了,因为内存随着程序的结束也释放了,无法保存通讯录的数据。
那么怎么解决此类的问题呢?
答:文件就可以解决数据丢失的问题。因为,程序一般是存放在内存中的,会随着程序结束而丢失,而文件是存放在硬盘上的,会保存数据。
/知识点汇总/
基本定义:磁盘上的文件就是文件
但是在程序设计中,一般分为程序文件和数据文件
程序文件:包括源程序文件(后缀为.c),目标文件(.obj),可执行文件(.exe)…
数据文件:不一定是程序,而是程序运行时读写的数据
磁盘文件:之前接触的输入输出所处理的数据都是以终端为对象的,即从终端的键盘输入数据,运行结果显示在显示器屏幕上;当我们把信息输出到磁盘上,从磁盘调用数据读取到内存中使用时,就属于磁盘文件。
即操作对象:键盘 <->屏幕
一个文件有唯一一个文件标识,以便于识别使用。
文件名包括3部分;文件路径+文件名主干+文件后缀
如:C:\code\test.txt 俗称文件名
(创建文件可不写后缀)
缓冲文件系统中,关键的概念就是文件类型指针,简称文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件名、文件状态以及文件存放的位置)
这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取为FILE
struct _iobuf
{
char* _ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char* _tmpfname;
};
typedef struct _iobuf FILE;
即:只要打开一个文件就会有一个文件信息区,类型为FILE类型结构体
所以本质是结构体(里面的内容使用者不必关心)
不同的编译器FILE类型包括的内容也有所差异
所以为了灵活的维护,引用一个FILE类型的指针
fopen – 返回的是一个FILE类型的指针 ,如:FILE* pf;
(1).打开文件 — fopen
(2).读/写文件 – 较多见后面
(3).关闭文件 – fcolse
fopen函数
功能:打开文件
原型:FILE* fopen(const char* filename,const char* mode);
fclose函数
功能:关闭文件
原型:int fclose(FILE* stream);
#include
int main()
{
FILE* pf = fopen("data3.txt", "w");//以"w"写模式打开文件时,如果没有该文件时,不会报错,直接创建该文件
//FILE* pf = fopen("data.txt", "r");//以“r”读模式打开文件时,不会创建无法读取而报错
//同时,文件的路径也决定能否正确打开
//相对路径:
// . 当前目录/路径
// .. 上一级目录/路径
//相对路径:
//fopen("data3.txt", "w") -- 默认当前路径,就是源文件所在路径
//fopen(".\\data3.txt","w") -- 指定当前路径
//fopen("..\\..\\data.txt","r") -- 上一级上一级路径
//绝对路径:
//fopen("D:\\code\\class\\data.txt", "w")
if (pf == NULL)
{
perror("fopen");
return 1;
}
//打开文件成功,操作写文件
//......
printf("打开文件成功\n");
//写完后,关闭文件
fclose(pf);
pf = NULL;
return 0;
}
补充:
(1).绝对路径:
(2).相对路径:
程序:保存数据、变量、数组…在内存中
由内存中写文件/输出操作到文件,文件保存在硬盘
由文件读取到内存是读文件/输入操作
那么具体如何进行读写操作呢?
顺序读写函数介绍:
(1).字符输入函数 — fgetc — 所有输入流
(2).字符输出函数 — fputc — 所有输出流
(3).文本行输入函数 — fgets — 所有输入流
(4).文本行输出函数 — fputs — 所有输出流
(5).格式化输入函数 — fscanf — 所有输入流
(6).格式化输出函数 — fprintf — 所有输出流
(7).二进制输入 — fread — 文件
(8).二进制输出 — fwrite — 文件
原型:int fputc(int character, FILE* stream);
#include
int main()
{
FILE* pf = fopen("E:\data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
//通过文件指针进入文件后,光标就会根据内容进行偏移
//fputc('a', pf);
//fputc('b', pf);
//fputc('c', pf);
//fputc('d', pf);
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
if (ch % 5 == 0)
fputc('\n', pf);
fputc(ch, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
扩展1:“流” – FILE*指针类型
scanf()从键盘上读取数据
printf()向屏幕打印数据
可直接操作,读取和打印;
是因为C程序只要运行起来,默认就打开了3个流
(1).标准输入流 – stdin – FILE*
(2).标准输出流 – stdout – FILE*
(3).标准错误流 – stderr — FILE*
#include
int main()
{
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
if (ch % 5 == 0)
fputc('\n', stdout);//标准输出流,实现数据打印在屏幕上
fputc(ch, stdout);
}
return 0;
}
原型:int fgetc ( FILE * stream );
功能:读取正确返回ASCII码值,读取失败返回EOF(-1)
#include
int main()
{
FILE* pf = fopen("\data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//int ch = fgetc(pf);
//printf("%c ", ch);
//ch = fgetc(pf);
//printf("%c ", ch);
//ch = fgetc(pf);
//printf("%c ", ch);
//ch = fgetc(pf);
//printf("%c ", ch);
int ch = 0;
while((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
原型:int fputs ( const char * str, FILE * stream );
功能:把字符串写进流里/屏幕流
#include
int main()
{
FILE* pf = fopen("E:\data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
//fputs("hello", pf);
//fputs("world", pf);
//fputs("\nhello\n", pf);
//fputs("world\n", pf);
//fputs("hello ", stdout);
//fputs("world ", stdout);
char arr[] = "absefga";
fputs(arr, stdout);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
原型:char * fgets ( char * str, int num, FILE * stream );
功能:读取到第num个元素的前一个字符结束,即num-1个,并且读取过程遇见’\n’会判定读取结束
#include
int main()
{
FILE* pf = fopen("E:\data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char arr[100] = { 0 };
fgets(arr, 100, pf);
printf("%s", arr);
//遇见'\n'结束,以行来读取
fgets(arr, 100, pf);
printf("%s", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
原型:int printf ( const char * format, … );
原型:int fprintf ( FILE * stream, const char * format, … );
原型:int sprintf ( char * str, const char * format, … );
#include
struct S
{
float f;
char c;
int n;
};
int main()
{
struct S s = { 3.1415,'w',100 };
FILE* pf = fopen("E:\data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
printf("%f %c %d", s.f, s.c, s.n);
fprintf(pf, "%f %c %d", s.f, s.c, s.n);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
原型:int scanf ( const char * format, … );
原型:int fscanf ( FILE * stream, const char * format, … );
原型:int sscanf ( const char * s, const char * format, …);
#include
struct S
{
float f;
char c;
int n;
};
int main()
{
struct S s = { 0 };
FILE* pf = fopen("E:\data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//scanf("%f %c %d", &(s.f), &(s.c), &(s.n));
fscanf(pf,"%f %c %d", &(s.f), &(s.c), &(s.n));
printf("%f %c %d", s.f, s.c, s.n);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
原型: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 );
#include
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
FILE* pf = fopen("E:\data.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//二进制写文件
fwrite(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
原型: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 );
#include
int main()
{
int arr[10] = { 0 };
FILE* pf = fopen("E:\data.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//二进制写文件
fread(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
原型:int scanf ( const char * format, … );
原型:int fscanf ( FILE * stream, const char * format, … );
原型:int sscanf ( const char * s, const char * format, …);
功能:把格式化数据转换成字符串
#include
struct S
{
float f;
char c;
int n;
};
int main()
{
struct S s = { 3.14,'c',100 };
char arr[100] = { 0 };
sprintf(arr, "%f %c %d", s.f, s.c, s.n);//将带有格式化的数据转换为字符串
printf("%s\n", arr);
//还原
struct S tmp = { 0 };
sscanf(arr,"%f %c %d", &(tmp.f), &(tmp.c), &(tmp.n));
printf("%f\n",tmp.f);
printf("%c\n", tmp.c);
printf("%d\n", tmp.n);
return 0;
}
扩展2:
scanf/fscanf/sscanf
printf/fprintf/sprintf
1.scanf:是格式化的输入函数,针对的是标准输入流(键盘)
2.printf:是格式化的输出函数,针对的是标准输出流(屏幕)
3.fscanf:是针对所有输入流(文件流、标准输入流)的格式化输入函数
4.fprintf:是针对所有输出流(文件流、标准输出流)的格式化输出函数
5.sscanf:将字符串数据转换成格式化数据
6.sprintf:将格式化数据转换成字符串
小结:格式转换之间需要保持格式一致
fseek函数
原型:int fseek ( FILE * stream, long int offset, int origin );
功能:根据文件指针的位置,计算偏移量
返回值:
a、SEEK_SET:Beginning of file
b、SEEK_CUR:Current position of the file pointer
c、SEEk_END:end of file*
#include
int main()
{
FILE* pf = fopen("E:\data.txt", "r");
//abcdef123456
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
ch = fgetc(pf);
printf("%c\n", ch);//d
//偏移读写位置
//fseek(pf, -3, SEEK_CUR);//从当前位置向前偏移4个单位进行读数据
//ch = fgetc(pf);
//printf("%c\n", ch);//a
//fseek(pf, 0, SEEK_SET);
//ch = fgetc(pf);
//printf("%c\n", ch);//a
fseek(pf, -19, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);//a
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
ftell函数
原型:long int ftell ( FILE * stream );
功能:计算当前文件指针相对于起始位置的偏移量,通常可搭配fseek使用
注意:文件中的空格等占位符也会影响偏移量的计算
#include
int main()
{
FILE* pf = fopen("E:\data.txt", "r");
//abcdef123456
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
ch = fgetc(pf);
printf("%c\n", ch);//d
//偏移量
int pos = ftell(pf);
printf("pos = %d\n", pos);
//偏移读写位置
fseek(pf, -pos, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);//a
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
rewind函数
原型:void rewind ( FILE * stream );
功能:让文件指针的位置回到文件的起始位置
#include
int main()
{
FILE* pf = fopen("E:\data.txt", "r");
//abcdef123456
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
ch = fgetc(pf);
printf("%c\n", ch);//d
//使文件指针归位
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);//d
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
#include
int main()
{
FILE* pf = fopen("E:\data.txt", "r");
//abcdef123456
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
//偏移量
int pos = ftell(pf);
printf("pos = %d\n", pos);
//偏移读写位置
fseek(pf, -pos, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);//a
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
通俗的讲:
把数据转换为字符,人能识别的数据文件 — 文本文件
二进制文件顾名思义就是机器识别的语言,人看不懂 –
二进制文件、
概念性讲:
数据在内存中以二进制的形式存储,如果不加转换的输出到外存上 – 二进制文件
如果要求在外存上以ASCLL码的形式存储,则需要在存储前转换;以ASCLL码字符的形式存储的文件是文本文件
那么一般情况下,数据是怎么存储的呢?
字符一律以ASCLL码的形式存储,数值型数据既可以用ASCLL形式存储,也可以用二进制形式存储。
比如:
整数10000,是以每一位的字符转换为ASCLL码存储输出到磁盘。则磁盘中占用5个字节。
而以二进制形式输出时,只占用4个字节。
#include
int main()
{
int a = 10000;
//0000 0000 0000 0000 0010 0111 0001 0000
//0x00 0x00 0x27 0x10
FILE* pf = fopen("text.txt", "wb");
fwrite(&a, 4, 1, pf);//以二进制存储
fclose(pf);
pf = NULL;
return 0;
}
feof函数
经常被错误使用的feof
注意:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
feof的作用是:当文件读取结束的时候,判断是读取结束的原因是否是,遇见文件尾结束。
1.文本文件读取是否结束,判断返回值是否为EOF,或者NULL;比如fgetc判断返回EOF
2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数,比如fread判断返回值是否小于实际要读的个数。
fread函数
fread要求读取count个大小为size字节的数据
1.如果真的读取到count个数据,函数返回count
2.如果没有读取到count个数据,返回的是真实的读取到的完整的数据个数
比如:
写一个代码完成文件的拷贝
data1.txt --> data2.txt
#include
int main()
{
//permission denied访问权限
FILE* pfread = fopen("E:\data1.txt", "r");
if (pfread == NULL)
{
perror("fopen");
return 1;
}
FILE* pfwrite = fopen("E:\data2.txt", "w");
if (pfwrite == NULL)
{
perror("fopen");
fclose(pfread);
pfread = NULL;
return 1;
}
//拷贝数据
int ch = 0;
while ((ch = fgetc(pfread)) != EOF)
{
fputc(ch, pfwrite);
}
//关闭文件
fclose(pfread);
pfread = NULL;
fclose(pfwrite);
pfwrite = NULL;
return 0;
}
文本文件的例子
#include
#include
int main()
{
int c;
FILE* fp = fopen("test.txt", "r");
if (!fp)
{
perror("File opening failed");
return EXIT_FAILURE;
}
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 successfully");
fclose(fp);
fp = NULL;
}
二进制文件的例子
#include
enum
{
SIZE = 5
};
int main()
{
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* fp = fopen("test.bin", "wb");
fwrite(a, sizeof(a[0]), SIZE, fp);
fclose(fp);
fp = NULL;
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof * b, SIZE, fp);
if (ret_code == SIZE)
{
puts("Arrav read successfully,contents:");
for (int n = 0; n < SIZE; ++n)
{
printf("%f ", b[n]);
}
}
else
{
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;
}
小结:
1.ferror – 在文件读取结束后,用来判断文件是否因为读取过程中,发生错误而结束
2.feof ---- 在文件读取结束后,用来判断文件是否因为读取过程中遇见文件结束标志(文件尾)而结束
ANSIC标准采用“缓冲文件系统”处理的数据文件,所谓的缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。
从内存向磁盘输出数据,会先送到内存中的缓冲区,等待装满缓冲区后一起送到磁盘上。— 输出缓冲区
如果从磁盘向计算机读入数据时,则从磁盘文件中读取数据输入到内存中缓冲区(等待装满),再从缓冲区逐个将数据送到数据对应的区。 — 输入缓冲区
另外,缓冲区的大小由编译器决定,缓冲区也是在内存中开辟的。
经典例子,证明存在缓冲区:
#include
#include
int main()
{
FILE* pf = fopen("E:\text.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒,已经写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
//注:fflush,在高版本的VS上不能使用
printf("再睡眠10秒,此时打开文件,文件已经有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}
掌握C语言的文件操作能够使你在编程过程中更灵活地处理数据、实现数据的永久保存、促进不同程序间的数据交流、实现数据备份、获取命令行参数、保护文件的安全性以及提高文件读写的效率等。
半亩方糖一鉴开,天光云影共徘徊。
问渠哪得清如许?为有源头活水来。–朱熹(观书有感)