喜迎
春节

文件


0.什么是文件

1.文本文件和二进制文件

c语言中主要有两种文件,文本文件和二进制文件。

2.打开和关闭文件

2.1打开文件

2.1.1fopen()函数

FILE *fp; // 定义文件指针
fp = fopen("filename", "mode"); // 打开文件

其中,第一个参数是文件名(包括路径),第二个参数是文件打开模式,可以是以下几种之一:

  • "r"

    • 只读方式打开文件,文件指针指向文件开头,从文件头开始读取。
    • 该文件必须存在
  • "w"

    • 写方式打开文件,如果文件不存在则新建一个文件
    • 如果文件已经存在则清空文件内容,并指向文件开头。
  • "a"

    • 追加方式打开文件,如果文件不存在则新建一个文件,指向文件结尾
    • 如果文件已经存在则指向文件结尾。
  • "r+"

    • 读写方式打开文件,文件指针指向文件开头。
    • 该文件必须存在
    • 该模式不会清除原有内容,只覆盖重新写入的内容,例如原来100个字节,写入10个,则只覆盖前10个字节。
  • "w+"

    • 读写方式打开文件,如果文件不存在则新建一个文件
    • 如果文件已经存在则清空文件内容,并指向文件开头。
  • "a+"

    • 读和追加方式打开文件,如果文件不存在则新建一个文件,指向文件结尾

    • 如果文件已经存在则指向文件结尾。

  • "b":

    • 与上面6种模式可结合(”rb”,”wb”,”ab”,”r+b”,”w+b”,”a+b”)
    • 描述的含义一样,只不过操作的对象是二进制文件

2.2关闭文件

fclose(fp); // 关闭文件

3.读写单个字符

3.1读取单个字符(fgetc和getc)

fgetcgetc 函数都是用于从文件中读取一个字符的函数,它们都需要包含头文件 stdio.h。它们的具体区别如下:

  1. fgetc(FILE *stream) 函数从指定的文件流(即由 fopen 函数返回的指针)中读取一个字符,并使文件指针后移一个位置。
int ch;
ch = fgetc(fp); // 从文件指针 fp 指向的文件中读取一个字符
  1. getc(FILE *stream) 函数与 fgetc 函数相似,也是从指定的文件流(即由 fopen 函数返回的指针)中读取一个字符。不同的是,getc 函数有时被定义为宏,这意味着它可以直接被编译器优化,而不需要调用函数。

使用这两个函数时需要注意以下几点:

  1. 如果达到文件结尾或读取失败,fgetcgetc 函数会返回 EOF(End Of File),其值为 -1。
  2. 需要检查返回值是否等于 EOF,以判断文件是否已经读取结束或读取出错,避免出现潜在的错误。
  3. 如果使用 fgetc 函数从标准输入 stdin 中读取一个字符,可以用 getchar() 函数代替,其作用与 fgetc(stdin) 相同。

总之,fgetcgetc 函数都是从文件中读取一个字符的函数,其大体相同,唯一的区别在于 getc 有可能被编译器定义为宏,两者的具体使用应该根据具体情况而定。

3.2写入单个字符(fputc和putc)

fputcputc 函数都是用于向文件中写入一个字符的函数,它们都需要包含头文件 stdio.h。它们的具体区别如下

  1. fputc(int c, FILE *stream) 函数将一个字符写入指定的文件流(即由 fopen 函数返回的指针),并使文件指针后移一个位置。
 int ch;
fputc(ch, fp); // 向文件指针 fp 指向的文件中写入一个字符 ch
  1. putc(int c, FILE *stream) 函数与 fputc 函数相似,也是向指定的文件流(即由 fopen 函数返回的指针)中写入一个字符。不同的是,putc 函数有时被定义为宏,这意味着它可以直接被编译器优化,而不需要调用函数。
int ch;
putc(ch, fp); // 向文件指针 fp 指向的文件中写入一个字符 ch

使用这两个函数时需要注意以下几点:

  1. 如果成功写入字符到文件中,fputcputc 函数会返回写入的字符;如果发生写入失败,则返回 EOF(End Of File),其值为 -1。
  2. 需要检查返回值是否等于 EOF,以判断写入是否成功,避免出现潜在的错误。
  3. 如果使用 fputc 函数向标准输出 stdout 中写入一个字符,可以用 putchar() 函数代替,其作用与 fputc(c, stdout) 相同。

