【C语言】文件操作

一.为什么使用文件?

我们在写程序时,我们输入的数据在程序运行结束后将不会被记录保存,那么这时我们可以使用C语言中的文件操作将我们所需要的数据永久保存在磁盘上,做到数据持久化,以便于下次我们不必再重复输入输入数据,可以直接在文件中读取数据

二.什么是文件?

持有数据的文件系统对象,能被写入或读取或二者皆可。文件拥有名称和属性,属性之一是文件类型,程序设计中我们一般(按文件的功能)将文件分为两类:

1.程序文件

文件的内容可以是程序,包括:源程序文件(后缀为.c

目标文件(windows环境后缀为.obj

可执行程序(windows环境后缀为.exe

2.数据文件

文件的内容可以是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者是输出内容的文件

那么,如何将程序中的数据输出到文件中,如何将文件中的数据输入到内存中?

注意:在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。

而现在我们要做的是:把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。


根据数据的组织形式,数据文件被称为文本文件或者二进制文件

文本文件

如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。(如整数10000,需要以ASCII码输出到磁盘上,则在磁盘中的存储形式就是10000).

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),以二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。

字符数据本身在内存中就经过了编码,所以无论是二进制还是文本形式都是一样的,而对于非字符数据来说要进行ASCII或者unicode编码。

二进制文件

把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放,即数据在内存中以二进制得到形式存储,不加转换的输出到外存,就是二进制文件。

用两种不同方式模拟计算机存储10000和10这两个数据:

在这里插入图片描述

从图中我们可以看出,从存储方式来说,文件在磁盘上的存储方式都是二进制形式,所以,文本文件其实也应该算二进制文件,区别就是二进制读写是将内存里面的数据直接读写入文本中,而文本是将数据先转换成了字符串再写入到文本中。

当以二进制形式存储整数10000:

int main(){
    FILE* pf=fopen("text.txt","wb");//打开
    if(pf==NULL){
        perror("fopen");
        return 1;
    }
    
    int a=10000;
    fwrite(&a,sizeof(int),1,pf);//存入整数10000
    fclose(pf);//关闭
    pf=NULL;
    return 0;
}

存入后使用记事本打开此文件会出现乱码(记事本看不懂二进制文件),那我们想看懂二进制文件该怎么办呢?

VS就可以帮助我们读取二进制文件:

img

将二进制文件添加到编译器中使用二进制编译器的打开方式打开:

img

可以看到10000这个数据在二进制中存储为10270000(前面00000000是地址不需要在意)。10270000是将二进制数据转换为十六进制并以小端存储方式显示的。

参考博文文本文件和二进制文件的差异和区别_请简述文本文件和二进制文件的区别-CSDN博客

3.文件名

命名一个文件的字符串。容许字符,大小写区别,最大长度以及被禁止名称是实现定义的。名称.(点)与..(双点)在库层次拥有特殊含义。

文件名包含3部分:文件路径+文件名主干+文件后缀,例如:c:\code\test.txt

三.文件的打开和关闭

1.文件指针

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件所在的当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE

FILE*pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能找到与它关联的文件。

2.文件操作——打开和关闭

//打开文件
FILE*fopen(const char* filename,const char* mode);
//filename为文件名,mode为打开方式
//关闭文件
int fclose(FILE* stream);

实例:

int main(){
    FILE* fp=NULL;
    fp=fopen("test.txt","w");//打开文件
    //text.txt在程序文件夹中可以直接这样写(相对路径),而绝对路径是完整的路径名
    //w是只写,r是只读
    
    //之间可以对文件进行操作
    
    fclose(fp);//关闭文件
    return 0}

文件的打开方式mode

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

四.文件的读写

1.顺序读写

功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输入流
文件行输入函数fgets所有输入流
文件行输出函数fputs所有输入流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输入流
二进制输入fread文件
二进制输出fwrite文件

fputc函数

C库函数int fputc(int char,FILE*stream)把参数char指定的字符(一个无符号字符)写入到指定的流stream中,并把位置标识符往前移动。

参数

  • char-这是要被写入的字符。该字符以其对应的int值进行传递
  • stream-这是指向FILE对象的指针,即输出流

返回值

成功时,返回被写入字符;失败时,返回EOF并设置stream上的错误指示器(见ferror()).

实例:

#include<stdio.h>
int main(){
    FILE* fp;
    int ch;
    fp=fopen("file.txt","w+");
    for(ch=33;ch<=100;ch++){//遍历文件中从第33到100的字符
        fputc(ch,fp);//把ch指定的字符写入到文件流fp中
    }
    fclose(fp);
    return 0;
}

fgetc函数

C库函数int fgetc(FILE* stream)从指定的流stream获取下一个字符(一个无符号字符),并把位置标识往前移动。

参数

  • stream-这是指向FILE对象的指针,即读取字符的来源

返回值

成功时以unsigned char获得并强制转换为int的形式返回读取的字符,失败时为EOF。

若文件尾条件导致失败,则另外设置stream上的文件尾指示器(见feof())。若某些其他错误导致失败,则设置stream上的错误指示器(见ferror())。

实例:

#include<stdio.h>
int main(){
    FILE* fp;
    int c;
    int n=0;
    
    fp=fopen("file.txt","r");
    if(fp==NULL){
        perror("打开文件时发生错误");
        return(-1);//返回一个代数值,表示该函数失败
    }
    do{
        c=fgetc(fp);
        if(feof(fp)){//检查是否已抵达给定文件流大的结尾
            break;
        }
        printf("%c",c);
    }while(1);//这是一个死循环,代码不再向下执行
    
    fclose(fp);
    return(0);
}

fputs函数

C库函数int fputs(const char* str,FILE* stream)字符串写入到指定的流stream中,但不包括空字符。

如同通过重复执行fputc。

参数

  • str-这是一个数组,包含了要写入的空终止字符串
  • stream-这是指向FILE对象的指针,该FILE对象标识了要被写入字符串的流,即输出流

返回值

成功时,返回非负值;失败时,返回EOF并设置stream上的错误指示器(见ferror)。

实例:

#include<stdio.h>
int main(){
    FILE* fp;
    fp=fopen("file.txt","w+");
    fputs("这是C语言。",fp);
    fputs("这是一种系统程序设计语言。",fp);
    fclose(fp);
    return 0;
}

fgets函数

C库函数char* fgets(char* str,int n,FILE* stream)从指定的流stream读取一行,并把它储存在str所指向的字符串内。当读取(n-1)个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

参数

  • str-这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n-这是要读取的最大字符数(包括最后的空字符)。通常是使用以str传递的数组长度。
  • stream-这是指向FILE对象的指针,即读取数据来源的文件流

返回值

成功时,该函数返回相同的str参数,失败时(到达文件末尾或者没有读取到任何字符或者发生错误)为空指针。

实例:

#include<stdio.h>
int main(){
    FILE* fp;
    char str[60];
    
    fp=fopen("file.txt","r");//打开用于读取的文件
    if(fp==NULL){
        perror("打开文件时发生错误")retur(-1);
    }
    if(fgets(str,60,fp)!=NULL){
        puts(str);//向标准输出stdout写入内容  
    }
    fclose(fp);
    return(0);
}

fprintf函数

C库函数int fprintf(FILE* stream,const char* format,...)发送格式化输出到流stream中。

参数

  • stream-要写入的输出文件流

  • format-这是C字符串,包含了要被写入到流stream中的文本。它可以包含嵌入的format标签,format标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format标签属性是

    在这里插入图片描述

    具体讲解如下:

    flags(标识)描述
    -在给定的字段宽度内左对齐,默认是右对齐(参见width子说明符)
    +强制在结果之前显示加号或减号(+或-),即正数前面会显示+号。默认情况下,只有负数前面会显示一个-号
    (space)如果没有写入任何符号,则在该值前面插入一个空格
    #与o.x或X说明符一起使用时,非零值前面会分别显示0,0x或0X。与e,E和f一起使用时,会强制输出包含一个小数点,即使后边没有数字时也会显示小数点。默认情况下,如果后边没有数字时候,不会显示小数点。与g或G一起使用e或E时相同,但是尾部的零不会被移除。
    0在指定填充padding的数字左边按放置零(0),而不是空格(参见width子说明符)
    width(宽度)描述
    (number)要输出的字符的最小数目。如果输出的值短于该数,结果会用空格填充。如果输出的值长于该数,结果不会被截断。
    *宽度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
    .precision(精度)描述
    .number对于整数说明符(d,i,o,u,x,X):precision指定了要写入的数字的最小位数。如果写入的值短于该数,结果会用前导零来填充。如果写入的值长于该数,结果不会被截断。精度为0意味着不写入任何字符。对于额,E和f说明符:要在小数点后输出的小数位数。对于g和G说明符:要输出的最大有效位数。对于s:要输出的最大字符数。默认情况下,所有字符都会被输出,直到遇到末尾的空字符。 对于 c 类型:没有任何影响。 当未指定任何精度时,默认为 1。如果指定时不带有一个显式值,则假定为 0。
    .*精度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
    length(长度)描述
    h参考被解释为短整型或无符号短整型(仅适用于整数说明符:i,d,o,u,x和X)
    l参数被解释为长整型或无符号长整形,适用于整数说明符(i,d,o,u,x和X)及说明符c(表示一个宽字符)和s(表示宽字符字符串)
    L参数被解释为长双精度型(仅适用于浮点数说明符:e,E,f,g和G)
    specifier(说明符)输出
    c字符
    d或i有符号十进制整数
    e使用e字符的科学计数法
    E使用E字符的科学计数法
    f十进制浮点数
    g自动选择%e或%f中合适的表示法
    G自动选择%E或%f中合适的表示法
    o有符号八进制
    s字符的字符串
    u无符号十进制整数
    x无符号十六进制整数
    X无符号十六进制整数(大写字母)
    p指针地址
    n无输出
    %字符
  • 附加参数-根据不同的format字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了format参数中指定的每个%标签。参数的个数应与%标签的个数相同。

返回值

成功时,返回写入的字符总数,失败时返回一个负数。

#include<stdio.h>
#include<stdlib.h>
int main(){
    FILE* fp;
    fp=fopen("file.txt","w+");
    fprintf(fp,"%s %s %s %d","We","are","in",2024);//发送格式化输出到文件流fp中
    fclose(fp);
    return(0);
}

fscanf函数

C库函数int fscanf(FILE* stream,const char* format,...)从流stream读取格式化输入,即按照format转译并将结果存储到指定位置。

参数

  • stream-这是指向FILE对象的指针

  • format-C字符串,包含了一下各项中的一个或多个:空格字符,非空格字符和format说明符。format说明符形式为在这里插入图片描述
    具体讲解如下:

  • 参数描述
    *这是一个可选的星号,表示数据是从流 stream 中读取的,但是可以被忽视,即它不存储在对应的参数中。
    width这指定了在当前读取操作中读取的最大字符数
    modifiers为对应的附加参数所指向的数据指定一个不同于整型(针对 d、i 和 n)、无符号整型(针对 o、u 和 x)或浮点型(针对 e、f 和 g)的大小: h :短整型(针对 d、i 和 n),或无符号短整型(针对 o、u 和 x) l :长整型(针对 d、i 和 n),或无符号长整型(针对 o、u 和 x),或双精度型(针对 e、f 和 g) L :长双精度型(针对 e、f 和 g)
    type一个字符,指定了要被读取的数据类型以及数据读取方式。具体参见下一个表格。

    fscanf类型说明符:

    类型合格的输入参数的类型
    c单个字符:读取下一个字符。如果指定了一个不为 1 的宽度 width,函数会读取 width 个字符,并通过参数传递,把它们存储在数组中连续位置。在末尾不会追加空字符。char *
    d十进制整数:数字前面的 + 或 - 号是可选的。int *
    e,E,f,g,G浮点数:包含了一个小数点、一个可选的前置符号 + 或 -、一个可选的后置字符 e 或 E,以及一个十进制数字。两个有效的实例 -732.103 和 7.12e4float *
    o八进制整数int *
    s字符串。这将读取连续字符,直到遇到一个空格字符(空格字符可以是空白、换行和制表符)。char *
    u无符号的十进制整数unsigned int *
    x,X十六进制整数int *
  • 附加参数-根据不同的format字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了format参数中指定的每个%标签。参数的个数应与%标签的个数相同。

返回值

成功时,返回成功匹配和赋值的个数,失败时(如果到达文件末尾或发生读错误),则返回EOF

实例:

#include<stdio.h>
#include<stdlib.h>
int main(){
    char str1[10],str2[10],str3[10];
    int year;
    FILE* fp;
    
    fp=fopen("file.txt","w+");
    fputs("We are in 2024",fp);//把字符串写入到文件流fp中
    
    rewind(fp);
    fscanf(fp,"%s %s %s %d",str1,str2,str3,&year);//从文件流fp中读取格式化输入
    
    printf("Read String1 |%s|\n",str1);
    printf("Read String2 |%s|\n",str2);
    printf("Read String3 |%s|\n",str3);
    printf("Read Integer |%d|\n",year);
    
    fclose(fp);
    return(0);
}

fwrite函数

C库函数size_t fwrite(const void* ptr,size_t size,size_t nmemb,FILE* stream)把ptr所指向的数组中的数据写入到给定流stream中

参数

  • ptr-这是指向要被写入的首个元素数组的指针
  • size-这是要被写入的每个元素的大小,以字节为单位
  • nmemb-这是元素的个数,每个元素的大小为size字节,即要被写入的对象数
  • stream-这是指向FILE对象的指针,即指向输出流的指针

返回值

成功时,返回一个size_t对象,表示元素的总数

fread函数

C库函数size_t fread(void* ptr,size_t size,size_t nmemb,FILE* stream)从给定流stream读取数据到ptr所指向的数组中

参数

  • ptr-这是指向带有最小尺寸size* nmemb字节的内存块的指针
  • size-要读取的每个元素的大小,以字节为单位
  • nmemb-元素的个数,每个元素的大小为size字节
  • stream-指向FILE对象的指针,即读取来源的输入文件流

返回值

成功时,成功读取的元素总数会以size_t对象返回,size_t对象是一个整型数据类型。

如果总数与nmemb参数不同,则可能发生了一个错误或者到达了文件末尾。

#include<stdio.h>
#include<string.h>
int main(){
    FILE* fp;
    char c[]="This is runoob";
    char buffer[20];
    
    fp=fopen("file.txt","w+");//打开文件用于读写
    fwrite(c,strlen(c)+1,1,fp);//写入数据到文件
    fseek(fp,0,SEEK_SET);//查找文件的开头
    fread(buffer,strlen(c)+1,1,fp);//读取并显示数据
    printf("%s\n",buffer);
    fclose(fp);
    return(0);
}

2.随机读写

fseek函数

C库函数int fseek(FILE* stream,long int offset,int whence)设置文件流stream的文件位置为给定的偏移offset所指向的值,参数offset意味着从给定的whence位置查找的字节数。

根据文件指针的位置和偏移量来定位文件指针

FILE与FILE stream的区别

FILE是一个静态类,FILE stream是一个非静态类;

最直接区别:将读取文件比作是从A桶往B桶运水。使用FILE就是整个用桶倒进去,使用FILE stream就是使用水管慢慢输送,应用场景不同,大文件推荐FILE stream,不会炸内存。

FILE:是一个文件的类,对文件进行操作。其内部封装了对文件的各种操作(MSDN:提供用于创建,复制,删除,移动和打开单一文件的静态方法,并协助创建FILE stream对象)。

FILE stream:是一个文件流的类,处理文件的原始字节,即处理byte[]。对txt,xml,avi等任何文件进行内容写入,读取,复制…

参数

  • stream-这是指向FILE对象的指针,即要修改的文件流

  • offset-这是相对whence的偏移量,以字节为单位。

  • whence-这是表示开始添加偏移offset的位置。它一般指定为下列常量之一:

    常量描述操作
    SEEK_SET文件的开头即从文件的最前端处开始向后偏移offset字节
    SEEK_CUR文件指针的当前位置当前文件指针的偏移处开始向后偏移offset字节
    SEEK_END文件的末尾即从文件的最末尾处开始向前偏移offset字节,当然在偏移数一定要为负数才能读取文件中的内容。

返回值

如果成功,则该函数返回为零,否则返回非零值。

实例:

#include<stdio.h>
int main(){
    FILE* pFile;//定义一个文件
    pFILE = fopen("example.txt","wb");//以读写形式打开这个二进制文件
    fputs ("This is an apple.",pFile);//字符形式输出该文件
    fseek (pFile,9,SEEK_SET);//从该文件开头偏移9字节的位置读取
    fputs ("sam",pFile);//
    fclose (pFile);//关闭文件
    return 0;
}

ftell函数

C库函数```long int ftell(FILE* stream)返回给定流stream的当前位置,即返回文件指针相对于起始位置的偏移量。

参数

  • stream-这是指向FILE对象的指针,即要检验的文件流

返回值

成功时,返回位置标识符的当前值,失败时,返回-1L,全局变量errno被设置为一个正值。

实例:

#include<stdio.h>
int main(){
    FILE* fp;
    int len;
    fp=fopen("file.txt","r");
    if(fp==NULL){
        perror("打开文件错误");
        return(-1);
    }
}

rewind函数

C库函数void rewind(FILE* stream)设置文件位置为给定流stream的文件的开头

参数

  • stream-要修改的文件流

返回值

实例:

#include<stdio.h>
int main(){
    int n;
    FILE* pFile;
    char buffer[27];
    pFile=fopen("myfile.txt","w+");
    for(n='A';n<='Z';n++)
        fputc(n,pFile);
    rewind(pFile);
    fread(buffer,1,26,pFile);
    fclose(pFile);
    buffer[26]='\0';
    puts(buffer);
    return 0;
}

五.文件读取结果的判定

feof函数的错误使用:

在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。

而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。(feof函数是判断结束过程而不是判断结束的结果)

文本文件读取是否结束,判断返回值是否为EOF(getc)或者NULL(fgets)

例如:

  • fgetc判断是否为EOF
  • fgets判断返回值是否为NULL

实例:

#include<stdio.h>
#include<stdlib.h>
int main(void){
    int c;//注意:int,非char,要求处理EOF
    FILE* fp=fopen("test.txt","r");
    if(!fp){
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while((c=fgetc(fp))!=EOF){//标准C I/O读取文件循环
        putchar(c);
    }
    
    if(ferror(fp))//判断是什么原因结束的
        puts("I/O error when reading");
    else if(feof(fp))
        puts("End of file reached successfully");
    fclose(fp);
}

二进制文件读取是否结束,判断返回值是否小于实际要读的个数。

例如:fread判断返回值是否小于实际要读的个数

实例:

#include<stdio.h>
enum{SIZE=5};
int main(void){
    double a[SIZE]={1.,2.,3.,4.,5.};
    FILE* fp=fopen("test.bin","wb");//必须用二进制形式
    fwrite(a,sizeof* a,SIZE,fp);//写double的数组
    
}

六.文件缓冲区

说到文件缓冲区,我们就自然而然想到输入缓冲区,即当一个字符一个字符从键盘上输入时,并不是直接输入到磁盘内,而是先放到输入缓冲区,而当输入缓冲区内的字符放满后,文件缓冲区才向磁盘内输入字符。

文件缓冲区也是一样的道理。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

测试代码:

#include<stdio.h>
#include<windows.h>
//VS2013 WIN10环境测试
int main(){
    FILE* pf=fopen("test.txt","w");
    fputs("abcdef",pf);//先将代码放在输出缓冲区
    printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n")Sleep(10000);
    printf("刷新缓冲区\n");
    fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
    //注:fflush 在高版本上的VS上不能使用了
    printf("再睡眠10秒-此时,再次打开test.txt文件,文件内有内容了\n")Sleep(10000);
    fclose(pf);//注:fclose再关闭文件时,也会刷新缓冲区
    pf=NULL;
    return 0;
}

我们可以测试一下这个代码,再程序第一个到fgets函数处时,立刻去打开test.txt文本文件,我们会发现里面没有内容,而我们用刷新文件缓冲区的fflush函数再次打开test.txt文本文件时,会发现里面已经有输入的内容,则能够证实的确有文件缓冲区的存在。

因为有文件缓冲区的存在,C语言再操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。

  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值