一.相关概念

1.文件信息区
  每个要被使用的文件(比如要进行读或写操作),内存中都会开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字,位置,状态等).这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名FILE.
  知道了FILE该结构体是用来保存文件的相关信息的,那么下面来看看该c编译环境提供的stdio.h头文件中该结构体的定义代码

typedef struct
{short level;                    //缓冲区"满"或"空"的程度 unsigned flags;                 //文件状态标志 char fd;                        //文件描述符 unsigned char hold;             //如缓冲区无内容不读取字符 short bsize;                    //缓冲区的大小 unsigned char *buffer;          //数据缓冲区的位置 unsigned char *curp;            //指针当前的指向 unsigned istemp;                //临时文件指示器 short token;                   //用于有效性检查
}FILE;

2.文件类型指针

  这样声明: FILE *fp; 就得到了一个文件指针fp,那么这个文件指针是什么呢? 其实就是指向FILE结构体内存空间的指针. 上面第一点也说到,被使用的文件会在内存中开辟一片文件信息的存储空间,里面存储了文件的相关信息,所以FILE *fp=fopen(“flie.text”,“r”); 的意思就是file.txt被使用了,其文件的相关信息被加载进内存中,然后我们声明的文件指针就获得了该片内存的地址,有了地址我们就可以对文件进行相关操作了.就好比我们获得了某个整形变量的地址,我们通过指针就能改变整形变量的值了,一个意思.

3.文件缓冲区
  其实数据并不是直接就从内存输出到磁盘或从磁盘输出到内存的,他们之间要经过一个文件缓冲区.内存区会为程序中每个正在使用的文件开辟一个文件缓冲区.从内存向磁盘输出数据必须先送到内存的缓冲区,装满缓冲区后才一起送到磁盘中去.如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量).

4.fopen(文件名,文件使用方式)

              C程序设计(第四版)中的文件使用方式介绍

文件使用方式    含义   如果指定的文件不存在
“r”(只读) 为了输入数据,打开一个已存在的文本文件 出错
“w”(只写) 为了输出数据,打开一个文本文件 建立新文件
“a”(追加) 向文本文件末尾添加数据 出错
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件 建立新文件
“ab”(追加) 向二进制文件末尾添加数据 出错
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,打开一个文本文件 建立新文件
“a+”(读写) 为了读和写,打开一个文本文件 出错
“rb+”(读写) 为了读和写,打开一个二进制文件 出错
“wb+”(读写) 为了读和写,打开一个二进制文件 建立新文件
“ab+”(读写) 为了读和写,打开一个二进制文件 出错

              在DEV-C++中的实际操作情况

文件使用方式    含义   如果指定的文件不存在
“r”(只读) 打开一个文件,只能读 出错
“w”(只写) 打开一个文件,清空原文件的所有内容,只能写 建立新文件
“a”(追加) 打开一个文件,向文件末尾添加数据 ,只能写 建立新文件
“rb”(只读) 打开一个文件, 只能读 出错
“wb”(只写) 打开一个文件,清空原文件的所有内容,只能写 建立新文件
“ab”(追加) 打开一个文件,向文件末尾添加数据 ,只能写 建立新文件
“r+”(读写) 打开一个文件,可读写 出错
“w+”(读写) 打开一个文件,清空原文件的所有内容,可读写 建立新文件
“a+”(读写) 打开一个文件,向文件末尾添加数据 ,可读写 建立新文件
“rb+”(读写) 打开一个文件,可读写 出错
“wb+”(读写) 打开一个文件,清空原文件的所有内容,可读写 建立新文件
“ab+”(读写) 打开一个文件,向文件末尾添加数据 ,可读写 建立新文件

温馨提示:

  1.通过上面两个表,需要注意一定特别重要的一点,就是"w" , “wb” , “w+” , “wb+” 这四种方式会清空原文件的所有内容,所以这是四个方式要慎用,建议仅在新建文件或要特意清空文件内容时使用.

  2.通过实际操作我发现"a" , “ab” , “a+” , “ab+” 这四个操作在指定文件不存在时也能够新建文件,当然这个可能是因为编译器或者c语言版本的原因,既然书上说了不能新建,建议就按标准来吧,用"w" , “wb” , “w+” , "wb+"这四个操作来新建文件吧.

  3.使用"a+"或者"ab+"进行读操作时,要记得将文件读写位置置于有内容的位置(下文会介绍相关函数),因为打开方式为"a+"或者"ab+"的指针初始时文件读写位置在文件末尾.

