C语言常见问题(八)——文件

        在实际应用中,有时会遇到这样一些问题:比如需要我们重复读入大量的数据,如果每次运行程序都要从键盘输入,就太麻烦了;又比如在程序运行结束后,我们还想查看程序运行的结果,如果程序比较大,再跑一次也不方便……

        为了解决这些问题,C引入了对文件的操作。文件是实现数据持久化的一种方式。

目录

一、文件概述

1.文件指针

2.文件路径

3.字节流

二、文件的读写 

1.文件的打开、关闭

1) 文件输入输出缓冲区

2) 打开文件fopen

3) 关闭文件fclose

2.文件定位

1) ftell:返回位置指针当前所处的位置

2) feof:标志位置指针是否指向文件末尾

3) rewind:使位置指针重新指向文件的开头

4) fseek:使位置指针指向origin+offset的位置

3.文件的读操作

1) 字符读入fgetc

2) 字符串读入fgets

3) 格式化输入fscanf

4) 数据块读fread

4.文件的写操作

1) 字符写入fputc

2) 字符串写入fputs

3) 格式化输出fprintf

4) 数据块写fwrite


一、文件概述

1.文件指针

        在以往的编程中,我们使用变量名来标识具体的变量,使用数组名来标识具体的数组。在读写文件时,我们使用文件指针来标识一个具体的文件。文件指针的类型是FILE * ,其定义通常如下:

FILE * fp = NULL;

FILE类型的定义位于stdio.h头文件中,此外,stdio.h还包含了文件标准输入输出等函数

2.文件路径

        1)首先我们需要知道什么是文件名。文件名是同一目录下文件的唯一标识,一般文件名的结构为“主文件名.拓展名”。例如一个C语言源程序文件hello.c,hello即是主文件名,c是拓展名。

        主文件名相同、拓展名不同的文件名标识的是两个不同的文件。例如hello.c和hello.exe是两个不同的文件,他们可以在同一目录下共存

        常见的文件拓展名有:txt、jpg、h、c、cpp、exe等

        2)文件路径分为绝对路径和相对路径。

        绝对路径直接指明了文件在硬盘上真正存在的位置,也可以理解为文件相对于根目录的位置。相对路径是指从某个基准目录开始,能找到文件位置的一条路径。

举个例子,在Windows系统下,对于VScodes文件夹下的文件p1.cpp,我们可以通过文件的属性查看其位置:

这个“位置”再加上文件名就是文件的绝对路径,即C:\C_program\VScode\p1.cpp

而VScode\p1.cpp就是文件p1.cpp相对于目录VScode的相对路径

3.字节流

        所有文件在计算机中都是以二进制形式存储的,但不同类别的文件编码和解码的方式有所不同。C语言的文件操作不区分文件类别,C把文件同一看作“字节流”,以字节为单位进行处理。


二、文件的读写 

1.文件的打开、关闭

        1) 文件输入输出缓冲区

        为了提高文件的读写速度,一般情况下我们不会直接读写文件,而是会先把要读写的数据复制到缓冲区中。

        对文件进行读操作时,我们会先读取文件的字节流,将其复制到输入文件缓冲区中,程序读取数据的时候从缓冲区中读入;对文件进行写操作时,我们也会把要存储的数据先写入输出文件缓冲区,等文件关闭时再把数据从缓冲区写入文件。文件标准输入输出的函数都是从/往缓冲区中读取/写数据的,这些函数被定义在stdio.h头文件中。

        2) 打开文件fopen

        在读写文件之前,我们需要打开文件。打开文件函数fopen完成了什么事呢?它将文件中的数据复制到了缓冲区,并返回了指向FILE对象的文件指针。fopen函数的原型:

    FILE * fopen(char * filename,char * mode);

        文件打开成功时,fopen返回FILE * 类型的指针;打开失败,则返回NULL。

        filename为文件路径,为了使程序具有较好的可移植性,我们通常使用相对路径,当文件与程序处于同一目录下时,filename填文件名就可以了。此外在C语言中' \ '属于转义字符,如果要在filename中使用的话,需要多加一个反斜杠:\\,例如C:\\C_program\\VScode\\p1.cpp

        mode是文件的打开模式,常见的文件打开模式有:

模式

含义

r

以只读方式打开ASCII文件,该文件必须存在

r+

可读方式打开ASCII文件,该文件必须存在

w

只写方式打开ASCII文件,原有内容全部清空,若文件不存在建立文件

