目录
7.2、文件光标移动函数 fseek(),文件字节数统计函数 ftell()
1、概念
●文件:存储在外部介质上数据的集合。
●我们用 "流" 表示数据从源端到目的端(终端)的流动。文件从外存被调入内存的过程就产生了输入流,从内存往外存中保存的过程就产生了输出流。
●文件光标:当我们打开一个文件时,鼠标在文件中点击一下,就会出现一个文件光标在跳动。这代表了我们可以在当前位置对文件进行编辑或者读取 。文件光标位置可以通过函数 fseek() 指定,在下面文件相关函数中介绍。
●文件结束标记EOF:在文件的末尾会有一个标记字符EOF来表示文件数据就那么多,后面没有了。EOF是一个宏,其值为-1。
2、文件路径
通过文件路径,我们可以知道文件在计算机外存中的存储位置,从而找到该文件,再进行目标操作。
2.1、绝对路径
绝对路径就是从根目录开始的完整的文件路径。Windows上根目录有C盘、D盘等等,Linux的根目录只有一个:/。
所以,在Windows电脑上一个绝对路径可以是 C:\Users\861\Pictures\画图\输入输出流.png。
但是,如果将这段路径保存为字符串时,就需要将单个反斜杠换成双反斜杠。因为单个反斜杠是转义字符的标志,双斜杠在字符串中才表示一个斜杠:"C:\\Users\\861\\Pictures\\画图\\输入输出流.png"。
也可以用正斜杠来代替反斜杠:"C:/Users/861/Pictures/画图/输入输出流.png"。
2.2、相对路径
相对路径就是在当前所在的目录(文件夹)下,文件相对于本文件夹的路径。所以,相对路径的前提是目标文件在本文件夹(或本文件夹的子文件夹)中。
对于Pictures文件夹,输入输出流.png文件的绝对路径就是 "画图/输入输出流.png"。
3、文件分类
3.1、文本文件(ASCII文件)
文本文件的后缀名为 .text、.cpp等,存储方式为字符串。也就是对于数字123,其在文本文件中以字符形式存储为'1','2',‘3’,大小为三字节。
3.2、二进制文件
多媒体文件全都存储为二进制文件(0101二进制机器码形式),后缀名为:.ppt、.mp3、.jng等。对于数字123,其在二进制文件中的存储形式为0111 1011,大小为1字节。
4、缓存
4.1、缓存
缓存是数据交换的缓冲区。
当CPU要读取数据时,先在缓存中查找数据,如果找到了数据,就直接使用;如果没找到,就去内存中找;如果内存中还没有就去外存中找。如果同一数据被多次从外存调入内存使用,系统就将该数据保存在缓存中,提高效率。
4.2、文件缓冲区
数据在存入文件,或我们通过scanf()对变量进行赋值时,不是一个数据一个数据进行操作的,而是先将数据保存在缓冲区,等数据数量达到上限,或输入结束,再将所有数据按顺序进行保存或赋值。
●ANSI C标准采用“缓冲文件系统”处理数据文件。
●文件系统:组织,管理文件的方法和数据结构。
●“缓冲文件系统”:系统自动地在内存中为每一个正在使用的文件开辟一个文件缓冲区。如果要将内存中的数据保存到外存的文件中,就将所有数据先输入到输出文件缓冲区中;等缓冲区被装满,或操作完成时,再将所有缓冲区的数据按顺序存储到外存中;然后对缓冲区进行刷新,等待下次操作。将文件数据调入内存中时原理相同。
缓冲区未装满之前,会进行等待,等到装满、操作完成或程序结束时,将数据进行向外存保存。目的是为了减少I/O调用,减少时间的浪费。
5、文件操作函数
5.1、文件打开
文件打开函数是fopen(),使用形式为:FILE* fp = fopen(" 文件路径.后缀名","读写模式");
●fopen()的返回值为文件类型的结构体指针 FILE* ,结构体的成员变量是文件的各种信息,指针指向了该文件。
●文件路径可以是绝对路径,也可以是相对路径,但要注意使用 "\\"或"/"。
●文件的读写模式分两大类,一种是针对文本文件,一种是针对二进制文件。其原理都相同,只不过二进制文件的读写模式在文本文件的读写模式后加上字母b。
以只读模式打开,就只能对文件数据进行读取,不能添加或修改;以只写模式打开,就只能对文件进行数据写入,不能读取。 所以,为了方便操作,一般选择r+、w+或a+模式打开文件。
5.2、文件数据写入
文件数据写入函数为 fwrite(),使用形式为:fwrite(buff,sizeof(数据类型),count,fp); 意为将buff字符串或字符数组中的count个数据写入到fp指针指向的文件中去。
●fwrite()的返回值为count,是写入到文件中的数据个数;
●sizeof(数据类型):是我们要写入文件中的数据的单个数据字节大小。
●count:这一次操作要向文件写入的数据个数;如果buff中的数据少于count,则系统会硬从缓冲区中读取够count个数据,写入到文件中。所以,count的大小应为buff数组中有效数据个数。
●fp:指向要写入数据的文件的指针。
5.3、文件数据读取
文件数据读取函数是fread(),其使用形式为fread(buff,sizeof(char),count,fp); 意为将fp指向的文件中最多count个数据读取到buff字符数组中。数据的读取是从文件数据首部开始,若文件数据量大于count,文件光标移动到count位置,下一次的读取从count位置开始。
●fread()返回值是成功从文件中读取到的实际数据个数,若fp指向文件的数据数量小于count,就只读取实际的数量。
●sizeof(char):单个数据大小。
●count:一次性想从fp指向的文件中读取的数据个数,注意不能超出buff的容量。
●fp:指向要读取数据的文件的指针。
5.4、文件关闭
文件关闭函数为fclose(),使用形式为fclose(fp);
文件被打开使用完之后,就需要关闭。正如上述所言,文件被使用时,系统会给文件开辟一块文件缓冲区,并且还会占据其他资源;如果只打开文件,而不关闭,必然会导致系统资源被浪费。
6、拷贝文件小练习
6.1、拷贝文本文件
对于一个文件"hello.text",其内容为"hello,world!",将其内容拷贝到一个新文件"newhello.text"中。
//以只读模式,打开hello.text文件,以fp1指向
//以r模式打开文件,只能对文件进行数据读取,
//若文件不存在,会返回NULL值
FILE* fp1 = fopen("C:\\Users\\86187\\Documents\\hello.txt","r");
assert(fp != NULL); //若打开失败,就中断程序
//以可读可写w+模式,打开newhello.text文件,以fp2指向
//系统中没有newhello.text,程序会自动创建文件
FILE* fp2 = fopen("C:\\Users\\86187\\Documents\\newhello.txt", "w+");
assert(fn != NULL);
//用来保存一次性读取到的数据,数组大小可以更大
//也可以用一个char型变量来保存,
//一次只读取一个字符,就比较慢
char buff[256]={0};
int count = 0;
//用count记录每次成功读取到的数据量
//若count==0,意味着文件数据被读取完了
while (count = fread(buff, sizeof(char), 255, fp)) {
//将buff中读取到的数据写入新文件
//读取到count个,就写入count个
//否则系统会强转从缓存区读取够你指定的数量
//就会读取到其他未知数据
fwrite(buff, sizeof(char), count, fn);
//将buff中数据清空,防止影响下次写入
memset(buff, 0, 256);
}
fclose(fp); //关闭文件
fclose(fn);
若执行成功,就会在指定路径下找到新拷贝的文件。
6.2、歌曲拷贝
在音乐软件中,下载一首普通歌曲。确认路径,然后以二进制文件形式打开。虽然二进制文件与文本文件对于数据的存储方式有所不同。如123:以文本文件存储为'1','2','3';以二进制文件存储为 123的二进制形式:0111 1011。但是,其最根本的形式还是二进制0101...机器码,所以我们直接按字节大小来读取。
一个char型数据容量为1字节,一个256个格子的字符数组能保存256个字节的数据。我们使用数组来保存读取到的字节数据,设置一次性读取的最大数据量为255,也可以是256、254......。count为实际读取到的数据字节数,根据实际字节数,将数据写入新文件。
//以二进制读模式打开歌曲
FILE* fp1 = fopen("D:/KuGou/EXO - 咆哮 (Chinese ver.).mp3", "rb");
//以二进制读写模式打开新歌曲文件
//没有文件,自动创建
FILE* fp2 = fopen("D:/KuGou/NewSong.mp3", "wb+");
char buff[256] = { 0 };
int count = 0;
while (count = fread(buff, sizeof(char), 255, fp1)) {
fwrite(buff, sizeof(char), count, fp2);
//清空buff数组,防止影响下次写入数据
memset(buff, 0, count);
}
fclose(fp1);
fclose(fp2);
若歌曲拷贝成功,就可以进行播放。新歌名为NewSong,歌手未知,但其他数据全部拷贝过来了。
7、文件相关函数
7.1、文件相关知识
1、我们先了解一下关于文件头部,光标当前位置和文件尾部的宏定义:
SEEK_SET:文件头部。数值:0
SEEK_CUR:文件光标当前位置。数值:1
SEEK_END:文件尾部。数值:2
2、标准输入stdin、标准输出stdout、标准错误stderr
在程序运行的时候,系统会默认开启三个输入输出流,stdin默认指向键盘,stdout和stderr默认指向屏幕,也可以重定向输出到文件中。这仨个流分布完成数据的传输:stdin将输入数据传输到指定位置,stdout将要输出的数据传输到指定位置,stderr将程序运行的错误信息传输到指定位置。在下方会配合文件相关函数,理解这三个流和文件函数。
7.2、文件光标移动函数 fseek(),文件字节数统计函数 ftell()
1、fseek(FILE* fp,int count,SEEK_SET):此时count需要 >= 0,因为当count为正时,代表将光标从指定的位置向后移动count位;当count为负时,代表将文件光标从指定位置向前移动count位;count为0时,代表就让鼠标待在指定的三个位置之一。
//指定文件光标,
//待在从文件开头向后移一位的位置
fseek(fp, 1, SEEK_SET);
//待在当前位置
fseek(fp, 0, SEEK_SCUR);
//待在从文件尾部向前移一位的位置
fseek(fp, -1, SEEK_END);
此时,还暂时没有体现出文件光标移动的意义,fseek()配合着统计光标之前的字节数函数ftell(),就体现出其一部分的作用了。
2、ftell(FILE* fp):统计该文件文件光标之前的数据字节数。如果我们想要通过代码知道一个文件的大小,就可以通过将文件光标移动到文件结尾,再统计光标之前的字节数,就可以知道文件的字节大小了。
将之前写入的内容清除,此时文件中只有12个字符,所以大小为12字节。
7.3、getc(),putc()
1、getc(FILE* fp)函数:从指定文件的文件光标处获取一个数据当作返回值返回。当文件刚被打开时,默认文件光标在文件开头。当getc() 获取到一个数据时,光标自动向后移动一位。
●使用方式:
FILE* fp = fopen("C:\\Users\\86187\\Documents\\hello.txt","a+");
assert(fp != NULL);
char a = getc(fp);
printf("%c", a);
//等价于
printf("%c", getc(fp));
fclose(fp);
连续使用,就可以按顺序获取到文件的数据。
●注意:对于二进制文件,其以二进制方式保存数据,如果该文件中刚好有 -1,其值与文件结束标记EOF的值相同,所以用getc()函数获取二进制文件的数据时可能会被误导。
char a = 0;
while ((a = getc(fp)) != EOF) {
printf("%c", a);
}
扩充:
若我们将getc获取数据的来源从文件改为输入流stdin,我们就实现的getchar()的功能:getc(stdin); <=> getchar(); //所以getchar()是getc()的特殊实现。//这也是一个专业术语--重定向,的实现。
2、putc(int a,FILE* fp)函数:将数据a输入到fp指向的文件中。如果文件是以写的w+或r+方式打开,a会从文件头开始保存,覆盖原数据;如果文件以a+方式打开,数据就从文件结尾开始保存。当a值的大小在ASCII码范围内,a被保存后,可以被识别为我们常见的字符;如果超出范围,我们打开文件就可能会显示乱码。
扩充:
若我们将putc输出数据的终端从文件改为输出流stdout,我们就实现的putchar()的功能:putc('a',stdout); <=> putchar(); //所以putchar()是putc()的特殊实现。
7.3、fgets(),fputs()
1、fgets(char* buff,int count,FILE* fp):将fp指向文件中的count个数据获取到字符串buff中。
当我们将其数据来源重定向为stdin时,我们就实现了gets()函数。读者可自行验证。
2、fputs(const char* buff,FILE* fp):将字符串buff中的数据输出到fp指向文件中。
当我们将其数据输出终端重定向为stdout时,我们就实现了puts()函数。读者可自行验证。
7.4、 fscanf()、fprintf()
1、fscanf(FILE*fp,Format 数据格式,数据去向):从文件中获取数据,以指定格式输入到指定去向。如:
//将fp指向的文件中光标位置的后两个数据
//以字符形式分别保存到a、b中
char a = 0, b = 0;
fscanf(fp, "%c%c", &a,&b);
printf("%c,%c",a,b);
将数据来源从文件修改为输入流stdin时,就可以实现scanf()函数:
2、fprintf(FILE* fp,Format 数据格式, 数据来源):将数据来源的数据以指定数据格式,写入文件fp。如:
将数据去向从文件修改为输出流stdout,就实现了printf()函数。读者可自行验证。
8、总结
1、在输入文件路径时,要注意斜杠问题,要使用双反斜杠"\\"或正斜杠'/'代替。
2、文件类型分为文本文件和二进制文件两种,但最根本的数据形式还是二进制机器码。我们在文本文件中编辑的数据都是各类字符,是我们认识的,所以文本文件以文本解析工具打开后我们还能认识其中内容。但二进制文件中全是机器码,其排列组合就不一定是我们能认识的字符或ASCII码表中的字符了,所以二进制文件以文本解析工具打开的话,其中有些字符我们能认识,但大部分都是乱码形式。
3、文本文件与二进制文件的打开模式相同,只不过多加了个字符b。
4、如果以一个字节一个字节的形式从二进制文件中读取数据,有可能读取到 -1的二进制(与文件结束标志相同)。让程序误以为读取到文件末尾了,导致出错。
5、文件数据读取与写入有缓冲区帮助,目的是为了减少I/O调用,避免浪费资源与时间。
6、各类从键盘输入和屏幕输出的函数,其实都是各类文件数据读取和写入函数的特殊实现。