C语言——文件的操作

1. 需要了解的概念

需要理解的知识点包括:数据流、缓冲区、文件类型、文件存取方式
 

1.1 数据流:

指程序与数据的交互是以流的形式进行,文件由输入流形式组成进行C语言文件的存取时,都会先进行“打开文件”操作,这个操作就是在打开数据流,而“关闭文件”操作就是关闭数据流。

1.2 缓冲区(Buffer):

指在程序执行时,所提供的额外内存,可用来暂时存放做准备执行的数据。它的设置是为了提高存取效率,因为内存的存取速度比磁盘驱动器快得多。

C语言中带缓冲区的文件处理:

C语言的文件处理功能依据系统是否设置“缓冲区”分为两种:一种是设置缓冲区,另一种是不设置缓冲区。由于不设置缓冲区的文件处理方式,必须使用较低级的I/O函数(包含在头文件io.h和fcntl.h中)来直接对磁盘存取,这种方式的存取速度慢,并且由于不是C的标准函数,跨平台操作时容易出问题。下面只介绍第一种处理方式,即设置缓冲区的文件处理方式:

当使用标准I/O函数(包含在头文件stdio.h中)时,系统会自动设置缓冲区,并通过数据流来读写文件。当进行文件读取时,不会直接对磁盘进行读取,而是先打开数据流,将磁盘上的文件信息拷贝到缓冲区内,然后程序再从缓冲区中读取所需数据,如下图所示:

事实上,当写入文件时,并不会马上写入磁盘中,而是先写入缓冲区,只有在缓冲区已满或“关闭文件”时,才会将数据写入磁盘,如下图所示。


1.3 文件类型:

分为文本文件和二进制文件两种。

文本文件是以字符编码的方式进行保存的。二进制文件将内存中数据原封不至文件中,适用于非字符为主的数据。如果以记事本打开,只会看到一堆乱码。

其实,除了文本文件外,所有的数据都可以算是二进制文件。二进制文件的优点在于存取速度快,占用空间小,以及可随机存取数据。

1.4 文件存取方式:

包括顺序存取方式和随机存取方式两种。

顺序读取也就是从上往下,一笔一笔读取文件的内容。保存数据时,将数据附加在文件的末尾。这种存取方式常用于文本文件,而被存取的文件则称为顺序文件。

随机存取方式多半以二进制文件为主。它会以一个完整的单位来进行数据的读取和写入,通常以结构为单位。


1.5  文件指针与文件位置指针:

分清文件位置指针(IO流上的文件指针)和文件指针的区别,前者是光标位置,表示当前读写数据的位置,后者是指向文件的“FILE类型变量”。

文件指针并不指向文件的储存位置。文件指针实际上是一个指向由系统定义的结构体的指针,结构体是在文件打开时由系统自动创建的,里面包含了各种处理文件所需要的信息。文件指针的移动是指在文件之间来移动。

1.6  补充:

1、只有文本文件是以EOF作为结束标志。

2fgetc在读入最后一个字符时依旧返回的是该字符,读完了才返回EOF

3EOF的值为-1

4EOFC定义的,而非windows,不然在linux就不能运行了。

5、换行符也会被fgetcfgets所读取。


2. 文本文件操作

C语言中主要通过标准I/O函数来对文本文件进行处理。相关的操作包括打开、读写、关闭与设置缓冲区。
相关的存取函数有:fopen(), fclose(), fgetc(), fputc(), fgets(), fputs(), fprintf(), fscanf()fwrite()fread()等。

fopen 正常返回文件指针(FILE),否则返回NULL(空指针为NULL!)

fclose正常返回0,否则返回非0值。(正常可读返回0否则返回非0

feof未完返回0,表示还可以读,已完返回非0值。(正常可读返回0否则返回非0

fputc正常返回字符,否则返回EOF

fgetc 正常返回字符,否则返回EOF

fputs正常返回0,否则返回EOF

fgets正常返回字符串,否则返回NULLgets因为是得到字符串,而EOF不算是字符串,所以返回NULL更好)

fseek成功返回0,否则返回非0址。(正常返回0)(作用:移动文件位置指针)

ftell成功返回偏移字节数,错误返回-1(可记成EOF,因为ftell函数是获取文件的当前指针位置相对于文件首地址的偏移字节数,不存在-1这一说)

fprintf()函数返回值是写入文件的字符个数,fwrite()函数返回值是写入文件的元素个数,如果与所注明写入个数一致则正常。(个数)

fscanf返回的是实际读取的数据个数,出错或者到结尾返回EOF

fread()函数如果调用成功返回实际读取到的元素个数,如果不成功或读到文件末尾返回 0

       fputcfgetcfputsfgets这四个只要知道不正常的情况下会返回什么就行,正常情况我们应用不上。

2.1 打开文件

函数原型为:

FILE *fopen(const char *path, const char *mode);

第一参数为文件名,第二个参数为打开模式。其中,文件名和打开模式都是要用" "来括起来。(即使打开模式是一个字符而已也要)

正常的地址:C:\Users\HYF\Desktop\d2.txt

C中,因为 \ 是转义字符,所以地址应为C:\\Users\\HYF\\Desktop\\d2.txt

打开成功,fopen返回一个结构指针地址,否则返回一个NULL(因为返回值是一个指针)如果没有指定文件路径,则默认为当前工作目录exe程序执行目录)

