C语言对文件的操作

操作文件的正确流程为:打开文件 --> 读写文件 --> 关闭文件。文件在进行读写操作之前要先打开,使用完毕要关闭。文件流:在《载入内存,让程序运行起来》一文中提到,所有的文件(保存在磁盘)都要载入内存才能处理,所有的数据必须写入文件(磁盘)才不会丢失。数据在文件和内存之间传递的过程叫做文件流,类似水从一个地方流动到另一个地方。数据从文件复制到内存的过程叫做输入流,...
摘要由CSDN通过智能技术生成

 

操作文件的正确流程为:打开文件 --> 读写文件 --> 关闭文件。文件在进行读写操作之前要先打开,使用完毕要关闭。

 

文件流:

在《载入内存,让程序运行起来》一文中提到,所有的文件(保存在磁盘)都要载入内存才能处理,所有的数据必须写入文件(磁盘)才不会丢失。数据在文件和内存之间传递的过程叫做文件流,类似水从一个地方流动到另一个地方。数据从文件复制到内存的过程叫做输入流,从内存保存到文件的过程叫做输出流。

文件是数据源的一种,除了文件,还有数据库、网络、键盘等;数据传递到内存也就是保存到C语言的变量(例如整数、字符串、数组、缓冲区等)。我们把数据在数据源和程序(内存)之间传递的过程叫做数据流(Data Stream)。相应的,数据从数据源到程序(内存)的过程叫做输入流(Input Stream),从程序(内存)到数据源的过程叫做输出流(Output Stream)。

输入输出(Input output,IO)是指程序(内存)与外部设备(键盘、显示器、磁盘、其他计算机等)进行交互的操作。几乎所有的程序都有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息,或者是把信息传递给外界。

打开文件:

所谓“打开文件”,就是让程序和文件建立连接的过程。

打开文件之后,程序可以得到文件的相关信息,例如大小、类型、权限、创建者、更新时间等。在后续读写文件的过程中,程序还可以记录当前读写到了哪个位置,下次可以在此基础上继续操作。

标准输入文件 stdin(表示键盘)、标准输出文件 stdout(表示显示器)、标准错误文件 stderr(表示显示器)是由系统打开的,可直接使用。

使用 <stdio.h> 头文件中的 fopen() 函数即可打开文件,它的用法为:

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

filename为文件名(包括文件路径),mode为打开方式,它们都是字符串。

mode的类型:

控制读写权限的字符串(必须指明)
打开方式说明
"r"以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。
"w"以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
"a"以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
"r+"以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。
"w+"以“写入/更新”方式打开文件,相当于wr+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
"a+"以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
控制读写方式的字符串(可以不写)
打开方式说明
"t"文本文件。如果不写,默认为"t"
"b"二进制文件。


 "rb"、"wb"、"ab"、"rb+"、"wb+"、"ab+"如果处理的是二进制文件,则需使用下面的打开模式来取代上面的打开模式。

fopen() 函数的返回值

fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 FILE 类型的结构体变量中,然后将该变量的地址返回。

FILE 是 <stdio.h> 头文件中的一个结构体,它专门用来保存文件信息。我们不用关心 FILE 的具体结构,只需要知道它的用法就行。

如果希望接收 fopen() 的返回值,就需要定义一个 FILE 类型的指针。例如:

FILE *fp = fopen("demo.txt", "r");

表示以“只读”方式打开当前目录下的 demo.txt 文件,并使 fp 指向该文件,这样就可以通过 fp 来操作 demo.txt 了。fp 通常被称为文件指针。

FILE *fp = fopen("D:\\demo.txt","rb+");

表示以二进制方式打开 D 盘下的 demo.txt 文件,允许读和写。

判断文件是否打开成功

打开文件出错时,fopen() 将返回一个空指针,也就是 NULL,我们可以利用这一点来判断文件是否打开成功,请看下面的代码:

FILE *fp;
if( (fp=fopen("D:\\demo.txt","rb") == NULL ){
    printf("Fail to open file!\n");
    exit(0);  //退出程序(结束程序)
}

代码解析:

我们通过判断 fopen() 的返回值是否和 NULL 相等来判断是否打开失败:如果 fopen() 的返回值为 NULL,那么 fp 的值也为  NULL,此时 if 的判断条件成立,表示文件打开失败。
以上代码是文件操作的规范写法,读者在打开文件时一定要判断文件是否打开成功,因为一旦打开失败,后续操作就都没法进行了,往往以“结束程序”告终。

关闭文件

文件一旦使用完毕,应该用 fclose() 函数把文件关闭,以释放相关资源,避免数据丢失。fclose() 的用法为:

int fclose(FILE *fp);

fp 为文件指针。例如:

fclose(fp);

文件正常关闭时,fclose() 的返回值为0,如果返回非零值则表示有错误发生。

注意事项


       1)调用fopen打开文件的时候,一定要判断返回值,如果文件不存在、或没有权限、或磁盘空间满了,都有可能造成打开文件失败。

       2)文件指针是调用fopen的时候,系统动态分配的内存,如果文件操作完了、或函数返回或程序退出的时候,必须用fclose关闭文件指针,释放内存,否则后果严重。

       3)如果文件指针是空的,用fclose关闭它相当于操作空指针,后果严重。

字符读取函数 fgetc

fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符。fgetc() 的用法为:

int fgetc (FILE *fp);

fp 为文件指针。fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF

EOF 是 end of file 的缩写,表示文件末尾,是在 stdio.h 中定义的宏,它的值是一个负数,往往是 -1。fgetc() 的返回值类型之所以为 int,就是为了容纳这个负数(char不能是负数)。

实例演示

最后,我们通过一段完整的代码来演示 fopen 函数的用法,这个例子会一行一行地读取文本文件的所有内容:

demo.txt文件:

I am a test demo!!!
Do you like it.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    FILE *fp=NULL;
    char ch;

    //判断文件是否存在,如果不存在就给出提示并退出
    if ((fp = fopen("d:/sfiles/VScode/code/demo.txt","r"))==NULL)
    {
        printf("Fail to open file!\n");
        exit(1);        //out
    }

    //遍历文件,并将文件的字符逐个输出
    while ((ch=fgetc(fp))!=EOF)
    {
        putchar(ch);
    }
    putchar('\n');
    if(fp!=NULL) fclose(fp);        //一定要先判断fp是否是空指针,如果是空指针关闭文件后果很严重

    return 0;
}
解析:

程序第 13 行是关键,while 循环的条件为(ch=fgetc(fp)) != EOF。fgetc() 每次从位置指针所在的位置读取一个字符,并保存到变量 ch,位置指针向后移动一个字节。当文件指针移动到文件末尾时,fgetc() 就无法读取字符了,于是返回 EOF,表示文件读取结束了。

对 EOF 的说明(end of file)

EOF 本来表示文件末尾,意味着读取结束,但是很多函数在读取出错时也返回 EOF,那么当返回 EOF 时,到底是文件读取完毕了还是读取出错了?我们可以借助 stdio.h 中的两个函数来判断,分别是 feof() 和 ferror()。

feof() 函数用来判断文件内部指针是否指向了文件末尾,它的原型是:

int feof ( FILE * fp );

当指向文件末尾时返回非零值,否则返回零值。

ferror() 函数用来判断文件操作是否出错,它的原型是:

int ferror ( FILE *fp );

出错时返回非零值,否则返回零值。

 

上面的示例基本能够保证将文件内的数据读取完毕。如果追求完美,也可以加上判断并给出提示:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    FILE *fp=NULL;
    char ch;

    //判断文件是否存在,如果不存在就给出提示并退出
    if ((fp = fopen("d:/sfiles/VScode/code/demo.txt","r"))==NULL)
    {
        printf("Fail to open file!\n");
        exit(1);        //out
    }

    //遍历文件,并将文件的字符逐个输出
    while ((ch=fgetc(fp))!=EOF)
    {
        putchar(ch);
    }
    putchar('\n');

    if(ferror(fp))                  //判断是否是文件操作错误,是的话则输出
        puts("read error");
    else 
        puts("read succeed");
        
    if(fp!=NULL) fclose(fp);        //一定要先判断fp是否是空指针,如果是空指针关闭文件后果很严重
    return 0;
}

 

字符写入函数 fputc

fputc 是 file output char 的所以,意思是向指定的文件中写入一个字符。fputc() 的用法为:

int fputc ( int ch, FILE *fp );

ch 为要写入的字符,fp 为文件指针。fputc() 写入成功时返回写入的字符,失败时返回 EOF,返回值类型为 int 也是为了容纳这个负数。例如:

fputc('a', fp);

或者:

char ch = 'a';
fputc(ch, fp);

表示把字符 'a' 写入fp所指向的文件中。

两点说明

1) 被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,并将写入的字符放在文件开头。如需保留原有文件内容,并把写入的字符放在文件末尾,就必须以追加方式打开文件。不管以何种方式打开,被写入的文件若不存在时则创建该文件。

