当前位置: 首页 > news >正文

【《C Primer Plus》读书笔记】第13章:文件输入/输出

【《C Primer Plus》读书笔记】第13章:文件输入/输出

  • 13.1 与文件进行通信
    • 13.1.1 文件是什么
    • 13.1.2 文本模式和二进制模式
    • 13.1.3 I/O的级别
    • 13.1.4 标准文件
  • 13.2 标准I/O
  • 13.3 一个简单的文件压缩程序
  • 13.4 文件I/O:fprintf()、fscanf()、fgets()和fputs()
  • 13.5 随机访问:fseek()和ftell()
    • C 库函数 - fseek()
    • C 库函数 - ftell()
    • reverse.c
    • 13.5.2 二进制模式和文本模式
    • 13.5.3 可移植性
    • 13.5.4 fgetpos()和fsetpos()函数
      • C 库函数 - fgetpos()
  • 13.6 标准I/O的机理
  • 13.7 其他标准I/O函数
    • C 库函数 - ungetc()
    • C 库函数 - fflush()

13.1 与文件进行通信

13.1.1 文件是什么

文件通常是在磁盘或固态硬盘上的一段已命名的存储区。

C把文件看作是一系列连续的字节,每个字节都能被单独读取。

C提供两种文件模式:文本模式二进制模式

13.1.2 文本模式和二进制模式

所有文件的内容(在磁盘上)都以二进制(0或1)表示。

因为编码方式不同,导致展现的形式不同。

定义

  • 如果文件使用二进制编码的字符,如Unicode,ASCII表示文本,那么就是文本文件。
  • 如果二进制代表的是机器语言,或数值数据,或图片,或音乐编码,该文件就是二进制文件。

区别

  1. 对于字符串中\n的处理不同。
    在Windows系统中用文本模式打开文件时会发现文件中的换行符是\n,这看起来再正常不过了,但当你改为用二进制模式打开文件时会发现文件中的换行符变为\r\n
    事实上,在Windows系统中的的文件中的换行符都是\r\n,只不过在使用文本模式时,会把文件内容从文本的系统表示法映射为C表示法,而使用二进制表示法时不会进行映射转换。

  2. 以二进制模式打开文件是,可以逐字节读取文件。
    这也就是为什么在使用部分函数例如fseek()和ftell()时必须以二进制模式打开文件

  3. 使用文本模式打开文件时,就要用fprintf()写入数据,使用二进制模式打开文件时候,就要用fwrite()写入数据。
    这是因为fprintf()类型的函数会将数据转换为文本,而fwrite()这类函数则不会。

为了规范文本文件的处理,C提供两种访问文件的途径:文本模式二进制模式

  • 在二进制模式中,程序可以访问文件的每个字节。
  • 在文本模式中,程序所见的内容与文件实际内容不同。典型代表就是对于字符串中\n的处理。

13.1.3 I/O的级别

  • 底层I/O:使用操作系统的基本I/O服务。
  • 标准高级I/O:使用C库的标准包和stdio.h头文件定义。

C标准只支持标准I/O包。有些实现会提供底层库,但C标准建立了可移植的I/O模型。

13.1.4 标准文件

C程序会自动打开3个文件:标准输入、标准输出、标准错误输出。

标准输入是系统的普通输入设备,通常是键盘。

标准输出、标准错误输出是系统的普通输出设备,通常是显示屏。

标准错误输出提供了一个从逻辑上不同的地方来发送错误的信息。

13.2 标准I/O

标准I/O由ANSI C标准定义,C标准中定义了C库,标准I/O就是C库中用来输入和输出的函数。标准I/O在系统调用的上一层多加了一个缓冲区,通过缓冲机制减少了系统调用,实现更高的效率。

标准I/O包的3个好处:

  1. 可移植
  2. 标准I/O有许多专门的函数简化了处理不同I/O的问题
  3. 输入和输出都是缓冲

程序清单13.1 count.c程序

