流动的代码:文件流畅读写的艺术(三)

文件操作

  • 函数对比
    • scanf,fscanf,sscanf
      • sscanf 函数
    • printf , fprintf , sprintf
      • sprintf函数
  • 文件的随机读写
    • fseek函数
    • ftell函数
    • rewind函数
  • 文件读取结束的判定
    • feof和ferror函数
      • 判断方式
  • 文件缓冲区

函数对比

scanf,fscanf,sscanf

scanffscanfsscanf 是 C 语言中用于输入操作的函数,特别是用于格式化输入。它们属于标准输入/输出库,用于按照指定格式从不同来源读取数据。
以下是它们的基本详情和区别:

scanf ( ):

  • 用途:它从标准输入流(stdin)读取输入,通常是键盘。
  • 格式:int scanf(const char *format, …);
  • 目的:用于根据提供的格式说明符从标准输入读取各种数据类型。

示例:读取一个整数和一个字符。

int i;
char c;
scanf("%d %c", &i, &c);

fscanf ( ):

  • 用途:它从文件流读取输入,不仅限于 stdin
  • 格式:int fscanf(FILE *stream, const char *format, …);
  • 目的:它类似于 scanf,但可用于任何使用 fopen 函数打开的文件或任何预定义的文件流。这允许从文件或其他输入流读取格式化输入

示例:从文件中读取一个整数。

FILE *fp;
int n;
fp = fopen("file.txt", "r");
if(fp != NULL) {
    fscanf(fp, "%d", &n);
    fclose(fp);
}

sscanf 函数

sscanf 函数用于从字符串中按指定格式读取数据,这对于解析字符串中的特定数据非常有用

int sscanf(const char *str, const char *format, ...);
  • str:要读取数据的源字符串。
  • format:格式字符串,指定了希望从源字符串中读取数据的类型和格式。
  • ‘…’:额外的参数,用于存储从源字符串中按照格式字符串读取的数据。

返回值:返回成功读取的数据项的数量。如果在读取任何数据之前遇到错误或到达字符串的结尾,则返回EOF

假设你有一个包含整数和浮点数的字符串,你想从中提取这些数值:

#include 

int main() {
    char *str = "100 3.14";
    int i;
    float f;

    int result = sscanf(str, "%d %f", &i, &f);

    if (result == 2) {
        printf("整数:%d\n", i);
        printf("浮点数:%f\n", f);
    } else {
        printf("格式化读取失败\n");
    }

    return 0;
}

在这个例子中,sscanf 会尝试从字符串 “100 3.14” 中读取一个整数和一个浮点数。如果成功,它会返回读取的项数(在这个例子中是2),并且变量 i 和 f 将分别被赋值为100和3.14。
注意事项

  • 安全性:与其他格式化输入函数一样,使用 sscanf 时需注意安全性,特别是对字符串的长度和格式的处理,以避免溢出等问题。
  • 错误处理:检查 sscanf 的返回值来确认成功读取的数据项数量,这对于验证和错误处理很重要。
  • 使用场景:sscanf 特别适用于从已经存在的字符串中提取数据,例如解析来自文件、网络或用户输入的数据。

printf , fprintf , sprintf

printf 函数

int printf(const char *format, ...);

用途:将格式化的输出发送到标准输出,通常是屏幕(控制台)。

  • format:格式字符串,指定了输出的格式。
  • ‘…’:可变参数列表,包含要输出的数据。

示例:向控制台打印整数和字符串。

int num = 10;
char *arr = "Hello, world!";
printf("Number: %d, arr: %s\n", num, arr);

fprintf 函数

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

用途:将格式化的输出发送到指定的文件流中,可以是任何 FILE 类型的流,包括标准输出(stdout)和标准错误(stderr)。

示例:向文件写入格式化文本。

FILE *fp = fopen("output.txt", "w");
if (fp != NULL) {
    fprintf(fp, "Number: %d\n", num);
    fclose(fp);
}

sprintf函数

sprintf 函数用于将格式化的数据写入字符串。它是标准输入输出库中的一个重要函数,特别适用于创建格式化字符串

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