文件可以没有后缀名。但如果有一定要加上。

FILE  * fp;     // 别忘了定义一个FILE的指针去接收返回值
fp  =  fopen( " c:\\temp\\test.txt "  " r "  // 由于反斜杠\是控制字符,所以必须再加一个反斜杠

 

使用fopen()函数打开的文件会先将文件复制到缓冲区。注意:所下达的读取或写入动作,都是针对缓冲区进行存取而不是磁盘,只有当使用fclose()函数关闭文件时,缓冲区中的数据才会写入磁盘。

2.1.1文件打开模式


打开文本文件:

"r":只能从文件中读数据,该文件必须先存在,否则打开失败
"w":只能向文件写数据,若指定的文件不存在则创建它,如果存在则先删除它再重建一个新文件
 
"a":向文件增加新数据(不删除原有数据),若文件不存在则打开失败,打开时位置指针移到文件末尾
"r+":可读/写数据,该文件必须先存在,否则打开失败
(r的性质为主)
"w+":可读/写数据,用该模式打开新建一个文件,先向该文件写数据,然后可读取该文件中的数据
"a+":可读/写数据,原来的文件不被删去,位置指针移到文件末尾

打开二进制文件的模式
访问文件(注意是访问)与打开文本文件的含义是一样的,不同的是模式名称里面多一个字母'b’,以表示以二进制形式打开文件。如:"rb"、"rb+"。
 

2.2 关闭文件

函数原型为:

int fclose(FILE *filename);

//关闭成功返回值0,否则返回非零值。(return 0正常退出,return 1错误退出)

注:在执行完文件的操作后,要进行“关闭文件”操作。虽然程序在结束前会自动关闭所有的打开文件,但文件打开过多会导致系统运行缓慢,这时就要自行手动关闭不再使用的文件,来提高系统整体的执行效率。

例1. 打开文件并进行判断和关闭文件

FILE  * fp;
fp 
 =  fopen( " c:\\temp\\test.txt "  " r " );

if (fp  ==  NULL)
    printf(
 " fail to open the file! \n " );
else
{
    printf(
 " The file is open! \n " );
    fclose(fp);
}


 

2.3 字符存取函数

函数原型为:

int fputc(int char, FILE *stream); 

//把参数 char 指定的字符(一个无符号字符)写入文件中,随后文件内部位置指针向后移动一个字节。

//形参变量char为要写入的字符,*stream为文件指针。fputc() 写入成功时返回写入的字符,失败时返回 EOF,返回值类型为 int 是为了容纳这个负数

//char -- 这是要被写入的字符。该字符以其对应的 int 值进行传递。

//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。

int fgetc(FILE* fp);

//字符读取函数fgetc()可从文件数据流中一次读取一个字符,然后读取光标移动到下一个字符可用循环逐步将文件的内容读出。

//在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节。在文件打开时,该指针总是指向文件的第一个字节。使用 fgetc() 函数后,该指针会向后移动一个字节,所以可以连续多次使用 fgetc() 读取多个字符。

注意:这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。而文件指针则是C语言中的指针。

如果字符读取成功,则返回所读取的字符,否则返回EOF(end of file)。返回值类型为 int是为了容纳这个负数EOF是表示数据结尾的常量,真值为-1。

fgetcgetc都可以指定流的输入地址,所以他们两个在一定程度上是可以等价的。

两个都是用来从stream中取得一个字符,并且把文件位置指针忘下一个字符移一位,区别在于调用getc函数时所用的参数stream不能是有副作用的表达式(稍后解释),而fgetc函数则可以,也就是说,getc可以被当作宏来调用,而fgetc只能作为函数来调用。 

              fgetc是一个函数 (不会有宏的副作用)
              getc是一个宏(速度更快,不需要堆栈)

另外,要判断文件是否读取完毕,可利用feof()进行检查。未完返回0,已完返回非零值。(正常可读return 0,不正常不可读return 1)。

EOF à feof

feof()函数原型为:

int feof(FILE *filename);

feof()经典错误:

#include<stdio.h>

int main(void){

    FILE *p;

    p = fopen("open.txt", "r");

    if (feof(p)){

        printf("文件为空。");

    }

    else{

        printf("文件不为空。");

    }

    return 0;

 }

feof()的原理:

feof()函数,并不是通过读取到文件的EOF来评判,这个文件是否为空。

对feof()来说,它的工作原理是,站在光标所在位置,向后看看还有没有字符。如果有,返回0;如果没有,返回非0。它并不会读取相关信息,只是查看光标后是否还有内容。

直接使用时的错误分析:

对于一个空文件来说,当程序打开它的时候,它的光标会停在文件的开头,但是由于文件里什么内容都没有存(但是EOF是存在的),即整个文件就存贮了一个EOF。当程序打开文件,并直接调用feof()时,这个函数就会站在光标的位置向后张望,结果就看见了EOF,然后就当然返回0了。

正确使用方法:

#include<stdio.h>

int main(void){

    FILE *p;

    p = fopen("open.txt", "r");

    getc(p);

    if (feof(p)){

        printf("文件为空。");

       }

    else{

        rewind(p);     //将光标跳回到文件开头

        int a;

        fscanf(p,"%d",&a);

        printf("%d", a);

    }

    return 0;

 }

分析:

对于文件来说,无论是空文件,还是存有信息的文件,当文件被打开,光标处于默认的开头时,光标后都有信息,这时候调用feof()来查看光标后是否还有内容,就没意义。

所以我们需要从相同中找不同,先使用getc(),从文件中读取一个字符,让光标向后移动一个字符。这时空文件的光标就已经移动到EOF的后面,这时使用feof()就会返回1了。这才是feof()的正确用法。

但是要注意,一定要将光标回到文件的开头,因为之前判断文件是否为空时,将光标向前移动了一位,必须要将光标恢复到开头,这样才能保证文件的正常读取。

例2. fgetc()函数的使用

版本1:利用feof()函数检查文件是否读取完毕

#include  < stdio.h >

main()
{
    FILE 
 * fp;
    fp 
 =  fopen( " c:\\temp\\test.txt "  " r " );
    
 if (fp  !=  NULL)
    {
        
 while ( ! feof(fp))
            printf(
 " %c " , fgetc(fp));
    }
    
 else
        printf( " fail to open! \n " );
    fclose(fp);

    
 return   0 ;
}


版本2:利用文件结束标志EOF(即-1)

#include  < stdio.h >

main()
{
    
 char  ch;
    FILE 
 * fp;
    fp 
 =  fopen( " c:\\temp\\test.txt "  " r " );
    
 if (fp  !=  NULL)
    {
        ch 
 =  fgetc(fp);
        
 while (ch  !=  EOF)
        {
            putchar(ch);
            ch 
 =  fgetc(fp);
        }

    }
    
 else
        printf( " fail to open! \n " );
    fclose(fp);

    
 return   0 ;
}


版本3 - 重构版本2

#include  < stdio.h >

main()
{
    
 char  ch;
    FILE 
 * fp;
    
 if ((fp  =  fopen( " test.txt "  " r " ))  !=  NULL)
        
 while ((ch  =  fgetc(fp))  !=  EOF)
            putchar(ch);   
// 先用变量去接收读取值再判断读取值是否为EOF,因为调用完fgetc()函数后位置光标会移动
     else
        printf( " fail to open! \n " );
    fclose(fp);

    
 return   0 ;
}


版本4 - 重构版本3 (不正确的重构)

#include  < stdio.h >

main()
{
    FILE 
 * fp;
    
 if ((fp  =  fopen( " test.txt "  " r " ))  !=  NULL)
        
 while (fgetc(fp)  !=  EOF)         // 这里调用了一次fget()函数,所以光标会后移一位,下面输出的就不是上面读取的数据
            putchar(fgetc(fp));
    
 else
        printf( " fail to open! \n " );
    fclose(fp);

    
 return   0 ;
}


 


若要将字符逐一写入文件,用fputc()函数。示例为:

例3. fputc()函数的使用

#include  < stdio.h >
#include  < conio.h >

main()
{
    
 char  filename[ 20 ], ch;
    FILE 
 * fp;
    printf(
 " Enter a filename:  " );
    scanf(
 " %s " , filename);             // filename,翻译:文件名
    printf( " Enter some characters to output to file:  " );
    
 if ((fp  =  fopen(filename,  " w " ))  ==  NULL)
        printf(
 " fail to open! \n " );
    
 else
    {
        
 while ((ch  =  getchar())  !=   ' \n ' )
            fputc(ch, fp);
    }
    fclose(fp);

    
 return   0 ;
}

2.4 字符串存取函数

函数原型为:

int fputs(const char *str, FILE *stream);
char *fgets(char *str, int num, FILE *stream);

fputs函数的作用是向指定文件输出一个字符串,如:fputs("HelloWord", fp);

把字符串"HelloWord"输出到fp指向的文件。fputs和fgets函数的第一个参数可以是字符串常量、字符数组名或字符型指针。后面的fwrite和fread也可以。若输出成功,则返回0,否则返回EOF。(正常返回0,错误EOF)

fgets函数的作用是从指定文件读入一个字符串,如:fgets(str, n, fp);

fgets()函数成功读取则返回字符串,否则返回NULL

fgets(...)读入文本行时的两种情况。

1如果n大于一行的字符串长度,那么当读到字符串末尾的换行符时,fgets(..)会返回。并且在s的最后插入字符串结束标志'\0'。 而s缓冲区剩余的位置不会再填充。

123abc

      fgets(s,10,fp);

此时,读入七个字符,123abc\n,并在最后加上'\0'所以,strlen(s)=7; 如果要去除末尾的\n,s[strlen(s)-1]='\0';便可。

2如果n小于等于一行的字符串的长度,那么读入n-1个字符,此时并没有读入\n因为并没有到行尾 ,同样在最后会插入'\0'fgets()会返回一个不完整的行,但fgets()如果没有遇到换行符,则下次调用fgets()函数时会继续在未读位置开始读取。

在用fgets(..)读入数据时,先定义一个字符数组或字符指针用于接收读入数据,如果定义了字符指针那么一定要用malloc函数初始化指针必须使用malloc函数来获得指向的内存空间,不可以赋值为NULL,可以这么理解:指针赋值NULL,只是让指针保存的地址值为0,此时指针是没有内存空间的,fgets函数不是返回一个字符串然后让指针接收,fgets是不会自己创建字符串空间的,需要外部创建,所以赋值为NULL无法读取到字符串,不然记住就行)