5.数据文件的类型
  根据数据的组织形式,数据可分为ASCII文件和二进制文件.数据在内存中是以二进制形式存储的.而磁盘中可以存储二进制类型或ASCII类型.

二.文本文件(ASCII形式)读写函数介绍

1. 非格式化读写文件

1.fopen(文件指针,文件操作方式) //打开文件失败返回NULL
功能:打开文件,使其文件信息载入内存,然后返回指定文件的信息区的地址,若打开文件失败返回NULL
例子: FILE *fp=fopen(“file.txt”,“r”);//fp获得文件相关信息的存储区的地址,可通过指针fp进行相关操作

2.fclose(文件指针)
功能:关闭文件,撤销文件信息区和文件缓冲区,使文件不能在被操作,除非再次载入(fopen),成功关闭返回0,失败返回EOF(-1)
例子:fclose(fp); //关闭文件,撤销文件信息区和文件缓冲区,fp不再指向文件信息区,不能再通过该指针进行相关操作

3.fgets(str,n,fp) //参数分别是(字符数组,读取字符数量,文件指针)
功能:从fp指向的文件读入一个长度为(n-1)个字符的字符串,然后末尾加上一个字符串结束字符’\0’并存放到字符数组str中,读成功返回地址str,失败则返回NULL

4.fputs(str,fp) //参数分别是(字符数组,读取字符数量,文件指针)
功能:把字符数组str中的字符串写入到文件指针fp所指向的文件中.写成功返回0,失败返回非0值

5.fgetc(fp)
功能:从fp指向的文件读入一个字符,读成功返回所读字符,失败返回文件结束标志EOF(-1)
例子: char ch=fgetc(fp); //文件中读入一个字符并赋值给变量ch

6.fputc(ch,fp)
功能:把字符ch写到文件指针变量fp所指向的文件中,写入成功返回值就是写入的字符,失败返回EOF(-1)

注意:
1.就算你用的是二进制的文件操作方式,fputs函数也只能向文件中写入ASCII类型文件内容
2.就是你用的是二进制的文件操作方式,fgets函数也能从ASCII类型文件中正确读取内容.

接下来实验一下验证一下

#include"stdio.h"
int main()
{   FILE *fp=fopen("信息表","wb");           //二进制写入方式 char str[20]={"apple"};fputs(str,fp);                         //写数据fclose(fp);FILE *fp2=fopen("信息表","rb");          //二进制读入方式 char str2[20];fgets(str2,6,fp2);                      //读数据,会读入n-1个字符,apple有5个字符,所以填6 fclose(fp2);printf("信息表内容:%s\n",str2);   return 0;
}

操作结果:

控制台输出显示

文件内容

可以看到验证结果正确

好了,继续函数介绍

2.格式化读写文件(不推荐使用)

1.fprintf(文件指针,格式字符串,输出列表);
功能:使用格式化向文件中写入内容(ASCII码类型数据),类似于printf
例子:fprintf(fp,"%s",ch); //把字符串ch的内容写入文件中
2.fscanf(文件指针,格式字符串,输入列表);
功能:使用格式化从文件中读入内容(ASCII码类型数据),类似于scanf
例子:fscanf(fp,"%s",ch); //把文件中的一串字符串读入并赋值给字符串数组ch

操作示例:

#include"stdio.h"
typedef struct
{char name[20];     //姓名 int  old;         //年龄 float height;     //身高
}stu;
int main()
{   FILE *fp=fopen("信息表\0","wb");           //二进制写入方式 stu S1={"Andy",20,180.5};fprintf(fp,"%s,%d,%f",S1.name,S1.old,S1.height);  //写数据fclose(fp);FILE *fp2=fopen("信息表","rb");          //二进制读入方式 stu S2; fscanf(fp2,"%s,%d,%f",S2.name,&S2.old,&S2.height); //读数据fclose(fp2);printf("姓名:%s\n年龄:%d\n身高:%f",S2.name,S2.old,S2.height);     return 0;
}