2) 每写入一个字符,文件内部位置指针向后移动一个字节。

【示例】从键盘输入一行字符,写入文件。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    FILE *fp1=NULL;
    char c;

    if((fp1 = fopen("d:/sfiles/VScode/code/demo1.txt","wt+"))==NULL)
    {
        printf("Fail to open file!\n");
        exit(1);
    }

    //每次循环向demo.txt文件添加字符
    while ((c=getchar())!='\n')     /* 字符写入*/
    {   
        fputc(c,fp1);
    }

    if(fp1!=NULL) fclose(fp1);        //一定要先判断fp是否是空指针,如果是空指针关闭文件后果很严重

    return 0;
}

fgetc() 和 fputc() 函数每次只能读写一个字符,速度较慢;实际开发中往往是每次读写一个字符串或者一个数据块,这样能明显提高效率

读字符串函数 fgets

fgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中,它的用法为:

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

str 为字符数组,n 为要读取的字符数目,fp 为文件指针

返回值:读取成功时返回字符数组首地址,也即 str;读取失败时返回 NULL;如果开始读取时文件内部指针已经指向了文件末尾,那么将读取不到任何字符,也返回 NULL。

注意,读取到的字符串会在末尾自动添加 '\0',n 个字符也包括 '\0'。也就是说,实际只读取到了 n-1 个字符,如果希望读取 100 个字符,n 的值应该为 101。例如

#define N 101
char str[N];
FILE *fp = fopen("D:\\demo.txt", "r");
fgets(str, N, fp);

 

表示从 D:\\demo.txt 中读取 100 个字符,并保存到字符数组 str 中。

需要重点说明的是,在读取到 n-1 个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。这就意味着,不管 n 的值多大,fgets() 最多只能读取一行数据,不能跨行。在C语言中,没有按行读取文件的函数,我们可以借助 fgets(),将 n 的值设置地足够大,每次就可以读取到一行数据。

【示例】一行一行地读取文件。

#include <stdio.h>
#include <stdlib.h>
#define N 100
int main(){
FILE *fp;
char str[N+1];
if( (fp=fopen("d:\\demo.txt","rt")) == NULL ){
puts("Fail to open file!");
exit(0);
}

while(fgets(str, N, fp) != NULL){
printf("%s", str);
}

fclose(fp);
return 0;
}

将下面的内容复制到 D:\\demo.txt:

C language 
http://c.biancheng.net
good coding website

那么运行结果为:


fgets() 遇到换行时,会将换行符一并读取到当前字符串。该示例的输出结果之所以和 demo.txt 保持一致,该换行的地方换行,就是因为 fgets() 能够读取到换行符。而 gets() 不一样,它会忽略换行符。

写字符串函数 fputs

fputs() 函数用来向指定的文件写入一个字符串,它的用法为:

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

str 为要写入的字符串,fp 为文件指针。写入成功返回非负数,失败返回 EOF。例如:

表示把把字符串 str 写入到 D:\\demo.txt 文件中。


【示例】向上例中建立的 d:\\demo.txt 文件中追加一个字符串。

#include<stdio.h>
int main(){
    FILE *fp;
    char str[102] = {0}, strTemp[100];
    if( (fp=fopen("D:\\demo.txt", "at+")) == NULL ){
        puts("Fail to open file!");
        exit(0);
    }
    printf("Input a string:");
    gets(strTemp);
    strcat(str, "\n");
    strcat(str, strTemp);
    fputs(str, fp);
    fclose(fp);
    return 0;
}

运行程序,输入C++ Java Linux Shell,打开 D:\\demo.txt,文件内容为:

C语言中文网
http://c.biancheng.net
一个学习编程的好网站!
C C++ Java Linux Shell

向文件中写入数据用的比较多的是:fprintf函数

函数声明如下:

int fprintf(FILE *fp, const char *format, ...);

向文件中写入数据
       C语言向文件中写入数据库函数有fputc、fputs、fprintf,在实际开发中,fputc和fputs没什么用,只介绍fprintf就可以了。fprintf函数的声明如下:

              int fprintf(FILE *fp, const char *format, ...);

       fprintf函数的用法与printf相同,只是多了第一个参数文件指针,表示把数据输出到文件。

       一般情况下,程序员不必关心fprintf函数的返回值。

  样例一:

#include<stdio.h>
int main(){
    FILE *fp;
    if( (fp=fopen("d:/sfiles/VScode/code/demo.txt", "a")) == NULL ){
        puts("Fail to open file!");
        return -1;
    }
    for (int  i = 0; i < 5; i++)
    {
        fprintf(fp,"I like you!!!");
    }
    fclose(fp);
    return 0;
}