char s[100];  //可以。

char *s;         //不可以,因为只是声明了一个指针。但并没有为它分配内存缓冲区。

所以,如果要用指针,则  char *s=(char *)malloc(100*sizeof(char)); 为其分配内存空间

实例:

char s[1024];

              while((fgets(s,1024,fp))!=NULL)       // 可以传给数组,因为fgets()内部代码不是对数组的一次性赋值

              {

printf(s);

              }

 

2.5 文件位置指针(IO流指针)偏移函数

函数原型为:

int fseek(FILE *stream, long offset, int fromwhere);

功 能: 重定位文件位置指针

描 述: 函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。

返回值: 成功,返回0,否则返回其他值。

注意,偏移量是long类型。

注意:

第一个参数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字节处;

ffseek(fp,-100L,2);把fp指针退回到离文件结尾100字节处。

fseek()函数一般用于二进制文件,因为文本文件要发生字符转换,计算位置时往往会发生混乱。

2.6 获取文件的当前指针位置相对于文件首地址的偏移字节数

函数原型为:

long int ftell(FILE *filename);      “tell 告诉我位置”

发生错误返回-1.

多用于查询文件的大小。

2.7 指针重返函数

函数原型为:

rewind(FILE *filename);      (return 到 win“最高处”)

回到文件首后写入内容会把原位置的内容所覆盖。