#include <stdio.h>
#include <stdlib.h>int main(int argc, char *argv[])
{int ch;FILE *fp;unsigned count = 0;if (argc != 2){printf("Usage: %s filename\n", argv[0]);exit(EXIT_FAILURE);}if ((fp = fopen(argv[1], "r")) == NULL){printf("Can't open %s\n", argv[1]);exit(EXIT_FAILURE);}while ((ch = getc(fp)) != EOF){putc(ch, stdout);count++;}fclose(fp);printf("File %s has %lu characters\n", argv[1], count);system("pause");return 0;
}

13.3 一个简单的文件压缩程序

程序清单13.2 reducto.c程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LEN 40int main(int argc, char *argv[])
{FILE *in, *out;int ch;char name[LEN];int count = 0;if (argc < 2){fprintf(stderr, "Usage: %s filename\n", argv[0]);exit(EXIT_FAILURE);}if ((in = fopen(argv[1], "r")) == NULL){fprintf(stderr, "I couldn't open the file \"%s\"\n", argv[1]);exit(EXIT_FAILURE);}strncpy(name, argv[1], LEN - 5);name[LEN - 5] = '\0';strcat(name, ".red");if ((out = fopen(name, "w")) == NULL){fprintf(stderr, "Can't create output file.\n");exit(3);}while ((ch = getc(in)) != EOF)if (count++ % 3 == 0)putc(ch, out);if (fclose(in) != 0 || fclose(out) != 0)fprintf(stderr, "Error in closing files\n");system("pause");return 0;
}

运行结果:

在这里插入图片描述

在这里插入图片描述

13.4 文件I/O:fprintf()、fscanf()、fgets()和fputs()

程序清单13.3 addword.c程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 41int main(void)
{FILE *fp;char words[MAX];if ((fp = fopen("wordy", "a+")) == NULL){fprintf(stderr, "Can't open \"wordy\" file.\n");exit(EXIT_FAILURE);}puts("Enter words to add to the file; press the #");puts("key at the beginning of a line to terminate.");while ((fscanf(stdin, "%40s", words) == 1) && (words[0] != '#'))fprintf(fp, "%s\n", words);puts("File contents:");rewind(fp); /*返回到文件开始处*/while (fscanf(fp, "%s", words) == 1)puts(words);puts("Done!");if (fclose(fp) != 0)fprintf(stderr, "Error closing file\n");system("pause");return 0;
}

运行结果:

在这里插入图片描述

在这里插入图片描述

13.5 随机访问:fseek()和ftell()

C 库函数 - fseek()

描述
C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。

声明
下面是 fseek() 函数的声明。

int fseek(FILE *stream, long int offset, int whence)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
  • offset – 这是相对 whence 的偏移量,以字节为单位。
  • whence – 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
常量描述
SEEK_SET文件的开头
SEEK_CUR文件指针的当前位置
SEEK_END文件的末尾

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

实例
下面的实例演示了 fseek() 函数的用法。

#include <stdio.h>int main ()
{FILE *fp;fp = fopen("file.txt","w+");fputs("This is runoob.com", fp);fseek( fp, 7, SEEK_SET );fputs(" C Programming Langauge", fp);fclose(fp);return(0);
}

让我们编译并运行上面的程序,这将创建文件 file.txt,它的内容如下。最初程序创建文件和写入 This is runoob.com,但是之后我们在第七个位置重置了写指针,并使用 puts() 语句来重写文件,内容如下:

This is C Programming Langauge

现在让我们使用下面的程序查看上面文件的内容:

#include <stdio.h>int main ()
{FILE *fp;int c;fp = fopen("file.txt","r");while(1){c = fgetc(fp);if( feof(fp) ){break ;}printf("%c", c);}fclose(fp);return(0);
}

运行结果:

在这里插入图片描述

C 库函数 - ftell()

描述
C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置,即:参数指向文件的当前位置距文件开始处的字节数。

声明:
下面是 ftell() 函数的声明。

