• C语言文件篇——文件操作


    目录

    为什么使用文件,文件是什么

     文件的打开和关闭

     文件的打开和关闭

    fputc和fgetc函数 

     fputs和fgets函数

    fscanf和fprintf函数

    理解文件的读和写

    fread和fwrite函数

    sprintf和sscanf函数

     fseek函数

     ftell函数

    rewind函数 

    文本文件和二进制文件 

     用VS查看二进制文件

     文件读取结束的判定

     文件缓冲区

    习题1 

    习题2 

    习题3 

    习题4 

    习题5 

    习题6 

    习题7 

    习题8 

    习题9 

    习题10 

    习题11 

    习题12 


    为什么使用文件,文件是什么

     使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化

    磁盘上的文件是文件。但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

    程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

    数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

    文件名:一个文件要有一个唯一的文件标识,以便用户识别和引用。
    文件名包含3部分:文件路径+文件名主干+文件后缀,例如: c:\code\test.txt,为了方便起见,文件标识常被称为文件名。
     

     文件的打开和关闭

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

    例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:

    1. struct _iobuf {
    2. char *_ptr;
    3. int _cnt;
    4. char *_base;
    5. int _flag;
    6. int _file;
    7. int _charbuf;
    8. int _bufsiz;
    9. char *_tmpfname;
    10. };
    11. typedef struct _iobuf FILE;

    不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
    每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
    一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
    下面我们可以创建一个FILE*的指针变量:
     

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

     文件的打开和关闭

     //打开文件
    FILE * fopen ( const char * filename, const char * mode );

    打开失败会返回空指针
    //关闭文件
    int fclose ( FILE * stream );

     如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:

    "rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

     通过pf找到文件,打开文件有可能失败,失败后会返回空指针

    下面打开其它地方的文件,用绝对路径

    fputc和fgetc函数 

    int fputc(int char, FILE *stream)
    • char -- 这是要被写入的字符。该字符以其对应的 int 值进行传递。
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。
    • 如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符

     

     

    int fgetc(FILE *stream)
    
    
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。
    • 该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。

    使用fgetc和fputc时,注意要切换打开文件的方式,fgetc和fputc一次只能读写一个字符 

     fputs和fgets函数

    int fputs(const char *str, FILE *stream)
    
    • str -- 这是一个数组,包含了要写入的以空字符终止的字符序列。
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。
    • 该函数返回一个非负值,如果发生错误则返回 EOF。

      写文件时候,会将文件之前的内容覆盖掉 

    char *fgets(char *str, int n, FILE *stream)
    • str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
    • n -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
    • 如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。

      如果发生错误,返回一个空指针。

       这里只读了四个字符,是因为第五个字符处要放\0,

    fscanf和fprintf函数

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

    int fscanf(FILE *stream, const char *format, ...)读
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
    • format -- 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符
      format 说明符形式为 [=%[*][width][modifiers]type=]
    • 如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。

    理解文件的读和写

     屏幕,网络,U盘,硬盘等称为外部设备,外部设备的读和写有所差异,先把数据写到流,再从流到内存,

     

     任何一个C程序,只要运行起来就会默认打开三个流,stdin标准输入流(键盘),stdout标准输出流(屏幕),stderr标准错误流(屏幕),这三个类型都是FILE*

    fread和fwrite函数

    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
    • ptr -- 这是指向要被写入的元素数组的指针。
    • size -- 这是要被写入的每个元素的大小,以字节为单位。
    • nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
    • 如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。

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

    • ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
    • size -- 这是要读取的每个元素的大小,以字节为单位。
    • nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
    • 成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

    sprintf和sscanf函数

    int sprintf(char *str, const char *format, ...)

     把一个格式化的字符,转换成字符串

    • str -- 这是指向一个字符数组的指针,该数组存储了 C 字符串。
    • format -- 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier
    • 如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。

     

    int sscanf(const char *str, const char *format, ...)

    从一个字符串中,返回到正确的格式

    • str -- 这是 C 字符串,是函数检索数据的源。
    • format -- 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符
      format 说明符形式为 [=%[*][width][modifiers]type=]
    • 如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。

     fseek函数

    int fseek(FILE *stream, long int offset, int whence)
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
    • offset -- 这是相对 whence 的偏移量,以字节为单位。
    • whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:

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

     

     ftell函数

    long int ftell(FILE *stream)
    • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
    • 该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。

    计算的是相对于起始位置的偏移量 

    rewind函数 

    void rewind ( FILE * stream );
    让文件指针的位置回到文件的起始位置

    文本文件和二进制文件 

     根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
    数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
    如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件

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

     以ASCII码值存进去的被称为文本文件

     以二进制写的文件打开之后看不懂。

     用VS查看二进制文件

     第一步先添加现有项,然后选择我们要添加的二进制文件

     然后右击该文件,选择打开方式

     选择二进制编辑器

     

     打开之后我们可以看到10000转换为16进制之后并且按照小端存储的结果

     文件读取结束的判定

     被错误使用的feof

    在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束,而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束
    1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
    例如:
                             fgetc 判断是否为 EOF .
                             fgets 判断返回值是否为 NULL .
    2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
    例如:
                              fread判断返回值是否小于实际要读的个数(该函数返回值为被成功读取的个数)。

    1. #include
    2. #include
    3. int main(void)
    4. {
    5. int c; // 注意:int,非char,要求处理EOF
    6. FILE* fp = fopen("test.txt", "r");
    7. if (!fp) {
    8. perror("File opening failed");
    9. return EXIT_FAILURE;
    10. }
    11. //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    12. while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
    13. {
    14. putchar(c);
    15. }
    16. //判断是什么原因结束的
    17. if (ferror(fp)) //判断是否因为读取错误而结束读取文件
    18. puts("I/O error when reading");
    19. else if (feof(fp))//判断是否因为读取结束而结束读取文件
    20. puts("End of file reached successfully");
    21. fclose(fp);
    22. return 0;
    23. }

     文件缓冲区

     ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

     等缓冲区放满了,才往下一级输出

     

     fclose也会刷新缓冲区

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

    习题1 

    关于fopen函数说法不正确的是:( )

    A.fopen打开文件的方式是"r",如果文件不存在,则打开文件失败

    B.fopen打开文件的方式是"w",如果文件不存在,则创建该文件,打开成功

    C.fopen函数的返回值无需判断

    D.fopen打开的文件需要fclose来关闭

    C选项中fopen的返回值可以检验文件是否打开成功,打开方式为"r"时尤其重要。ABD为文件操作的基本概念和原则。

     

    习题2 

    C语言以二进制方式打开一个文件的方法是?( )

    A.FILE *f = fwrite( "test.bin", "b" );

    B.FILE *f = fopenb( "test.bin", "w" );

    C.FILE *f = fopen( "test.bin", "wb" );

    D.FILE *f = fwriteb( "test.bin" );

    首先,因为要打开文件,AD直接拖出去,由于不存在一个“fopenb”函数,所以直接选C。二进制描述中的b要放在权限后,也就是“wb”才是合法的。

     

    习题3 

    下列关于文件名及路径的说法中错误的是:( )

    A.文件名中有一些禁止使用的字符

    B.文件名中一定包含后缀名

    C.文件的后缀名决定了一个文件的默认打开方式

    D.文件路径指的是从盘符到该文件所经历的路径中各符号名的集合

    B选项中,文件名可以不包含后缀名。A的话,文件中不能包含这些字符:\/:*?"<>|,C表述了后缀名的作用,D是路径的基本概念。故选B。

     

    习题4 

    C语言中关于文件读写函数说法不正确的是:( )

    A.fgetc是适用于所有输入流字符输入函数

    B.getchar也是适用于所有流的字符输入函数

    C.fputs是适用于所有输出流的文本行输出函数

    D.fread是适用于文件输入流的二进制输入函数

    B选项中,getchar只针对标准输入流stdin。即使对stdin重定向,getchar针对的也只是stdin。f系列的输入输出函数都是作用于所有流的的,所以AC没问题,D的表述也没问题,fread做的就是二进制的活。

     

    习题5 

    1. int main()
    2. {
    3. long num=0;
    4. FILE *fp = NULL;
    5. if((fp=fopen("fname.dat", "r"))==NULL)
    6. {
    7. printf("Can’t open the file!");
    8. exit(0);
    9. }
    10. while(fgetc(fp) != EOF)
    11. {
    12. num++;
    13. }
    14. printf("num=%d\n",num);
    15. fclose(fp);
    16. return 0;
    17. }

     上面程序有什么功能?

    A.拷贝文件

    B.统计文件的字符数

    C.统计文件的单词数

    D.统计文件的行数

    程序只通过只读方式打开了一个文件,所以A排除,文中使用的fgetc,且没有' '和'\n'相关的统计,所以排除CD,直接选B。

    习题6 

    下面说法不正确的是:( )

    A.scanf和printf是针对标准输入、输出流的格式化输入、输出语句

    B.fscanf和fprintf是针对所有输入、输出流的格式化输入、输出语句

    C.sscanf是从字符串中读取格式化的数据

    D.sprintf是把格式化的数据写到输出流中

    选项中,sprintf是把格式化的数据写到字符串中,与输出流无关。其他三句都准确描述了函数功能。

     

    习题7 

    下面哪个不是预处理指令:( )

    A.#define

    B.#if

    C.#undef

    D.#end

    #define执行查找替换,#if可以区分是否编译,#undef可以反定义,也就是取消#define宏定义的东西,#end并没有这玩意,只有#endif,故选D。

     

    习题8 

    下面哪个不是预定义符号?( )

    A.__FILE__

    B.__TIME__

    C.__DATE__

    D.__MAIN__

    前三个是常用宏,分别是:打印所在文件、打印编译时间、打印编译日期。除此之外,还有__LINE__(行号)、__FUNCTION__(函数名)等宏,而__MAIN__并不存在,故选D。

     

    习题9 

    ( ) 的作用是将源程序文件进行处理,生成一个中间文件,编译系统将对此中间文件进行编译并生成目标代码。

    A.编译预处理

    B.汇编

    C.生成安装文件

    D.编译

    题干中提到了“编译”,说明是编译的上一步,那自然是预处理。故选A

     

    习题10 

    由多个源文件组成的C程序,经过编辑、预处理、编译、链接等阶段会生成最终的可执行程序。下面哪个阶段可以发现被调用的函数未定义?( )

    A.预处理

    B.编译

    C.链接

    D.执行

    预处理只会处理#开头的语句,编译阶段只校验语法,链接时才会去找实体,所以是链接时出错的,故选C。这里附上每个步骤的具体操作方式:

    预处理:相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有头文件(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。

    编译:将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,不负责寻找实体。

    链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。

     

    习题11 

    test.c文件中包括如下语句:

    1. #define INT_PTR int*
    2. typedef int*int_ptr;
    3. INT_PTR a,b;
    4. int_ptr c,d;

     

    文件中定义的四个变量,哪个变量不是指针类型?( )

    A.a

    B.b

    C.c

    D.d

    预处理的#define是查找替换,所以替换过后的语句是“int*a,b;”,其中b只是一个int变量,如果要让b也是指针,必须写成“int *a, *b;”。而typedef没有这个问题,c、d都是指针。故选B。

    习题12 

    关于feof函数描述不正确的是:( )

    A.feof函数是用来判断文件是否读取结束的

    B.feof函数是在文件读取结束的时候,检测是否是因为遇到了文件结束标志EOF,而读取结束

    C.读取文本判断是否结束时,fgetc看返回值是否为EOF, fgets看返回值是否为NULL

    D.二进制文件判断读取结束,看实际读取个数是否小于要求读取个数

    说明:feof函数是在文件读取结束后,判断文件读取结束的原因的,是读取失败结束,还是遇到文件尾结束。所以A是错误的,其他选项均正确。

     

  • 相关阅读:
    TensorFlow和Pytorch两种机器学习框架的比较及优缺点
    常见的Java上机面试题
    MySQL进阶04_索引_索引使用_索引设计原则
    如果回到5年前,20几岁的时候,想对过去的自己说点什么
    Python自学笔记8:实操案例五(循环输出26个字母对应的ASCII码值,模拟用户登录,猜数游戏,计算100-999之间的水仙花数)
    ASP.NET Core - 缓存之内存缓存(下)
    面试:linux相关
    Linux基础-常见问题 添加桌面快捷方式链接shell脚本
    使用java连接数据库报错:Exception in thread “main“ com.mysql.cj.jdbc.exceptions.
    java/golang/csharp/c++/python生成pb文件的方法
  • 原文地址:https://blog.csdn.net/weixin_49449676/article/details/125820776