2.8 格式化存取函数

函数原型为:

int fprintf(FILE *stream, const char *format, ...) 
// 传送格式化输出到一个文件中
// stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
/* format -- 这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier    */

例如:fprintf(fp,"%d,%x,%o",10,10,10); 

// 分别以十进制,十六进制,八进制,输出10到文件中,即10,a,12。

// fprintf()函数返回值是写入文件的字符个数。
 


int fscanf(FILE *stream, const char *format, ...)
// stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
// format -- 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符。
// format 说明符形式为 [=%[*][width][modifiers]type=]
例如:fscanf(fp,"%d",&ch);      // ch为一个用于接收的int类型数组

//fscanf返回的是实际读取的数据个数,出错或者到结尾返回EOF

它们与printf和scanf函数相仿,都是格式化读写函数。不同的是:fprintf和fscanf函数的读写对象不是终端(标准输入输出),而是磁盘文件。printf函数是将内容输出到终端(屏幕),因此,fprintf就是将内容输出到磁盘文件了。

注意:fprintf()是将agars(参数表)内各项的值,按format(格式控制字符串)所表示的格式,将数据格式为字符串的形式写入到文件指针fp指向的文件中。即写入的是一串字符,格式控制符只不过是控制其以文本形式展示的样子(同printf的格式控制符只是控制展现形式)