操作结果

  这是怎么回事?原来字符数组name容量太大了,一次性把所有内容都读进数组了,所以年龄和身高都不能被读取到了…

注意事项:
  1.读文件时,fscanf(文件指针,格式字符串,输入列表);函数是按格式字符串的顺序来读取文件的,所以格式字符串顺序要与文件内容中类型顺序一样,如果文件中是:1.123456,12 那么格式字符串的顺序应该是%f,%d 否则读入的字符会有错误,系统是不会自动识别变量类型而自动把匹配类型的字符赋给它们的,所以文件内容中的类型顺序要和格式字符串顺序保持一致

  2.写文件时,fprintf(文件指针,格式字符串,输入列表);格式字符串类型要记得加上逗号隔开,如%d,%d 切记不要%d%d,因为比如写入20和180,那么文件中被写入内容为20180,可是这样是表示几个数呢? 那么fscanf(fp,"%d,%d",a,b)根本无法分别读入20和180,一次性就读入了整形20180并赋值给a,所以会导致从文件中读取内容时出错,因此切记格式字符串类型要记得加上逗号或空格隔开,如%d,%d,这样写入的内容就是20,180 ,之间有逗号隔开,表示有两项数据
  列举两种正确方式

//第一种,用逗号隔开
fprintf(fp,"%d,%d",a,b);   //写
fscanf(fp,"%d,%d",a,b);    //读//第二种,用空格隔开
fprintf(fp,"%d %d",a,b);  //写
fscanf(fp,"%d %d",a,b);   //读//注意:fscanf要于fprintf格式字符串类型的隔开方式一致,
//    即fprintf格式字符串类型用逗号隔开,那么fscanf也要用逗号隔开

  3.用fscanf或fgets,读文件时,要将文件中的ASCII码转换为二进制形式再保存在内存变量中,用fprintf写文件时又要将内存中的二进制形式转换为ASCII码,然后才写入文件中,这样要花费很多时间,所以在大量文件读写操作时不推荐使用fgets,fputs,fprintf,pscanf.

三.二进制文件读写函数介绍(推荐使用)

1.前言:

  前面介绍了fgets,fputs函数,但这两种函数只能适用于每次读入或写入都是相同字符数量的数据项的情况,要是数据字符数不相同就会造成数据混乱.而fscanf ,fprintf不太适合字符串类型的数据,fscanf对于相邻数据项是字符串类型会无法识别有几项数据项,造成读的错误.

  而且以上这些方式都是先将内存中的数据由二进制转换为ASCII形式,以ASCII码的文件内容形式写入磁盘的,而读入时,又要将磁盘中文件内容的ASCII码转化为二进制才能读入内存.这样相互转换会耗费很多时间,所以不推荐使用,于是下面推荐二进制的文件读写方式

2.函数介绍

1.fwrite(buff,size,count,fp)//函数执行成功会返回1,失败返回0
功能:文件内容的读写位置后移size个字节,每移动一个字节就写入一个字节的数据,这样就将buff指向的变量中的size个字节数据内容写入了磁盘的文件中,执行count次,也就是写入count项数据
2.fread(buff,size,count,fp) //函数执行成功会返回1,失败返回0
功能:文件内容的读写位置后移size个字节,每移动一个字节就读入一个字节的数据,执行count次,也就是读入count项数据,然后把从文件中读入的size*count个字节的数据内容并保存buff指向的变量内存空间中

说明:
buff: 变量的地址,fread中是读入文件中的数据并保存在变量中,buff就是那个变量的地址, fwrite中是将变量中的数据写入到文件中,buff就是那个变量的地址
size: 要读写的字节数
count: 要读写多少个数据项(每个数据项长度为size)
fp: FILE类型指针

3.rewind(文件指针)
功能:将文件读写位置置为文件内容第一个字节(文件内容从第1个字节开始读写)的前一字节位置,也就是0字节位置(文件中实际上并没有这个位置,只是个假定位置,为了方便操作而已)