long int ftell(FILE *stream)

参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值
该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。
返回类型为long。

实例
下面的实例演示了 ftell() 函数的用法。

#include <stdio.h>int main ()
{FILE *fp;int len;fp = fopen("file.txt", "r");if( fp == NULL ) {perror ("打开文件错误");return(-1);}fseek(fp, 0, SEEK_END);len = ftell(fp);fclose(fp);printf("file.txt 的总大小 = %d 字节\n", len);return(0);
}

假设我们有一个文本文件 file.txt,它的内容如下:

This is runoob.com

让我们编译并运行上面的程序,如果文件内容如上所示,这将产生以下结果,否则会根据文件内容给出不同的结果:

file.txt 的总大小 = 18 字节

运行结果:

This is runoob.com

reverse.c

程序清单13.4 reverse.c

逆序打印文件内容。

代码:

#include <stdio.h>
#include <stdlib.h>
#define CNTL_Z '\032' /* eof marker in DOS text files */
#define SLEN 81
int main(void)
{char file[SLEN];char ch;FILE *fp;long count, last;puts("Enter the name of the file to be processed:");scanf("%80s", file);if ((fp = fopen(file, "rb")) == NULL){ /* read-only mode   */printf("reverse can't open %s\n", file);exit(EXIT_FAILURE);}fseek(fp, 0L, SEEK_END); /* go to end of file */last = ftell(fp);for (count = 1L; count <= last; count++){fseek(fp, -count, SEEK_END); /* go backward      */ch = getc(fp);if (ch != CNTL_Z && ch != '\r') /* MS-DOS files */putchar(ch);}putchar('\n');fclose(fp);return 0;
}

文件内容:

在这里插入图片描述

运行结果:

在这里插入图片描述

13.5.2 二进制模式和文本模式

ftell()函数在二进制模式和文本模式的工作方式不同。

ANSI C规定:

  • 对于文本模式,ftell()返回的值可以作为fseek()的第2个参数。
  • 对于MS-DOS,ftell()返回的值把\r\n当作一个字节计数。

13.5.3 可移植性

理论上,fseek()和ftell()应该符合UNIX模型。

限制:

  • 在二进制模式中,实现不必支持SEEK_END模式
  • 在文本模式中,只有以下调用能保证其相应的行为
函数调用效果
fseek(file, 0L, SEEK_SET)定位到文件的开头
fseek(file, 0L, SEEK_CUR)保持当前位置不动
fseek(file, 0L, SEEK_END)定位到文件的末尾
fseek(file, ftell_pos, SEEK_SET)到距离文件开始处ftell_pos的位置,ftell_pos是ftell()的返回值

不过,许多常见的环境都支持更多的行为。

13.5.4 fgetpos()和fsetpos()函数

fseek()和ftell()潜在的问题是,它们都把文件大小限制在long类型能表达的范围内。

但随着文件越来越大,ANSI C新增了2个处理较大文件的新定位函数:fgetpos()fsetpos()

这两个函数不使用long类型的值表示位置,它们使用一种全新的类型:fpos_t

C 库函数 - fgetpos()

描述
C 库函数 int fgetpos(FILE *stream, fpos_t *pos) 获取流 stream 的当前文件位置,并把它写入到 pos。

声明
下面是 fgetpos() 函数的声明。

int fgetpos(FILE *stream, fpos_t *pos)

参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
pos – 这是指向 fpos_t 对象的指针。

返回值
如果成功,该函数返回零。如果发生错误,则返回非零值。

实例
下面的实例演示了 fgetpos() 函数的用法。

#include <stdio.h>int main ()
{FILE *fp;fpos_t position;fp = fopen("file.txt","w+");fgetpos(fp, &position);fputs("Hello, World!", fp);fsetpos(fp, &position);fputs("这将覆盖之前的内容", fp);fclose(fp);return(0);
}