w+

可读方式打开ASCII文件,原有内容全部清空,若文件不存在建立文件

a

以只写附加方式打开ASCII文件,新写入的数据加到文件尾,若文件不存在建立文件

a+

以可读写附加方式打开ASCII文件,新写入的数据会被加到文件尾,若文件不存在建立文件

rb

以只读方式打开二进制文件,该文件必须存在

rb+

以可读可写方式打开二进制文件,该文件必须存在

wb

以只写方式打开二进制文件,原有内容全部清空,若文件不存在会建立该文件

wb+

以可读可写方式打开二进制文件,原有内容全部清空,若文件不存在会建立该文件

ab

以只写附加方式打开二进制文件,新写入的数据追加到文件尾,若文件不存在会建立该文件

ab+

以可读写附加方式打开二进制文件,新写入的数据会被追加到文件尾,若文件不存在会建立该文件

         它的调用方式通常如下:

    FILE * fp=NULL;
    if( (fp=fopen("text.txt","r")) == NULL ){
    printf("File open error!\n");//文件打开失败
    exit(0);//退出程序
    }

        3) 关闭文件fclose

        关闭文件使用fclose函数,close执行后会将缓冲区中的数据写入文件。其函数原型为:

    int fclose(FILE *);

        文件关闭成功时返回0,否则返回EOF

        它的调用方式比较简单,例如:fclose(fp);

        尽管在C程序结束时会自动关闭文件,但是由于文件打开过多会使系统运行缓慢,所以对于不再使用的文件要及时关闭。

2.文件定位

        C语言把文件看做是字节流,使用文件读写位置指针来定位文件中正要操作的字节。为了方便对位置指针进行操作,stdio.h定义了下面几个函数:

1) ftell:返回位置指针当前所处的位置

        long int ftell(FILE * fp);

        //若操作成功则返回位置指针值,否则返回EOF(-1)

2) feof:标志位置指针是否指向文件末尾

        int feof(FILE * fp);

        //若位置指针位于文件末尾则返回EOF(-1),否则返回0

3) rewind:使位置指针重新指向文件的开头

        void rewind(FILE * fp);

        //无返回值

4) fseek:使位置指针指向origin+offset的位置

        int fseek(FILE * fp,long offset,int origin);

        //若操作成功返回0,否则返回非0值

        //feek一般用于二进制文件

3.文件的读操作

1) 字符读入fgetc

        int fgetc(FILE * fp);

        //操作成功返回读取字符的ASCII码;出错或文件结束返回EOF

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char ch=0;
    FILE * fp=NULL;
    if( (fp=fopen("text.txt","r")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    while(!feof(fp)){
        ch=fgetc(fp);
        printf("%c",ch);
    }
    fclose(fp);
    return 0;
}

2) 字符串读入fgets

        char * fgets(char * str,int num,FILE * fp);

        //从文件中读取num-1个字符(在字符串结尾添加\0)到str中

        //操作成功返回字符串str的地址,否则返回NULL

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char str[100]={0};
    FILE * fp=NULL;
    if( (fp=fopen("text.txt","r")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    fgets(str,10,fp);//从fp指向的文件中读取9个字符
    printf("%s\n",str);
    fclose(fp);
    return 0;
}

3) 格式化输入fscanf

        int fscanf(FILE * fp,char * format,arg_list);

        //将fp指向的文件按format格式转换,读取到arg_list中

        //若操作成功返回实际读取的参数个数,否则返回一个负数

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char str[100]={0};
    FILE * fp=NULL;
    if( (fp=fopen("text.txt","r")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    fscanf(fp,"%s",str);
    printf("%s\n",str);
    fclose(fp);
    return 0;
}

4) 数据块读fread

        int fread(void * buf,int size,int count,FILE * fp);

        /*从fp指向的文件读取count个size大小的字段,送入数据区buf的count个元素中,同时将读写位置指针向下移动size*count个字节*/

        //若操作成功返回实际读取的字段个数,出错或文件结束返回EOF

        fread是以二进制形式读入数据的,所以最好和fwrite配套使用。即fread读入的文件,在过去应该是以fwrite形式写入的,并且数据块的类型也要保持一致,否则容易出现乱码。(可以结合本文 数据块写fwrite 部分一起理解)

        

4.文件的写操作

1) 字符写入fputc

        int fputc(int ch,FILE * fp);

        //操作成功返回读取字符的ASCII码;出错或文件结束返回EOF