解析:
        

样例二:

#include<stdio.h>
int main(){
    FILE *fp;
    char str[102] = {0}, strTemp[100];
    if( (fp=fopen("d:/sfiles/VScode/code/demo.txt", "a")) == NULL ){
        puts("Fail to open file!");
        return -1;
    }
    for (int  i = 0; i < 5; i++)
    {
        fprintf(fp,gets(str));
    }
    fclose(fp);
    return 0;
}

 样例三:

#include<stdio.h>

#define N 2

struct stu{
    char name[10];
    int num;
    int age;
    float score;
} boya[N], boyb[N], *pa, *pb;

int main(){
    FILE *fp;
    int i;
    pa=boya;
    pb=boyb;
    if( (fp=fopen("D:\\demo.txt","wt+")) == NULL ){
        puts("Fail to open file!");
        exit(0);
    }

    //从键盘读入数据,保存到boya
    printf("Input data:\n");
    for(i=0; i<N; i++,pa++){
        scanf("%s %d %d %f", pa->name, &pa->num, &pa->age, &pa->score);   
    }
    pa = boya;
    //将boya中的数据写入到文件
    for(i=0; i<N; i++,pa++){
        fprintf(fp,"%s %d %d %f\n", pa->name, pa->num, pa->age, pa->score);   
    }
    //重置文件指针
    rewind(fp);
    //从文件中读取数据,保存到boyb
    for(i=0; i<N; i++,pb++){
        fscanf(fp, "%s %d %d %f\n", pb->name, &pb->num, &pb->age, &pb->score);
    }
    pb=boyb;
    //将boyb中的数据输出到显示器
    for(i=0; i<N; i++,pb++){
        printf("%s  %d  %d  %f\n", pb->name, pb->num, pb->age, pb->score);
    }

    fclose(fp);
    return 0;
}

二进制文件的读写
      二进制文件没有行的概念,存放的数据也不是字符串,不存在以0结尾的情况。

      我们直接把内存中的数据结构写入二进制文件,读取的时候,也是从文件中读取数据结构的大小一块数据,直接保存到数据结构中。

1、向文件中写入数据
       fwrite() 库函数用来向文件中写入块数据,它的原型为:

              size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

       参数的说明:

       ptr:为内存区块的指针,存放了要写入的数据的地址,它可以是数组、变量、结构体等。

       size:固定填1。

       nmemb:表示打算写入数据的字节数。

       fp:表示文件指针。

       函数的返回值是本次成功写入数据的字节数,一般情况下,程序员不必关心fwrite函数的返回值。

       示例(book115.c)

       

       编译并运行程序,得到数据文件,用vi命令打开文件,显示如下:

       

       可以看到很多乱码,其实并不是文件的内容乱,而是vi无法识别文件的格式,把内容当成ASCII码显示,文件中的字符串是ASCII码,所以能正确显示,但年龄和身高是整数,就无法显示了。

2、从文件中读取数据
       fread() 库函数用来从文件中读取块数据,它的原型为:

              size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);

         ptr:用于存放从文件中读取数据的变量地址,它可以是数组、变量、结构体等。

       size:固定填1。

       nmemb:表示打算读取的数据的字节数。

       fp:表示文件指针。

       调用fread函数如果成功的读取到内容,函数返回读取到的内容的字节数,如果读取错误或文件已结束,返回空,即0。如果fread返回空,可以认为是文件结束而不是发生了错误,因为发生错误的情况极少出现。

       示例(book117.c)

       

       运行结果

       

3、注意事项
       1)我对fread和fwrite函数的size和nmemb以及它们的返回值的解释是不正确的,我这么做的原因是为了方便大家的使用,正确的解释会把大家搞晕,等你功力够的时候,我们再讨论它的准确含义。

       2)fwrite和fread函数也可以写入和读取文本文件,但是没有换行的概念,不管是0还是换行或其它的特殊字符,无区别对待。

       3)二进制文件有自已的数据格式,写入数据时要按约定的格式写,读取的时候也要按约定的格式读取,book115.c写入的是超女数据结构数据,book117.c就要用超女数据结构来存放读取的数据,这道理就像图片查看软件无法打开音频文件,音频播放软件也无法打开图片文件。

       4)如果程序员不知道二进制文件的格式,也可以用fread和fwrite函数读写文件,例如文件复制和文件传输程序,它不会去解析文件的数据,所以不必关心文件的格式。

       示例(book119.c)

       

       运行结果

    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值