4.fseek(文件指针,位移量,起始点)

起始点 名字 用数字代表
文件内容第一个字节的前端(0字节的位置) SEEK_SET 0
文件内容当前读写位置 SEEK_CUR 1
文件内容的最后一个字节处 SEEK_END 2

"位移量"指以"起始点"为基点,向前移动的字节数.位移量应是long型数据(在数字的末尾加一个字母L,就是表示long型).
fseek函数一般用于二进制文件.下面是fseek函数调用的几个例子

fseek(fp,100L,0);//将文件读写位置移动到离文件内容第1个字节的前端位置(0字节位置)的第100字节位置,也就是0+100
fseek(fp,100L,1);//将文件读写位置移动到离当前位置100字节的位置,也就是当前位置+100
fseek(fp,-100L,2);//将文件读写位置移动到离文件内容的最后一个字节的位置-100字节的位置,也就是文件内容最后一个字节位置-100

5.ftell(文件指针)
功能:返回文件读写的当前位置

6.feof(文件指针)
功能:检测是否读到文件的结束标志(末尾无字节内容处为结束标志),如果是返回true,否返回false

注意:这个函数并不推荐用来判断是否读到文件内容的末尾,因为假如文件内容有5项数据,
每项数据4个字节, while(!feof(fp)) 作为循环,当我们用5次循环读入文件内容后,文件读入位置是在第20字节处,这时feof(fp)判断第20个字节处没有读到结束标志,是有内容的,则返回false,所以还会进行一次循环,也就是进行第6次循环,这也就是为什么会多输出一项.

接下来我们操作比对一下
先用feof(fp)函数来作为读入结束判断条件

#include"stdio.h"
#include"string.h"
typedef struct
{char name[20];     //姓名 int  number;       //学号
}stu;                  //占24个字节
int main()
{   FILE *fp=fopen("信息表","wb");           //二进制写入方式 printf("\n姓名:");stu S;                                  scanf("%s",S.name);                                      while(strcmp(S.name,"#")!=0)              //以输入'#'结束输入操作 {          printf("学号:");scanf("%d",&S.number);        fwrite(&S,sizeof(stu),1,fp);         //向文件中写入一项结构体中数据,24个字节 printf("\n姓名:");scanf("%s",S.name);                      }fclose(fp);    FILE *fp2=fopen("信息表","rb"); int pos=ftell(fp2);                       //读写位置 printf("\n初始读写位置=%d\n\n",pos);stu S2; while(!feof(fp2))  {   pos=ftell(fp2);printf("读操作之前的读写位置=%d\n",pos);          //显示读操作之前的读写位置 fread(&S2,sizeof(stu),1,fp2);                   //读入一项数据,一项24个字节 printf("姓名:%s 学号:%d\n",S2.name,S2.number);pos=ftell(fp2);printf("读操作之后的读写位置=%d\n\n\n",pos);        //显示读操作之后的读写位置      } fclose(fp2);  return 0;
}

操作结果

  是吧,多进行了一次循环.
  是这样的,当读完内容的最后一项(第3项)数据时,此时文件读写位置位于内容的最后一个字节处,也就是72字节处,而用feof(fp)判断72字节处是否读到结束标志,72字节处是有内容的,所以返回fasle,所以会进行第4次循环.
  第四次循环过程是这样的:执行fread函数,读写位置先从72字节位置后移一个字节然后读入一个字节的数据,但是第73个字节的位置并没有内容,所以读到文件的结束标志,故读入数据失败,然后读写位置会从73自动回到72,但是为什么还会显示内容?那是因为读入数据失败,结构体S2没有被重新赋值,显示的是上一次循环的值,然后再回到循环条件判断,因为刚读73字节位置时读到了结束字符,feof(fp)此时就为真了,所以循环退出.

所以这里建议使用fread函数返回值作为读入数据的结束标志
操作一下