总之,fputcputc 函数都是向文件中写入一个字符的函数,其大体相同,唯一的区别在于 putc 有可能被编译器定义为宏,两者的具体使用应该根据具体情况而定。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    FILE *fp1 ;
    FILE *fp2 ;
    int ch ;

    if((fp1 = fopen("hello.txt","r")) == NULL)
    {
        printf("文件打开失败") ;
        exit(EXIT_FAILURE);
    }

    if((fp2 = fopen("fishc.txt","w")) == NULL)
    {
        printf("文件打开失败") ;
        exit(EXIT_FAILURE);
    }

    while((ch = fgetc(fp1)) != EOF)
    {
        fputc(ch,fp2) ; //将读取到的文件,写入fp2对应的文件中 
    }

    fclose(fp1) ;
    fclose(fp2) ;

}

4.读写字符串

4.1读取字符串(fgets)

fgets 函数用于从一个文件流中读取一行内容,其函数原型如下:

char *fgets(char *s, int size, FILE *stream);

它接受三个参数:

  • s:指向一个字符数组,用于存储读取到的数据。
  • size:表示读取字符数的最大值。当读取到 \n 或者读取字符数达到了 size-1 ,就会停止读取。
  • stream:指向一个文件流,用于指定从哪个文件读取。

fgets 函数会依次读取每个字符,直到遇到换行符 \n 或者读取字符数达到上限(由 size 指定)也可以说是,然后将读取到的字符存储到 s 指向的缓冲区中,并在字符串结尾添加一个空字符 \0,表示字符串的结束。如果遇到文件结尾 EOF,或者读取过程中出现错误,fgets 将停止读取并返回 NULL。

==补充==:如果在读取字符的过程中遇到EOF,则eof指示器被设置;如果还么读入任何字符就遇到这种EOF,则s参数指向的位置保持原来的内容,函数返回NULL,例如字符串的末尾有换行符,此时就来到读取下一行了,但是下一行什么也没有,是空的,此时就会指向上一行的内容。只有打印的时候会,查看文件内容还是实质的内容。

4.2写入字符串(fputs)

fputs 函数用于将一个字符串写入到指定的文件流中,其函数原型如下:

int fputs(const char *s, FILE *stream);

它接受两个参数:

  • s:指向要写入的字符串。
  • stream:指向要写入的文件流。

fputs 函数会将指定的字符串 s 写入到 stream 指定的文件流中,并返回一个非负数值表示写入的字符数,非0值(不包括结尾的空字符 \0)。如果写入过程出现错误,则返回一个负数表示错误码,返回EOF。

4.3feof()

用于检查文件指针所指向的文件是否已经达到文件末尾。该函数通过测试文件流上的文件结束标识符来确定文件是否结束

如果结束则返回非零值,否则返回零。

feof() 的函数原型如下:

int feof(FILE *stream);

其中 stream 是一个文件指针,指向需要被检查的文件。

当文件读取完毕后,调用 feof() 函数会返回一个非零值;而在文件读取过程中调用 feof() 函数将会返回零。

通常情况下,我们在使用 fgets() 或者 fscanf() 等函数在一个循环中读取文件内容时,可以利用 feof() 函数来判断循环何时应该停止

#include<stdio.h>
#include<stdlib.h>

#define MAX 1024

int main()
{
    FILE *fp ;
    char buffer[MAX] ;

    if((fp = fopen("lines.txt","w")) == NULL)
    {
        printf("文件打开失败\n") ;
        exit(EXIT_FAILURE) ;
    }

    // 向这个文件中写入数据 
    fputs("Lines one : hello world\n",fp);   
    fputs("lines two : hello ljh\n",fp) ;
    fputs("lines three : i love fishc.com\n ",fp);

    fclose(fp) ;

    if((fp = fopen("lines.txt","r")) == NULL)
    {
        printf("文件打开失败\n") ;
        exit(EXIT_FAILURE) ;
    }

    // 读取数据 
    while(!feof(fp))  // 当文件到达末尾的时候,返回非零,所以我们取反,就是没有到达末尾的时候就while循环,到达末尾了就是非零的取反为0,此时就跳出循环了 
    {
        fgets(buffer,MAX,fp) ;
        printf("%s",buffer) ;    
    }
}

5.格式化读写文件

5.1fscanf读

fscanf() 函数的原型为:

int fscanf(FILE *stream, const char* format, ...);

它的第一个参数是要读取的文件流,第二个参数是格式化字符串,后面跟着可选的参数列表,对应格式化字符串中的占位符。fscanf() 将按照格式化字符串中指定的格式从文件流中读取数据,并将其存储到相应的参数中。

例如,下面的代码从文件中读取一个整数和一个浮点数:

int i;
float f;
FILE *fp = fopen("data.txt", "r");
fscanf(fp, "%d%f", &i, &f);
fclose(fp);