让我们编译并运行上面的程序,这将创建一个文件 file.txt,它的内容如下。首先我们使用 fgetpos() 函数获取文件的初始位置,接着我们向文件写入 Hello, World!,然后我们使用 fsetpos() 函数来重置写指针到文件的开头,重写文件为下列内容:

这将覆盖之前的内容

现在让我们使用下面的程序查看上面文件的内容:

#include <stdio.h>int main ()
{FILE *fp;int c;int n = 0;fp = fopen("file.txt","r");while(1){c = fgetc(fp);if( feof(fp) ){break ;}printf("%c", c);}fclose(fp);return(0);
}

运行结果:

在这里插入图片描述

执行第13行代码前,file.txt的内容:

在这里插入图片描述

执行第13行代码前,覆盖后file.txt的内容:

在这里插入图片描述

13.6 标准I/O的机理

通常,使用标准I/O的第1步是调用fopen()打开文件(前面介绍过,C程序会自动打开3种标准文件)。fopen()函数不仅打开一个文件,还创建了一个缓冲区(在读写模式下会创建两个缓冲区)以及一个包含文件和缓冲区数据的结构。另外,fopen()返回一个指向该结构的指针,以便其他函数知道如何找到该结构。假设把该指针赋给一个指针变量fp,我们说fopen()函数“打开一个流”。如果以文本模式打开该文件,就获得一个文本流;如果以二进制模式打开该文件,就获得一个二进制流。

这个结构通常包含一个指定流中当前位置的文件位置指示器。除此之外,它还包含错误和文件结尾的指示器、一个指向缓冲区开始处的指针、一个文件标识符和一个计数(统计实际拷贝进缓冲区的字节数)。

我们主要考虑文件输入。通常,使用标准I/O的第2步是调用一个定义在stdio.h中的输入函数,如fscanf()、getc()或 fgets()。一调用这些函数,文件中的数据块就被拷贝到缓冲区中。缓冲区的大小因实现而异,一般是512字节或是它的倍数,如4096或16384(随着计算机硬盘容量越来越大,缓冲区的大小也越来越大)。最初调用函数,除了填充缓冲区外,还要设置fp所指向的结构中的值。尤其要设置流中的当前位置和拷贝进缓冲区的字节数。通常,当前位置从字节0开始。

在初始化结构和缓冲区后,输入函数按要求从缓冲区中读取数据。在它读取数据时,文件位置指示器被设置为指向刚读取字符的下一个字符。由于stdio.h系列的所有输入函数都使用相同的缓冲区,所以调用任何一个函数都将从上一次函数停止调用的位置开始。

当输入函数发现已读完缓冲区中的所有字符时,会请求把下一个缓冲大小的数据块从文件拷贝到该缓冲区中。以这种方式,输入函数可以读取文件中的所有内容,直到文件结尾。函数在读取缓冲区中的最后一个字符后,把结尾指示器设置为真。于是,下一次被调用的输入函数将返回EOF。

输出函数以类似的方式把数据写入缓冲区。当缓冲区被填满时,数据将被拷贝至文件中。

13.7 其他标准I/O函数

C 库函数 - ungetc()

描述
C 库函数 int ungetc(int char, FILE *stream) 把字符 char(一个无符号字符)推入到指定的流 stream 中,以便它是下一个被读取到的字符。

声明
下面是 ungetc() 函数的声明。

int ungetc(int char, FILE *stream)

参数
char – 这是要被推入的字符。该字符以其对应的 int 值进行传递。
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了输入流。

返回值
如果成功,则返回被推入的字符,否则返回 EOF,且流 stream 保持不变。

实例
下面的实例演示了 ungetc() 函数的用法。

代码:

#include <stdio.h>int main ()
{FILE *fp;int c;char buffer [256];fp = fopen("file.txt", "r");if( fp == NULL ){perror("打开文件时发生错误");return(-1);}while(!feof(fp)){c = getc (fp);/* 把 ! 替换为 + */if( c == '!' ){ungetc ('+', fp);}else{ungetc(c, fp);}fgets(buffer, 255, fp);fputs(buffer, stdout);}return(0);
}

