最近写哈夫曼树编码的项目,重新学习了一遍C的文件操作主要的几个函数,感觉大体上文件操作需要的函数就是开关、读写和定位,并且这次写代码的过程中还踩了fgetc()读取二进制文件的大坑,现做一个总结。
fopen函数
fopen()函数
1.作用: 在C语言中fopen()函数用于打开指定路径的文件,获取指向该文件的指针。
2.函数原型:
FILE * fopen(const char * path,const char * mode);
-- path: 文件路径,如:"F:\Visual Stdio 2012\test.txt"
-- mode: 文件打开方式,例如:
"r" 以只读方式打开文件,该文件必须存在。
"w" 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
"w+" 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
"a" 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
"a+" 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。(原来的EOF符不保留)
"wb" 只写打开或新建一个二进制文件,只允许写数据。
"wb+" 读写打开或建立一个二进制文件,允许读和写。
"ab" 追加打开一个二进制文件,并在文件末尾写数据。
"ab+"读写打开一个二进制文件,允许读,或在文件末追加数据。
--返回值: 文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回NULL,并把错误代码存在errno中。
fopen_s 函数
FILE *fp1;//建立一个文件操作指针
errno_t err; //判断此文件流是否存在 存在返回1
err = fopen_s(&fp1,"File Information.txt", "a"); //若return 1 , 则将指向这个文件的文件流给fp1
fprintf(fp1,"input format : %s \n",name); //写入
fclose(fp1);
fseek函数
函数名: fseek
功 能: 重定位流上的文件指针
用 法: int fseek(FILE *stream, long offset, int fromwhere);
描 述: 函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。
返回值: 成功,返回0,否则返回其他值。
第一个参数stream为文件指针
第二个参数offset为偏移量,整数表示正向偏移,负数表示负向偏移
第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2.
简言之:
fseek(fp,100L,0);把fp指针移动到离文件开头100字节处;
fseek(fp,100L,1);把fp指针移动到离文件当前位置100字节处;
fseek(fp,100L,2);把fp指针退回到离文件结尾100字节处。
C语言 fread()与fwrite()函数说明与示例
1.作用
读写文件数据块。
2.函数原型
(1)size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
其中,ptr:指向保存结果的指针;size:每个数据类型的大小;count:数据的个数;stream:文件指针
函数返回读取数据的个数。
(2)size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
其中,ptr:指向保存数据的指针;size:每个数据类型的大小;count:数据的个数;stream:文件指针
函数返回写入数据的个数。
3.注意
(1)写操作fwrite()后必须关闭流fclose()。
(2)不关闭流的情况下,每次读或写数据后,文件指针都会指向下一个待写或者读数据位置的指针。
4.读写常用类型
(1)写int数据到文件
#include <stdio.h>
#include <stdlib.h>
int main ()
{
FILE * pFile;
int buffer[] = {1, 2, 3, 4};
if((pFile = fopen ("myfile.txt", "wb"))==NULL)
{
printf("cant open the file");
exit(0);
}
//可以写多个连续的数据(这里一次写4个)
fwrite (buffer , sizeof(int), 4, pFile);
fclose (pFile);
return 0;
}
(2)读取int数据
#include <stdio.h>
#include <stdlib.h>
int main () {
FILE * fp;
int buffer[4];
if((fp=fopen("myfile.txt","rb"))==NULL)
{
printf("cant open the file");
exit(0);
}
if(fread(buffer,sizeof(int),4,fp)!=4) //可以一次读取
{
printf("file read error\n");
exit(0);
}
for(int i=0;i<4;i++)
printf("%d\n",buffer[i]);
return 0;
}
执行结果:
5.读写结构体数据
(1)写结构体数据到文件
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct{
int age;
char name[30];
}people;
int main ()
{
FILE * pFile;
int i;
people per[3];
per[0].age=20;strcpy(per[0].name,"li");
per[1].age=18;strcpy(per[1].name,"wang");
per[2].age=21;strcpy(per[2].name,"zhang");
if((pFile = fopen ("myfile.txt", "wb"))==NULL)
{
printf("cant open the file");
exit(0);
}
for(i=0;i<3;i++)
{
if(fwrite(&per[i],sizeof(people),1,pFile)!=1)
printf("file write error\n");
}
fclose (pFile);
return 0;
}
(2)读结构体数据
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct{
int age;
char name[30];
}people;
int main () {
FILE * fp;
people per;
if((fp=fopen("myfile.txt","rb"))==NULL)
{
printf("cant open the file");
exit(0);
}
while(fread(&per,sizeof(people),1,fp)==1) //如果读到数据,就显示;否则退出
{
printf("%d %s\n",per.age,per.name);
}
return 0;
}
执行结果:
fscanf和fprintf的用法
fscanf()
1.功能:
从文件指针fp指向的文件中,按format中对应的控制格式读取数据,并存储在agars对应的变量中;若读取数据成功会返回所读取数据的个数,并将数据按照指定格式存入内存中的变量或数组中,文件指针自动向下移动;若读取失败则返回EOF。
2.原型:
fscanf(FILE *fp, const char *format, agars)
#include<stdio.h>
#include<stdlib>
int main()
{
FILE *fp;
char ch;
fp = fopen("test.txt","r");
if(fp == NULL)
{
printf("Open filefailure!");
exit(1);
}
else
{
fscanf(fp,"%s",&ch);
}
printf("%s\n",ch);
fclose(fp);
return 0;
}
注:
1.如果要读取一个整数(该整数必须在所存变量的数据类型表示的范围之内)则为:fscanf(fp, “%d”, &ch),而此时ch应该定义为int;若读取的数据大于int所能表示的范围,则读取的数据屏幕显示为负数,即读取的数据发生越界,如果此时的ch依然为char型,则运行时报错(内存读写错误)。
对于fscanf()主要应用在按行读取一个文件中的所有内容或依次读取每行相隔的几个数据,具体参照以下示例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp;
char *ch, *ah;
ch =(char *) malloc(sizeof(char) * 100);
ah =(char *) malloc(sizeof(char) * 100);
fp = fopen("test.txt","r");
if(fp == NULL)
{
printf("Open filefailure!");
exit(1);
}
else
{
while(!feof(fp))
{
fscanf(fp, “%s”, ch);
printf(“%s”, ch);//这两行为按行读取所有数据
fscanf(fp, “%s%s”, ch, ah);
printf(“The value of ch and ah is:%s %s\n”,ch,ah);//这两行为分别读取每行相隔的几个数据
}
}
printf("%s\n",ch);
free(ch);
free(ah);
fclose(fp);
return 0;
}
fprintf()
1.功能:
将agars(参数表)内各项的值,按format(格式控制字符串)所表示的格式,将数据格式为字符串的形式写入到文件指针fp指向的文件中。
2.原型:
fprintf(FILE *fp, const char *format, agars)
fprintf()和fscanf()相对应,其用法也基本和fscanf()相同。具体参照以下示例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp;
fp = fopen("test.txt","a+");
fprintf(fp,“%d %d”,123456,789);//将123456和789写到test.txt文件中
fprintf(fp,"%s %s","China","ChongQing"); //将字符串China和ChongQing追加写到test.txt文件中
fclose(fp);
return 0;
}
了解fscanf,fread的用法和区别
首先一定要记住fread函数只用于读二进制文件,而fscanf可以读文本也可以读二进制。
其次在用链表进行文件的读取建议用fscanf(当然也可以先踩踩坑)。
文本方式不能完全读取, 而二进制方式能的原因
文本方式读取文件, 最主要的用处是一次读取一整句( 以换行符’/n’, 即二进制的换行标志”/r/n”结束 ), 方便用于特殊用处ReadString、fscanf(…,”%s”,…)之类, 每次读取的内容长度是不定的;
而二进制读取方式Read、fread等, 都是读取固定长度 所以文本方式读取对EOF的判定, 是一个文件尾结束标志, 如果是文本文件, 则这个文件尾肯定不会出现在文件内容中( 因为是不可打印字符构成的结束标志, 人可读的文本文件不会包括它 ), 这样以结束标志为文件尾则是可以的;
fgetc函数踩过的坑
二进制文件内容可以是任意字节, 如果把它当文本文件来读, 以文件尾为结束, 当然可能出现把文件内容判定为文件尾的情况;
(这个坑博主在写哈夫曼树编码项目的读取压缩文件时踩过)
二进制读取方式由于每次读取固定字节, 所以只需要用总文件长度( 这个数值是系统管理的数值, 不是计算得出来的 )减去每次读取的长度( 或根据Seek的位置计算长度 ), 就可以知道是否到文件尾, 不需要定义结束标志; 所以用二进制方式打开任何文件都是合理的
(这个解决方案可以完美解决上一句的问题,并且特别适用于处理压缩文件的读取,防止为了写入方便填入的二进制位被误解压成字符)