如fprintf(fp,"%c%d%d",65,2,3); 实际写入的是A23字符串

而fscanf() 格式控制符 要与 所提取的字符所呈现的类型 一致(同scanf的格式控制符要与输入的类型一致),读取类型 与接收读取内容的变量 类型一致(别混淆了,虽然在文本中是一串字符,但不需要用%s来读取,不是与读取的类型一致,是与接收的变量类型一致)(可以理解成一个括号内的相互限制),且scanf的每一个格式控制符对应每一串fprintf()函数写入的字符串。

所以:

 

(读取时123是一个整体(同一字符串),但所呈现的是int类型,所以用%d读取,且用int类型变量去接收,接收结果为int类型的123)

3. 二进制文件操作

3.1 数据块存取函数(不用关心返回值)

函数原型:

size_t fwrite(const void* buffer,size_t size, size_t count, FILE* stream);

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

因为fwritefread都是对二进制文件操作,所以可以直接读取数组内容或直接写入数组内容。前面的fputsfgets也可以。

所以当要求一次存取一组数据(如,一个数组、一个结构体变量的值),freadfwrite函数可以解决该类问题。它们的调用形式一般为:

fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp);

buffer:对于fread来说,指的是从文件读入数据的后其存放地址;对于fwrite来说,是要输出到文件的内容的地址。
size:读写数据时,每笔数据的大小
count:元素的个数,每个元素的大小为 size 字节。
fp:文件指针

***** fwrite()函数返回值是写入文件的元素个数,如果与所注明写入个数一致则正常。*****

* fread()函数如果调用成功返回实际读取到的元素个数,如果不成功或读到文件末尾返回 0*

实例5. fread和fwrite函数的使用

#include  < stdio.h >
#define  SIZE 3

typedef 
 enum  { MM, GG } Gender;

typedef 
 struct
{
    
 char  name[ 10 ];
    
 int   age;
    Gender gender;
} Person;


void  write2file(Person emp[SIZE])
{
    FILE 
 * fp;
    
 if ((fp  =  fopen( " emp.txt "  " wb " ))  ==  NULL)
    {
        printf(
 " cannot open file! \n " );
        
 return ;
    }

    
 for ( int  i = 0 ; i < SIZE; i ++ )
        
 if (fwrite( & emp[i],  sizeof (Person),  1 , fp)  !=   1 )    // 正常则返回写入元素个数
            printf( " file write error! \n " );
    fclose(fp);
}


void  read_from_file(FILE  * fp)
{
    Person emp_out[SIZE];

    
 if ((fp  =  fopen( " emp.txt "  " rb " ))  ==  NULL)
    {
        printf(
 " cannot open file! \n " );
        
 return ;
    }

    printf(
 " \n%d employee's information read: \n " , SIZE);

    
 for ( int  i = 0 ; i < SIZE; i ++ )
    {
        
 if (fread( & emp_out[i],  sizeof (Person),  1 , fp)  !=   1 )
            
 if (feof(fp))
            {
                fclose(fp);
                
 return ;
            }
            printf(
 " %-5s %4d %5d \n " , emp_out[i].name, emp_out[i].age, emp_out[i].gender);
    }
    fclose(fp);
}


void  main()
{
    FILE 
 * fp  =  NULL;
    Person employee[SIZE];

    printf(
 " Enter %d employee's information: \n " , SIZE);
    
 for ( int  i = 0 ; i < SIZE; i ++ )
        scanf(
 " %s %d %d " , employee[i].name,  & employee[i].age,  & employee[i].gender);

    write2file(employee);

    read_from_file(fp);
}

附:fprintf与fwrite的区别:

1. fprintf(fp, "%d", buffer); 是将格式化的数据写入文件

   fwrite(&buffer, sizeof(int),1, fp);是以二进位方式写入文件

2. fprintf写文本文件,用记事本可以打开查看

   fwrite写二进制文件

3. fprintf写入时,对于整数来说,一位占一个字节,比如1,占1个字节;10,占2个字节;

所以文件的大小会随数据的大小而改变,对大数据空间占用很大。

fwrite是按二进制写入,写入数据所占空间是根据数据类型来确定,所以二进制写入比格式化写入更省空间

参考文献:

C语言详解 FILE文件操作_木牛的博客-CSDN博客_c语言文件操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值