/*将a~z共26个字母写入文件text.txt中*/
#include <stdio.h>
#include <stdlib.h>
int main()
{
    char ch=0;
    FILE * fp=NULL;
    if( (fp=fopen("text.txt","w")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    for(ch='a';ch<='z';ch++)
        fputc(ch,fp);
    fclose(fp);
    return 0;
}

2) 字符串写入fputs

        int * fputs(char * str,FILE * fp);

        //把字符串str写入fp指向的文件中(不包括\0)

        //操作成功返回非负值,否则返回EOF

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char str1[10]="Hello",str2[10]=" World!";
    FILE * fp=NULL;
    if( (fp=fopen("text.txt","w")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    fputs(str1,fp);
    fputs(str2,fp);
    fclose(fp);
    return 0;
}

 

3) 格式化输出fprintf

        int fprintf(FILE * fp,char * format,arg_list);

        //将arg_list中各参数按format格式转换,输出到fp指向的文件中

        //若操作成功返回实际输出的参数个数,否则返回一个负数

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char str[100]="Hello World!";
    FILE * fp=NULL;
    if( (fp=fopen("text.txt","w")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    for(int i=0;i<100;i++){
        fprintf(fp,"Day%d %s\n",i,str);
    }
    fclose(fp);
    return 0;
}

 

4) 数据块写fwrite

        int fwrite(void * buf,int size,int count,FILE * fp);

        /*从数据区的buf中读取count个size大小的字段,送入fp指向的文件中,同时将读写位置指针向下移动size*count个字节*/

        //若操作成功返回实际写入的字段个数,出错或文件结束返回EOF

        如果使用fwrite写入文件,查看文件时发现有很多乱码,不用慌,这是正常情况。因为fwrite是以二进制形式写入文件的,如果你想查看这些数据,使用fread读入就好,注意数据块的类型最好保持一致。

例如,我们用fwrite往文件里写一个账单结构数组:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct PayList{
    int year,month,day; //日期:年、月、日
    char dish_name[30]; //菜名
    float money;        //价钱
}plist;
int main()
{
    plist paylist[31]={0};
    FILE * fp=NULL,* fp2=NULL;
    if( (fp=fopen("paylist.txt","w")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    for(int i=0;i<31;i++){
        paylist[i].year=2021;
        paylist[i].month=10;
        paylist[i].day=i+1;
        if(i%7==0){
            strcpy(paylist[i].dish_name,"黄焖鸡米饭\0");
            paylist[i].money=20;
        }else if(i%6==0){
            strcpy(paylist[i].dish_name,"水煮肉片\0");
            paylist[i].money=15;
        }else{
            strcpy(paylist[i].dish_name,"小碗菜\0");
            paylist[i].money=16;
        }
    }
    fwrite(paylist,sizeof(plist),31,fp);
    fclose(fp);
    return 0;
}

 我们发现paylist.txt文件中有很多乱码:

 接下来我们再写一个程序,从 paylist.txt 中读入10月1~10日的账单,将其打印在屏幕上;并且将这十天的账单分别用 fwrite 和 fprintf 拷贝到 copy1.txt 和 copy2.txt 文件中:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct PayList{
    int year,month,day; //日期:年、月、日
    char dish_name[30]; //菜名
    float money;        //价钱
}plist;
int main()
{
    int i=0;
    plist paylist[31]={0};
    FILE * fp=NULL,* fp1=NULL,* fp2=NULL;
    //打开文件:
    if( (fp=fopen("paylist.txt","r")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    if( (fp1=fopen("copy1.txt","w")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    if( (fp2=fopen("copy2.txt","w")) == NULL ){
        printf("File open error!\n");
        exit(0);
    }
    //读入十天的账单:
    fread(paylist,sizeof(plist),10,fp);
    //输出账单:
    for(i=0;i<10;i++)
        printf("%4d.%2d.%2d %10s %.2f\n",paylist[i].year,paylist[i].month,paylist[i].day,paylist[i].dish_name,paylist[i].money);
    //保存账单到文件中:
    fwrite(paylist,sizeof(plist),10,fp1);
    for(i=0;i<10;i++)
        fprintf(fp2,"%4d.%2d.%2d %10s %.2f\n",paylist[i].year,paylist[i].month,paylist[i].day,paylist[i].dish_name,paylist[i].money);
    fclose(fp);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

易水卷长空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值