5.2fprintf写

fprintf() 函数的原型为:

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

它的第一个参数是要写入的文件流,第二个参数是格式化字符串,后面跟着可选的参数列表,对应格式化字符串中的占位符。fprintf() 将按照格式化字符串中指定的格式将数据写入到文件流中。

例如,下面的代码向文件中写入一个字符串和一个整数:

char str[] = "Hello, World!";
int i = 123;
FILE* fp = fopen("data.txt", "w");
fprintf(fp, "%s %d\n", str, i);
fclose(fp);

案例

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int main()
{
    FILE *fp ;
    struct tm *p ;
    time_t t ;

    time(&t) ;
    p = localtime(&t) ;

    if((fp = fopen("date.txt","w")) == NULL)
    {
        printf("打开文件失败") ;
        exit(EXIT_FAILURE) ;
    }

    fprintf(fp,"%d-%d-%d",1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday) ;//因为年份是从1900开始的,所以要加上,月份是从0开始的,要加1

    fclose(fp) ;

    int year , month ,day  ;

    if((fp = fopen("date.txt","r")) == NULL)
    {
        printf("打开文件失败l\n") ;
        exit(EXIT_FAILURE) ;
    }

    fscanf(fp,"%d-%d-%d",&year,&month,&day) ;
    printf( "%d-%d-%d\n",year,month,day) ;

    fclose(fp) ;
}    

6.二进制读写文件

6.1fread读

6.2fwrite写

函数原型:

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

对参数的说明:

  • ptr 为内存区块的指针,它可以是数组、变量、结构体等。
    • fread() 中的 ptr 用来存放读取到的数据
    • fwrite() 中的 ptr 用来存放要写入的数据。
  • size:表示每个数据块的字节数。
  • count:表示要读写的数据块的块数。
  • fp:表示文件指针。
  • 理论上,每次读写 size*count 个字节的数据

返回值:返回成功读写的块数,也即 count。如果返回值小于 count:

  • 对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。
  • 对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。

案例:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct Date
{
    int year ;
    int month ;
    int day ;
} ;

struct Book
{
    char name[40] ;
    char author[40] ;
    char publisher[40] ;
    struct Date date ;
} ;

int main()
{
    FILE *fp ;
    struct Book *book_for_write , *book_for_read ;

    book_for_write = (struct Book *)malloc(sizeof(struct Book)) ;
    book_for_read     = (struct Book *)malloc(sizeof(struct Book)) ;
    // ..申请内存空间判断

    //写一些要存放进fp文件的数据
    strcpy(book_for_write->name,"学习笔记") ;
    strcpy(book_for_write->author,"ljh") ;
    strcpy(book_for_write->publisher,"未来出版社") ;
    book_for_write->date.year = 2023 ;
    book_for_write->date.month = 5 ;
    book_for_write->date.day = 2 ;


    if((fp = fopen("file.txt","w")) == NULL)
    {
        printf("打开文件失败") ;
        exit(EXIT_FAILURE) ;
    }
//将上面的文件写进fp中
    fwrite(book_for_write,sizeof(struct Book),1,fp) ;
    fclose(fp) ;

    if((fp = fopen("file.txt","r")) == NULL)
    {
        printf("打开文件失败") ;
        exit(EXIT_FAILURE) ;
    }
//    从文件fp中读取数据,存放到book_for_read中
    fread(book_for_read,sizeof(struct Book),1,fp) ;
    printf("书名:%s\n",book_for_read->name) ; 
    printf("作者:%s\n",book_for_read->author) ; 
    printf("出版社:%s\n",book_for_read->publisher) ; 
    // 打印结果
    printf("出版日期:%d-%d-%d\n",book_for_read->date.year,book_for_read->date.month,book_for_read->date.day) ; 
    fclose(fp) ;
}

打开该文件来看就是乱码,因为是二进制文件

7.随机读写文件

7.1ftell

用于获取当前文件流位置的函数

函数原型:

long int ftell(FILE *stream);

需要注意的是,ftell() 函数返回的位置信息仅在二进制模式下是有意义的。在文本模式下,由于可能存在换行符等特殊字符的转换,因此返回的位置信息可能与实际位置不同。如果需要获取文本文件的真实字符数,请使用 fgetpos() 函数和 fsetpos() 函数配合使用。