#include"stdio.h"
#include"string.h"
typedef struct
{char name[20];     //姓名 int  number;       //学号
}stu;                  //占24个字节
int main()
{   FILE *fp=fopen("信息表","wb");           //二进制写入方式 printf("\n姓名:");stu S;                                  scanf("%s",S.name);                                      while(strcmp(S.name,"#")!=0)              //以输入'#'结束输入操作 {          printf("学号:");scanf("%d",&S.number);        fwrite(&S,sizeof(stu),1,fp);         //向文件中写入一项结构体中数据,24个字节 printf("\n姓名:");scanf("%s",S.name);                      }fclose(fp);    FILE *fp2=fopen("信息表","rb"); int pos=ftell(fp2);                       //读写位置 printf("\n初始读写位置=%d\n\n",pos);stu S2; while(fread(&S2,sizeof(stu),1,fp2))  {         printf("姓名:%s 学号:%d\n",S2.name,S2.number);        } pos=ftell(fp2);                         //读写位置 printf("\n读数据操作完成后读写位置=%d\n\n",pos);fclose(fp2);return 0;
}

操作结果

是吧,这样就没什么毛病

  上面是两个指针进行的操作(一个用来写,一个用来读),当然我们也可以只用一个指针来操作(读写都是同一个指针),即使用可读可写的打开方式,但是注意要使用rewind函数或fseek函数,把文件读写位置移动到有内容的位置
操作一下

#include"stdio.h"
#include"string.h"
typedef struct
{char name[20];     //姓名 int  number;       //学号
}stu;                  //占24个字节
int main()
{   FILE *fp=fopen("信息表","wb+");           //二进制写入方式 printf("\n姓名:");stu S;                                  scanf("%s",S.name);                                    while(strcmp(S.name,"#")!=0)              //以输入'#'结束输入操作 {          printf("学号:");scanf("%d",&S.number);        fwrite(&S,sizeof(stu),1,fp);         //向文件中写入一项结构体中数据,24个字节 printf("\n姓名:");scanf("%s",S.name);                      }rewind(fp);      //将文件读写位置至于开头位置(0位置),使用fseek(fp,0L,0);也是等效的 int pos=ftell(fp);                  //读写位置 printf("\n初始读写位置=%d\n\n",pos);stu S2; while(fread(&S2,sizeof(stu),1,fp))  {        printf("姓名:%s 学号:%d\n",S2.name,S2.number);        } pos=ftell(fp);                         //读写位置 printf("\n读数据操作完成后读写位置=%d\n\n",pos);fclose(fp);     return 0;
}

操作结果

温馨提示:
  1.就算使用的是非二进制的打开方式(w,a等),用fwrite函数写入文件的也是二进制形式的数据,非二进制的打开方式(r,r+等),也照样可以用fread函数来读取二进制文件.当然我们最好还是按照标准来,使用rb,wb,ab等二进制打开方式

  2.不能存在2个以上的指针对同一文件同时进行操作,会导致文件数据损坏.即一个指针未关闭时(flcose(fp1)),不能再创建另一个指针(fp2)指向同一文件然后对其进行相关操作.一定要先关闭fp1,才能创建fp2对文件进行操作.

四.文件读写的出错检测

1.ferror函数
  在调用各种读写函数(如fgets,fwrite等)时,如果出现错误,除了函数返回值有所反映外,还可以用ferror函数检查.
  它的一般调用形式为ferror(fp);如果ferror返回值为0(false),表示未出错;如果返回一个非零值,表示出错.应该注意,对同一个文件每一次调用读写函数,都会产生一个新的ferror函数值,所以应当在调用一个读写函数后立即检查ferror函数的值,否则信息会丢失.
  在执行fopen函数时,ferror函数的初始值自动置为0.

2.clearerr函数
  clearerr的作用是使文件错误标志和文件结束标志置为0. 假设在调用一个读写函数出现错误,ferror函数值为非零值.应该立即调用clearerr(fp),使ferror(fp)的值变为0,以便在进行下一次的检测.

