1:在程序设计中为什么要使用文件?
文件可以将数据持久化的保存,如果不使用文件,我们写的程序的数据是保存在电脑的内存中,如果程序退出,内存回收,数据就会丢失。
2:文件及其分类
磁盘(外存)上的文件就是所说的文件
从文件的功能角度:可分为程序文件和数据文件。
程序⽂件:包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows 环境后缀为.exe)。
数据文件:⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或者输出内容的⽂件
从文件的组织形式角度:可分为文本文件和二进制文件
二进制文件:数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的⽂件中,就是⼆进制⽂件。
文本文件:如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的⽂件就是⽂本⽂件。
以 10000(十进制)在文本文件和二进制文件中的储存为例:
3:文件的打开和关闭
1)流和标准流
1.1)流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念。
通过以下图片可以了解流带来的好处:
a:
b:
*可以看出,抽象出流的概念后,易于程序员对文件的读写操作
*C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。
1.2)标准流
C语⾔程序在启动的时候,默认打开了3个流:
• stdin-标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中 读取数据。
• stdout-标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出 到标准输出流中。
• stderr-标准错误流,⼤多数环境中输出到显⽰器界⾯。 这是默认打开了这三个流, 我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
*并且因为 C语⾔程序在启动的时候,默认打开了这3个流。我们在写C语言程序时能 直接从键盘输入数据,并在屏幕上显示数据,甚至在能进行代码报错
2)文件指针
每个被使⽤的⽂件都在内存中开辟了⼀个相应的文件信息区,⽤来存放⽂件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名FILE。
⼀般都是通过⼀个FILE的指针(类型为:FILE* )来维护这个FILE结构的变量,这样使用起来更加⽅便。
画图理解:
3):文件的打开和关闭
对文件进行操作时必须先打开文件,在进行读写操作,最后一定要关闭文件
3.1):fopen和fclose函数
fopen:
格式:FILE * fopen ( const char * filename, const char * mode )
filename为文件名,mode表示文件打开模式
功能:打开文件
返回值:若成功打开文件,则返回一个FILE*的指针;
若打开失败,则返回NULL
fclose:
功能:刷新Stream所指向的流,并关闭文件
格式:int fclose ( FILE * stream )
返回值:若成功关闭,则返回0;
否则返回EOF
3.2):文件的打开模式
(还有其它模式,这里就不一一列举了)
⽂件使⽤⽅式 含义 如果指定⽂件不存在
“r”(只读) 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 出错
“w”(只写) 为了输出数据,打开⼀个⽂本⽂件 建⽴⼀个新的⽂件
“a”(追加) 向⽂本⽂件尾添加数据 建⽴⼀个新的⽂件
“rb”(只读) 为了输⼊数据,打开⼀个⼆进制⽂件 出错
“wb”(只写) 为了输出数据,打开⼀个⼆进制⽂件 建⽴⼀个新的⽂件
“ab”(追加) 向⼀个⼆进制⽂件尾添加数据 建⽴⼀个新的⽂件
3.3):文件操作的基本格式
/* fopen fclose example */
#include <stdio.h>
int main ()
{
FILE * pFile;
//打开⽂件
pFile = fopen ("myfile.txt","w");
if (pFile == NULL)
{
printf("打开失败");
return -1;
}
//进行文件操作
fputs("abcdef", pFile);
//关闭文件
fclose(pFile);
return 0;
}
4:文件的顺序读取
文件顺序读取函数(每次读取成功后,再次调用函数会从下一个位置开始读取):
fgetc,fputc,fgets,fputs,fscanf,fprintf,fread,fwrite
函数 功能 适⽤于
fgetc 字符输⼊函数 所有输⼊流
fputc 字符输出函数 所有输出流
fgets ⽂本⾏输⼊函数 所有输⼊流
fputs ⽂本⾏输出函数 所有输出流
fscanf 格式化输⼊函数 所有输⼊流
Fprintf 格式化输出函数 所有输出流
fread ⼆进制输⼊ ⽂件输⼊流
fwrite ⼆进制输出 ⽂件输出流
1)fgetc
格式:int fgetc(FILE* Stream)
功能:从Stream指向的文件中读取字符,每次只读取一个字符
返回值:若读取成功则返回成功读取字符的ASCII码;
若遇到文件末尾或读取失败则返回EOF(文件结束的标致,值为 -1)
代码演示:
2)fputc
格式:int fputc(int Character, FIEL* Stream)
功能:将指定字符(ASCII码为Character)写入到Stream指向的文件
返回值:若写入成功,则返回指定字符的ASCII;
若写入失败则返回EOF
代码演示:
3)fgets
格式:char* fgets(char* Buffer, int Maxcount, FILE* Stream)
功能:从Stream指向的文件中读取字符写入Buffer指向的内存空间,并且最多向该内存空间写入长度为Maxcount - 1 的字符串。
返回值:若读取成功则返回Buffer;
若读取失败或遇到文件末尾则返回NULL
具体说明:
注意:Buffer指向内存空间的大小一定要大于等于Maxcount个字节,否则会发生缓冲区溢出
a:fgets函数可以简单理解为每次读取一行数据
b:当从Stream指向的文件读取的字符个数(以’\n’作为读取结束的标志)大于等于Maxcount - 1 时,则会将第Maxcount - 1个字符后加上 ’\0’ 后组成的字符串写入Buffer指向的内存空间
c:当从Stream指向的文件读取的字符个数(以’\n’作为读取结束的标志)小于Maxcount - 1 时,会将在读取到的最后一个字符,即’\n’,后加上’\0’后组成的字符串写入Buffer指向的内存空间
代码演示:
4)fputs
格式:int fputs(char* Buffer, FILE* Stream)
功能:将Buffer指向内存空间的字符串写入到Stream指向的文件中,而不会自动写入字符串结束符’\0’。注意Buffer指向的内存空间中一定要含有’\0’,当Buffer指向内存空间没有’\0’时,就会向Stream指向的文件中写入其他字符直至遇到’\0’
返回值:若写入成功,则返回一个非负数;
若写入失败,则返回EOF
代码演示:
5)fscanf
格式:int fscanf(FILE* Stream, const char* const Format, . . . )
功能:从Stream指向的文件中格式化读取数据存放在指定变量里,和scanf函数类似
返回值:若读取成功,则返回输入项数;
若只成功读取部分数据,则返回成功读取的项数;
若没执行任何转换就发生错误,则返回EOF
代码演示:
6)fprintf
格式:int fprintf(FILE* Stream, const char* const Format, . . . )
功能:向Stream指向的文件中格式化写入数据,和printf函数类似
返回值:返回写入的字符数;
若写入错误则返回负值
代码演示:
7)fread
格式:size_t fread(void* Buffer, size_t ElementSize, size_t ElementCount, FILE* Stream)
功能:从Stream指向的文件(指向二进制文件)中读取 ElementCount个元素大小为ElementSize个字节的数据,并写入Buffer指向的内存空间
返回值:返回实际读取的元素元素个数;
若返回值小于ElementCount,则可能是遇到文件结尾或发生错误
代码演示:
再使用fread函数从该二进制文件中读取数据,代码如下:
8)fwrite
格式:size_t fwrite(void* Buffer, size_t ElementSize, size_t ElementCount, FILE* Stream)
功能:从Buffer指向的内存空间读取 ElementCount个元素大小为ElementSize个字节的数据,并写入Stream指向的文件中
返回值:返回实际写入的项数;
若返回值小于ElementCount,则可能是遇到文件结尾或发生错误
代码演示:
5:文件的随机读写
文件随机读取函数:
1)fseek
格式:int fseek(FILE* Stream, long Offset, int Origin)
功能:重新定位文件指针,使文件指针指向Origin位置偏移Offset个字节后的位置
返回值:若定位成功,则返回0
否则返回其他值
具体说明:
Origin可取:SEEK_SET:指文件指针的起始位置
SEEK_CUR:指文件指针的当前位置
SEEK_END:指文件末尾
代码演示:
2)ftell
格式:long ftell(FILE* Stream)
功能:返回文件指针相对于起始位置的偏移量;
返回值:文件指针相对于起始位置的偏移量
代码演示:
3)rewind
格式:void rewind(FILE* Stream)
功能:让文件指针回到起始位置
返回值:void
代码演示:
6:文件读取结束的判断
EOF、feof、ferror
EOF为文本文件结束的结束标致,而feof, ferror函数则是用来在文件结束后,判断文件结束的原因
1)EOF
EOF作为文本文件的结束标志,在文本文件中,数据都是以ASCII码值的形式存放。fgetc, fputc等函数在读取文本文件时函数的返回值为对应字符的ASCII码值,而ASCII码值的取值范围为:0~127,所以EOF(值为-1)可以作为文件结束或写入错误的标致
代码演示:
2)feof
格式:int feof(FILE* Stream )
功能:判断文件结束的原因是遇到文件末尾还是发生错误
返回值:若返回非零值,则代表遇到文件末尾;
否则返回零
代码演示:
3)ferror
格式:int ferror(FIEL* Stream)
功能:检查是否设置了与流相关联的错误标识,来判断文件结束的原因是遇到文件末尾还是发生错误
返回值:若返回非零值,则代表成功设置了与流相关联的错误标识,文件结束的原因为发生错误;
否则返回0
代码演示:
注意:对同一文件每次调用输入输出函数,均会产生一个新的ferror函数值,所以应当在调用一个输入输出函数后立即检查ferror函数的值,否则信息会丢失。在执行fopen函数时,ferror函数的初始值自动置为0
7:文件缓冲区
ANSIC标准采⽤“缓冲⽂件系统”处理数据⽂件,所谓缓冲⽂件系统是指系统⾃动地在内存中为 程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据C编译系统决定的。
画图理解:
代码演示:
#include <windows.h>
#include <stdio.h>
int main() {
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf);
//将"abcdef"输入输入缓冲区
//10秒内test.txt文件中没有出现"abcdef"
// 说明缓冲区没有被装满,
// "abcdef"也就没有被输入到外存设备
printf("睡眠10秒,请打开并观察test.txt文件,发现没有 abcdef,请关闭test.txt\n");
Sleep(10000);
//刷新缓冲区,将"abcdef"输入到外存设备
fflush(pf);
printf("请重新打开并观察test.txt文件,发现有 abcdef\n");
//再次睡眠10秒,排除fclose函数刷新缓冲区的可能
Sleep(10000);
fclose(pf);
pf = NULL;
return 0;
}
*在两次提示打开并观察test.txt文件时,及时打开文件并进行观查。发现第一次无"abcdef"而第二次有"abcdef",说明缓冲区确实存在。