案例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    FILE *fp ;

    if((fp = fopen("hello.txt","w")) == NULL)
    {
        printf("文件打开失败\n") ;
        exit(EXIT_FAILURE) ; 
     } 

     printf("%ld\n",ftell(fp)) ;  // 刚开始该文件是空的,所以打印结果为0 
     fputc('F',fp) ;
     printf("%ld\n",ftell(fp)) ; // 写入了一个字符'f',之后,打印结果为1 
     fputs("fishc",fp) ;
     printf("%ld\n",ftell(fp)) ;  // 写入了之后,打印的结果是累加的和6, 

     fclose(fp) ; 
}

7.2rewind

rewind() 函数是 C 标准库中用于将文件流位置指针重置到文件开头的函数。它的原型为:

void rewind(FILE *stream);

该函数将文件流 stream 的位置指针重置到文件开头。它不返回任何值。

案例:用上面的代码,rewind之后,发现,再写入数据,之前的数据就全部没有了。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    FILE *fp ;

    if((fp = fopen("hello.txt","w")) == NULL)
    {
        printf("文件打开失败\n") ;
        exit(EXIT_FAILURE) ; 
     } 

     printf("%ld\n",ftell(fp)) ;  // 刚开始该文件是空的,所以打印结果为0 
     fputc('F',fp) ;
     printf("%ld\n",ftell(fp)) ; // 写入了一个字符'f',之后,打印结果为1 
     fputs("fishc",fp) ;
     printf("%ld\n",ftell(fp)) ;  // 写入了之后,打印的结果是累加的和6, 

     rewind(fp) ;  //rewind
     fputs("hello",fp) ; // 插入数据,会直接覆盖原始数据 

     fclose(fp) ; 
}

7.3fseek随机读取任意位置的数据

fseek() 函数是 C 标准库中用于移动文件流位置指针的函数。它的原型为:

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

该函数将文件流 stream 的位置指针移动 offset 个字节,移动方向由参数 origin 指定。参数 origin 可以取三个值:SEEK_SETSEEK_CURSEEK_END,分别表示从文件开头、从当前位置和从文件末尾开始移动。

如果移动成功,则返回 0;否则返回非零值。

需要注意的是,如果将文件流位置指针移动到不可读或不可写的位置,可能会导致文件读写失败。因此,在使用 fseek() 函数时,需要确保移动的位置正确,并检查函数返回值以确保移动是否成功。

案例:

#include <stdio.h>
#include<stdlib.h>

#define N 4

struct Stu
{
    char name[24] ;
    int num ;
    float score ;
} stu[N] , sb ;

int main()
{
    FILE *fp ;
    int i  ;

    if((fp = fopen("score.txt","wb")) == NULL)
    {
        printf("打卡文件失败") ;
        exit(EXIT_FAILURE) ;
    }
    printf("请开始录入成绩 格式:姓名,学号,成绩\n") ;
    for(i = 0 ; i < N ; i++)
    {
        scanf("%s %d %f",stu[i].name,&stu[i].num,&stu[i].score) ;
    }

    fwrite(stu,sizeof(struct Stu),N,fp) ;
    fclose(fp) ;

    if((fp = fopen("score.txt","rb")) == NULL)
    {
        printf("打卡文件失败") ;
        exit(EXIT_FAILURE) ;
    }

    fseek(fp,sizeof(struct Stu),SEEK_SET) ;
    fread(&sb,sizeof(struct Stu),1,fp) ; // 将读取到的数据,存放到结构体的指针,这里可以看fread的函数原型 
    printf("%s(%d)的成绩是:%.2f\n",sb.name,sb.num,sb.score ) ;

    fclose(fp) ;
}

8.标准流和错误处理

这里就类似于Java种的抛出异常的那卦的知识,之前c语言中有错误的时候,都是用printf打印出错误,这时候又跑到了标准输出流去了,并不是错误输出流,本内容学习了之后就可以使用错误输出流来代替之前的printf打印错误的方式了。

案例:

这里偷个小懒,个人觉得这里的知识不是很重要,听一下了解一下就可以了。

9.IO缓冲区


文章作者: ljhaa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ljhaa !
评 论
 上一篇
编译流程
编译流程
C 语言的编译过程包括了预处理、编译、汇编和链接四个步骤,下面对这四个步骤进行详细解释。 1.预处理在编译之前,C 语言编译器需要先对源代码进行预处理。预处理器读取源代码并进行文本替换、条件编译等操作,生成一个经过预处理的源文件,例如: #
2023-05-02
下一篇 
结构体
结构体
1.结构体(个人理解,结构体跟java中的类差不多) 可以理解为结构体是类的原形,毕竟c比java早诞生。 类似于类,但是不是类,因为c是面向过程编程,java是面向对象编程,两者之间是有差别的 可以在main函数中也可以在 main函数外
2023-05-02
  目录