C语言_文件的读与写相关推荐

  1. java jxl 写 excel文件_使用jxl读和写Excel文件(速成)

    同行整理,简单明了,快速上手! =============================正文1============================ 最近和Excel频繁亲密接触,主要将N个Exc ...

  2. Go语言bufio包(读与写)

    Go语言bufio包(读与写) bufio:高效io读写 buffer缓存 io:input/output 将io包下的Reader,Write对象进行包装,带缓存的包装,提高读写的效率 ReadBy ...

  3. Python 学习笔记(3)对txt文件的读与写操作(下)

    上一章节我们讨论了如何对txt文本文件进行读写操作,这一张将讨论如何进行二进制文件的写与读.<Python 学习笔记(3)对txt文件的读与写操作(上)>的链接如下https://blog ...

  4. 文件的读和写(Python)

    文件的读和写(Python) 读文件 循环读取文件内容 写文件 序列化 反序列化 实例 读取图片 注意:文件夹和文件名是n,x,t,r,v,b等开头,会被转义的.但是大写可以,具体有哪些可以查查Pyt ...

  5. php打开文件读和写,PHP实现文件的读和写功能

    PHP实现文件的读和写功能 进行文件的读和写,先打开一个文件,然后开始读或者写文件,最后再关系这个文件资源. 如,文件的读操作: $file = fopen('your file path','r') ...

  6. 【C 语言】文件操作 ( 配置文件读写 | 写出或更新配置文件 | 追加键值对数据 | 更新键值对数据 )

    文章目录 一.追加键值对数据 二.更新键值对数据 三.完整代码示例 一.追加键值对数据 在上一篇博客 [C 语言]文件操作 ( 配置文件读写 | 写出或更新配置文件 | 逐行遍历文件文本数据 | 获取 ...

  7. Python 学习笔记(3)对txt文件的读与写操作(上)

    目录 1.file 对象 2.open() 方法 3.txt文件的读与写 3.1.写txt文件 3.2.读txt文件 3.2.在文件末尾续写文件 4.写在最后 1.file 对象 file 对象使用 ...

  8. Python3之文件的读、写、修改操作

    文件yesterday Somehow, it seems the love I knew was always the most destructive kind 不知为何,我经历的爱情总是最具毁灭 ...

  9. json文件的读与写

    本地JSON文件 的读与写 最近老师给我们一个日历让我们给日历添加记事本功能,推荐我们使用JSON文件,我之前从来没接触过JSON,在经过一系列摸爬滚打之后总算是取得了一些进展. 大致实现了 1.创建 ...

最新文章

  1. AI科研绘图3:排版
  2. 【C#】【APK】APK文件解析AXML-层层深入APK文件解析之一
  3. Edward Gaming, the Champion 字符串,模拟(2021.11.沈阳)
  4. 学习笔记(47):Python实战编程-pack布局
  5. 初探Golang(3)-数据类型
  6. 【LeetCode笔记】437. 路径总和III(Java、双重递归、二叉树)
  7. [转载]SIFT(尺度不变特征变换)算法小结
  8. jstack会导致JVM停顿
  9. 使用代码把一个目录打包成jar
  10. R语言及Rstudio入门小建议(一)
  11. 一个动态路由OSPF配置实例(eNSP)
  12. SECS/GEM协议库开发开源代码
  13. 外贸网站服务器搬迁方案,WordPress网站迁移到新服务器教程
  14. Beyond Compare 4 “授权秘钥已被吊销“ 的解决办法
  15. 神经网络拟合函数表达式,神经网络拟合函数matlab
  16. wincc c 语言改颜色,wincc常用c脚本小草设置
  17. 企业工商信息查询API开发文档
  18. 百度 翻译 api 使用
  19. 基于quartz开发企业级任务调度应用
  20. 告诉你1年读100本书的方法

热门文章

  1. scanner java_Scanner在java中有什么用法怎么用
  2. 引用的概念和使用方法
  3. 基于朴素贝叶斯分类器的钞票真伪识别模型
  4. 源码时代UI干货分享|史上最全的HTML三大列表的写法总结,快来收藏吧!
  5. 访问chrome扩展中心
  6. Python编程:协程coroutine
  7. 医疗疾病感染数据分析——以手术感染为例
  8. 使用Android Stdio和Sqlite查看debug apk的数据库
  9. 8、Java网络编程——MINA框架
  10. linux告警 微信,使用alertmanager实现微信报警