返回值:返回写入到目标字符串的字符数,不包括终结的空字符(‘\0’)。如果发生错误,则可能返回负值。

假设您想将一个整数和一个浮点数格式化为一个字符串:

#include 

int main() {
    int num = 25;
    float pi = 3.14159;
    char buffer[50];

    sprintf(buffer, "%d, %f", num, pi);

    printf("格式化后的字符串:%s\n", buffer);

    return 0;
}

流动的代码:文件流畅读写的艺术(三)_第1张图片
在这个例子中,sprintf 将整数 num 和浮点数 pi 按指定的格式写入字符串 buffer。之后,可以使用 printf 打印这个字符串,或者以其他方式使用它。

文件的随机读写

顺序读写数据是按照顺序一个接一个地读取或写入的,通常从文件的开始位置开始,然后逐步向后移动,直到文件结束。
而随机读写允许直接跳转到文件中的任何位置进行读取或写入。不必遵循特定的顺序,可以访问文件的任何部分

fseek函数

fseek 函数用于在文件中移动文件指针到指定位置,从而实现文件的随机访问

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

offset:相对于 origin 参数所指定位置的偏移量,以字节为单位。
origin:起始位置,它可以是 SEEK_SET(文件开头)、==SEEK_CUR(当前位置)==或 SEEK_END(文件末尾)

先举一个例子:

int main()
{
	FILE* pf = fopen("test2.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	fclose(pf);
	pf = NULL;
	return 0;
}

在test2.txt中我们放入abcdefgh,打印结果
流动的代码:文件流畅读写的艺术(三)_第2张图片
这里,移动文件指针按照顺序移动,那么如果我想让指针重新指向a呢?这里就得使用fseek函数:

fseek(pf,-3, SEEK_CUR);

这里从当前位置移动,-3即为向文件开头方向移动.
打印结果:
流动的代码:文件流畅读写的艺术(三)_第3张图片

fseek(pf,0, SEEK_SET);

这种写法是从起始位置偏移0个字符,所以还是起始位置
流动的代码:文件流畅读写的艺术(三)_第4张图片

ftell函数

若现在不知道偏移量是多少,就可以使用ftell函数;

long ftell(FILE *stream);

比如上面的例子:

int main()
{
	FILE* pf = fopen("test2.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);
	int n = ftell(pf);
	printf("%d\n", n);
	fseek(pf,0, SEEK_SET);
	ch = fgetc(pf);
	printf("%c\n", ch);

	fclose(pf);
	pf = NULL;
	return 0;
}

这里用n来接收偏移量,打印结果:
流动的代码:文件流畅读写的艺术(三)_第5张图片
我们也可以用这个函数来判断文件有多少个字节:

  • 先用fseek将指针移动到末尾
  • 再用ftell函数;
int main()
{
	FILE* pf = fopen("test2.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf, 0, SEEK_END);

	int n = ftell(pf);
	printf("%d\n", n);
	
	fclose(pf);
	pf = NULL;
	return 0;
}

rewind函数

rewind用于将文件的位置指针重置到文件的开始位置。它的功能类似于使用 fseek 函数来将文件指针移动到文件开头,但 rewind 不返回值,因此不能用来检测错误。

void rewind(FILE *stream);
int main()
{
	FILE* pf = fopen("test2.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	rewind(pf);

	ch = fgetc(pf);
	printf("%c\n", ch);

	fclose(pf);
	pf = NULL;
	return 0;
}

还以这串代码为例,rewind函数调用后,移动指针指向起始位置,打印结果为a
流动的代码:文件流畅读写的艺术(三)_第6张图片

文件读取结束的判定

feof和ferror函数

feof 和 ferror 是用于检查文件状态的两个不同函数,它们分别用于检测文件流的结束-of-file (EOF) 状态和读写错误。
feof

int feof(FILE *stream);

feof 用于检查是否已经读取到文件的末尾。它检查与文件流关联的 EOF 标志位。
如果已经达到文件末尾,返回非零值;否则,返回 0

FILE *filePointer = fopen("file.txt", "r");
// ... 文件读取操作 ...

if (feof(filePointer)) {
    // 已到达文件末尾
}

ferror

int ferror(FILE *stream)

ferror 用于检查文件流是否因为错误而无法继续读取或写入
如果文件流有错误,返回非零值;否则,返回 0

注意点

  • EOF and 错误:feof 和 ferror 检查的是不同的情况:feof 是检查是否到达文件末尾而 ferror 是检查文件操作是否发生错误
  • 循环中使用:在循环中读取文件时,应当检查这两个函数来确保正确处理文件末尾和可能发生的错误。
  • feof 的误用:经常有误用 feof 的情况,即在循环条件中直接使用 feof。正确的方法是在读取操作后检查 feof。因为只有在尝试读取超过文件末尾之后,EOF 标志才会被设置。

判断方式

  1. 文本文件读取是否结束,判断返回值是否为EOF( fgetc),或者NULL(fgets)
  2. 二进制文本的读取结束判断,判断返回值是否小于实际要读的个数,例如
    fread判断返回值是否小于实际要求的个数

使用 fgetc 读取文件的示例

#include 

int main() {
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("fopen");
        return 1;
    }

    int c; // 注意:int 类型用于存储 EOF

    // 使用 fgetc 逐字符读取文件,直到文件结束
    while ((c = fgetc(file)) != EOF) {
        putchar(c); // 输出字符
    }

    // 检查是否因为文件末尾才停止读取
    if (feof(file)) {
        puts("\n文件已读取完毕");
    } else if (ferror(file)) {
        perror("读取文件时发生错误");
    }

    fclose(file);

    return 0;
}

使用 fread 读取文件的示例

#include 

int main() {
    FILE *file;
    int number;

    // 打开文件用于二进制读取
    file = fopen("output.txt", "rb");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 使用fread读取二进制数
    size_t itemsRead = fread(&number, sizeof(int), 1, file);
    if (itemsRead == 1) {
        printf("读取的整数是:%d\n", number);
    } else {
        // 如果没有读取到一个整数,打印错误信息
        if (feof(file)) {
            printf("文件结束,未读取到数据。\n");
        }
        if (ferror(file)) {
            printf("读取文件时出错。\n");
        }
    }

    // 关闭文件
    fclose(file);

    return 0;
}

文件缓冲区

缓冲区在计算机科学中是一块内存区域,用于临时存放数据,目的是在数据在发送者和接收者之间传输时调节和平衡数据流。在 I/O 操作的上下文中,缓冲区的主要作用是减少对硬件设备(如硬盘、网络设备等)的直接访问次数,提高数据处理的效率和吞吐量。

标准库提供的文件操作函数(如 fread、fwrite、printf、scanf 等)通常都会使用这些缓冲区

  • 功能和使用
    • 提高性能:缓冲区可以减少对底层 I/O(输入/输出) 系统的调用次数,因为数据是在缓冲区中累积起来,然后一次性进行读写,这通常可以提高性能。
    • 缓冲区管理:C 标准库提供了一系列函数来管理和控制缓冲区,如 setbuf、setvbuf 等。
    • 刷新缓冲区:在需要时,可以使用 fflush 函数手动刷新输出缓冲区,将缓冲区内的数据写入实际的 I/O 设备中。例如,可能需要在写入文件后立即刷新缓冲区,以确保数据被物理写入磁盘。
    • 关闭文件:在关闭文件时(使用 fclose),缓冲区会自动被刷新

例如,在 C 中,FILE 结构就关联了一个缓冲区。当你使用 fopen 打开一个文件时,系统会自动分配一个缓冲区,你可以使用 setvbuf 来更改其缓冲行为。当你读写数据时,例如使用 fread 或 fwrite 函数,这些数据会传递通过这个缓冲区,从而提高读写操作的效率。

在文本编辑器中,用户的输入通常存储在缓冲区内直到按下 “保存” 按钮时才写入硬盘。在网络通信中,数据包可能会首先存储在缓冲区内,然后一起发送以减少网络传输开销。在视频流媒体播放中,视频数据可以预先存储在缓冲区内,以避免播放时由于网络延迟导致的卡顿。

本章内容到此结束!感谢大家的观看!!

你可能感兴趣的:(c语言)