假设我们有一个文本文件 file.txt,它的内容如下。文件将作为实例中的输入:

this is runoob
!c standard library
!library functions and macros

让我们编译并运行上面的程序,这将产生以下结果:

this is runoob
+c standard library
+library functions and macros

运行结果:

在这里插入图片描述

输出:

在这里插入图片描述

C 库函数 - fflush()

描述
C 库函数 int fflush(FILE *stream) 刷新流 stream 的输出缓冲区。

声明
下面是 fflush() 函数的声明。

int fflush(FILE *stream)

参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个缓冲流。
返回值
如果成功,该函数返回零值。如果发生错误,则返回 EOF,且设置错误标识符(即 feof)。

实例
下面的实例演示了 fflush() 函数的用法。

代码:

#include <stdio.h>
#include <string.h>int main()
{char buff[1024];memset( buff, '\0', sizeof( buff ));fprintf(stdout, "启用全缓冲\n");setvbuf(stdout, buff, _IOFBF, 1024);fprintf(stdout, "这里是 runoob.com\n");fprintf(stdout, "该输出将保存到 buff\n");fflush( stdout );fprintf(stdout, "这将在编程时出现\n");fprintf(stdout, "最后休眠五秒钟\n");sleep(5);return(0);
}

让我们编译并运行上面的程序,这将产生以下结果。在这里,程序把缓冲输出保存到 buff,直到首次调用 fflush() 为止,然后开始缓冲输出,最后休眠 5 秒钟。它会在程序结束之前,发送剩余的输出到 STDOUT。

启用全缓冲
这里是 runoob.com
该输出将保存到 buff
这将在编程时出现
最后休眠五秒钟

说明

调用fflush()函数引起输出缓冲区中所有的未写入数据被发送到参数fp指定的输出文件。

这个过程称为刷新缓冲区

如果fp是空指针, 所有输出缓冲区都被刷新。

在输入流中使用fflush()函数的效果是未定义的。只要最近一次操作不是输入操作,就可以用该函数来更新流。

运行结果:

在这里插入图片描述

http://www.lryc.cn/news/19520.html

相关文章:

  • Datacom-HCIE考试经验分享
  • 第十二章 系统错误消息 - 一般系统错误消息 P - S
  • 【git】Idea中git的使用
  • Centos安装Python、PyCharm
  • 搞百亿补贴,京东不能只“砸钱”
  • automl介绍以及代码实例
  • kill 与killall
  • 【加密】开发常见加密类型
  • 数据结构之基:从根儿上了解数据结构的特性
  • C++ 枚举详解
  • 【vue3】ref , reactive ,toRef ,toRefs 使用和理解
  • fastadmin:如何点击按钮弹出存在的指定页面的弹窗
  • 【storybook】你需要一款能在独立环境下开发组件并生成可视化控件文档的框架吗?(三)
  • Android源码分析 —— Activity栈管理(基于Android8)
  • Python实现贝叶斯优化器(Bayes_opt)优化支持向量机分类模型(SVC算法)项目实战
  • 【华为OD机试模拟题】用 C++ 实现 - 分积木(2023.Q1)
  • FFmpeg/OpenCV 实现全屏斜体水印
  • Calendar计算两个时间之间相差几个月
  • FPGA基础知识
  • C语言运算符逻辑运算符位运算符
  • 机器学习:基于主成分分析(PCA)对数据降维
  • 软件测试之测试模型
  • 【项目精选】网络考试系统的设计与实现(源码+视频+论文)
  • Python近红外光谱分析与机器学习、深度学习方法融合实践技术
  • 实例7:树莓派呼吸灯
  • java 接口 详解
  • 用 Visual Studio 升级 .NET 项目
  • JavaWeb中FilterListener的神奇作用
  • 移动端布局
  • 前端无感登录,大文件上传