MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C 语言fscanf和fprintf函数用法详解

2022-09-051.1k 阅读

C 语言 fscanf 和 fprintf 函数用法详解

一、fscanf 函数概述

在 C 语言中,fscanf 函数用于从指定的文件流中按照指定的格式读取数据。它是 scanf 系列函数的一部分,与 scanf 不同的是,scanf 是从标准输入流(通常是键盘)读取数据,而 fscanf 可以从任何已打开的文件流中读取数据。这使得 fscanf 在处理文件数据输入时非常有用。

fscanf 函数的原型定义在 <stdio.h> 头文件中,其原型如下:

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

其中,stream 是指向要读取数据的文件流指针,format 是一个格式字符串,用于指定读取数据的格式,... 表示可变参数列表,用于接收读取到的数据。

二、fscanf 函数格式字符串

格式字符串 formatfscanf 函数的关键部分,它决定了如何从文件流中解析数据。格式字符串由以下几部分组成:

  1. 空白字符:空格、制表符(\t)、换行符(\n)等空白字符在格式字符串中会被忽略,但它们会匹配文件流中的一个或多个空白字符。例如,格式字符串 " %d" 中的空格会跳过文件流中的任何数量的空白字符,然后读取一个整数。

  2. 普通字符:格式字符串中的普通字符必须与文件流中的字符完全匹配。例如,格式字符串 "name: %s" 要求文件流中先出现字符串 "name:",然后是一个或多个空白字符,最后是一个字符串。

  3. 转换说明符:转换说明符以 % 字符开头,用于指定要读取的数据类型。常见的转换说明符有:

    • %d:读取一个十进制整数。
    • %f:读取一个浮点数。
    • %c:读取一个字符。
    • %s:读取一个字符串,遇到空白字符停止。
    • %[... ]:扫描字符集合,例如 %[a - z] 读取由小写字母组成的字符串。

三、fscanf 函数使用示例

1. 从文件读取整数和浮点数

假设有一个文本文件 data.txt,内容如下:

10 3.14

以下是使用 fscanf 从该文件读取整数和浮点数的代码示例:

#include <stdio.h>

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

    int num;
    float fnum;
    int result = fscanf(file, "%d %f", &num, &fnum);

    if (result == 2) {
        printf("Read integer: %d\n", num);
        printf("Read float: %f\n", fnum);
    } else {
        printf("Failed to read data as expected.\n");
    }

    fclose(file);
    return 0;
}

在上述代码中,首先使用 fopen 打开文件 data.txt,然后使用 fscanf 按照格式字符串 "%d %f" 从文件中读取一个整数和一个浮点数。如果成功读取两个数据(result == 2),则输出读取到的值;否则,输出错误信息。最后使用 fclose 关闭文件。

2. 从文件读取字符串

假设有一个文本文件 names.txt,内容如下:

Alice
Bob

以下是使用 fscanf 从该文件读取字符串的代码示例:

#include <stdio.h>

#define MAX_LENGTH 50

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

    char name[MAX_LENGTH];
    while (fscanf(file, "%s", name) != EOF) {
        printf("Read name: %s\n", name);
    }

    fclose(file);
    return 0;
}

在这个例子中,通过 while 循环不断使用 fscanf 从文件中读取字符串,直到文件结束(fscanf 返回 EOF)。每次读取到一个字符串后,将其输出。注意,为了防止缓冲区溢出,需要确保 name 数组的大小足够大。

3. 复杂格式读取

假设有一个文本文件 students.txt,内容如下:

Alice 20 3.5
Bob 21 3.2

每一行包含学生姓名、年龄和平均绩点(GPA)。以下是使用 fscanf 读取这种复杂格式数据的代码示例:

#include <stdio.h>

#define MAX_NAME_LENGTH 50

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

    char name[MAX_NAME_LENGTH];
    int age;
    float gpa;
    while (fscanf(file, "%s %d %f", name, &age, &gpa) != EOF) {
        printf("Name: %s, Age: %d, GPA: %f\n", name, age, gpa);
    }

    fclose(file);
    return 0;
}

在这段代码中,格式字符串 "%s %d %f" 用于匹配文件中每一行的格式,依次读取学生姓名、年龄和 GPA,并在每次读取成功后输出相关信息。

四、fscanf 函数返回值

fscanf 函数的返回值表示成功匹配并赋值的参数个数。如果在匹配任何值之前到达文件末尾,则返回 EOF。例如,在前面从 students.txt 文件读取数据的示例中,如果文件中某一行的格式不正确,fscanf 可能无法成功匹配并赋值所有参数,返回值就会小于 3。可以根据返回值来判断读取操作是否成功,并进行相应的错误处理。

五、fprintf 函数概述

fscanf 函数相反,fprintf 函数用于将数据按照指定的格式输出到指定的文件流中。它同样定义在 <stdio.h> 头文件中,其原型如下:

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

其中,stream 是指向要输出数据的文件流指针,format 是格式字符串,用于指定输出数据的格式,... 是可变参数列表,包含要输出的数据。

六、fprintf 函数格式字符串

fprintf 的格式字符串与 fscanf 的格式字符串有很多相似之处,但用途不同。格式字符串中的转换说明符用于指定如何格式化输出数据。常见的转换说明符及其含义与 fscanf 中的类似,例如:

  • %d:以十进制格式输出整数。
  • %f:以浮点数格式输出,默认保留 6 位小数。
  • %c:输出一个字符。
  • %s:输出一个字符串。

此外,还可以在转换说明符中使用修饰符来进一步控制输出格式,例如:

  • %mdm 为整数,指定输出整数的最小宽度。如果输出的整数位数小于 m,则在前面补空格。
  • %0md:与 %md 类似,但在前面补零而不是空格。
  • %.nfn 为整数,指定浮点数输出的小数位数。

七、fprintf 函数使用示例

1. 向文件输出整数和浮点数

以下代码示例将整数和浮点数输出到文件 output.txt 中:

#include <stdio.h>

int main() {
    FILE *file = fopen("output.txt", "w");
    if (file == NULL) {
        perror("Failed to open file");
        return 1;
    }

    int num = 10;
    float fnum = 3.14;
    int result = fprintf(file, "Integer: %d, Float: %f", num, fnum);

    if (result > 0) {
        printf("Data written successfully.\n");
    } else {
        printf("Failed to write data.\n");
    }

    fclose(file);
    return 0;
}

在上述代码中,使用 fopen 以写入模式打开文件 output.txt,然后使用 fprintf 将整数和浮点数按照指定格式输出到文件中。fprintf 的返回值表示成功写入的字符数,如果返回值大于 0,则表示写入成功。最后关闭文件。

2. 向文件输出字符串

以下代码示例将字符串输出到文件 message.txt 中:

#include <stdio.h>

int main() {
    FILE *file = fopen("message.txt", "w");
    if (file == NULL) {
        perror("Failed to open file");
        return 1;
    }

    char message[] = "Hello, World!";
    int result = fprintf(file, "%s", message);

    if (result > 0) {
        printf("Message written successfully.\n");
    } else {
        printf("Failed to write message.\n");
    }

    fclose(file);
    return 0;
}

此示例中,通过 fprintf 将字符串 message 输出到文件 message.txt 中,并根据返回值判断写入操作是否成功。

3. 格式化输出复杂数据

假设有一个结构体表示学生信息,如下:

#include <stdio.h>

#define MAX_NAME_LENGTH 50

typedef struct {
    char name[MAX_NAME_LENGTH];
    int age;
    float gpa;
} Student;

int main() {
    FILE *file = fopen("students_output.txt", "w");
    if (file == NULL) {
        perror("Failed to open file");
        return 1;
    }

    Student student1 = {"Alice", 20, 3.5};
    Student student2 = {"Bob", 21, 3.2};

    int result1 = fprintf(file, "Name: %s, Age: %d, GPA: %.2f\n", student1.name, student1.age, student1.gpa);
    int result2 = fprintf(file, "Name: %s, Age: %d, GPA: %.2f\n", student2.name, student2.age, student2.gpa);

    if (result1 > 0 && result2 > 0) {
        printf("Student data written successfully.\n");
    } else {
        printf("Failed to write student data.\n");
    }

    fclose(file);
    return 0;
}

在这个例子中,定义了一个 Student 结构体来存储学生的姓名、年龄和 GPA。然后使用 fprintf 将两个学生的信息按照特定格式输出到文件 students_output.txt 中。这里使用了 %.2f 格式说明符来限制 GPA 输出的小数位数为 2 位。

八、fprintf 函数返回值

fprintf 函数的返回值表示成功写入文件流的字符数。如果发生错误,返回一个负数。例如,如果磁盘空间不足或者文件流出现故障,fprintf 可能返回负数。在实际编程中,应该检查 fprintf 的返回值,以确保数据正确写入文件。

九、fscanf 和 fprintf 函数的注意事项

1. 文件流的打开与关闭

在使用 fscanffprintf 之前,必须先使用 fopen 打开文件,并在操作完成后使用 fclose 关闭文件。如果忘记关闭文件,可能会导致资源泄漏,特别是在程序频繁读写文件的情况下。另外,在打开文件时,要注意选择正确的打开模式(如 r 只读、w 只写、a 追加等),以避免意外的数据丢失或错误。

2. 格式字符串的准确性

格式字符串必须与实际要读取或输出的数据类型和格式完全匹配。如果格式不匹配,可能会导致未定义行为,例如程序崩溃或数据读取/输出错误。在使用 %s 读取字符串时,要确保目标数组的大小足够大,以防止缓冲区溢出。同样,在使用 fprintf 输出字符串时,也要注意格式字符串中的宽度限制,避免截断字符串。

3. 错误处理

在使用 fscanffprintf 时,要始终检查它们的返回值。对于 fscanf,返回 EOF 可能表示文件结束或读取错误;对于 fprintf,返回负数表示写入错误。通过检查返回值,可以及时发现并处理可能出现的错误,提高程序的健壮性。

4. 缓冲区问题

文件 I/O 操作通常涉及缓冲区。在向文件写入数据时,数据可能先被存储在缓冲区中,直到缓冲区满或者调用 fflush 函数(或者关闭文件)时才真正写入磁盘。这意味着,如果在写入数据后没有及时刷新缓冲区或关闭文件,可能会导致数据丢失。同样,在读取文件时,也可能会受到缓冲区的影响,例如某些读取操作可能会从缓冲区中获取数据,而不是直接从文件中读取。

十、fscanf 和 fprintf 函数与其他 I/O 函数的比较

1. 与 scanf 和 printf 的比较

scanfprintf 是从标准输入流(键盘)读取数据和向标准输出流(屏幕)输出数据的函数。而 fscanffprintf 可以操作任意文件流,这使得它们在处理文件数据时更加灵活。另外,scanfprintf 通常用于交互式程序,而 fscanffprintf 更适用于文件处理和数据持久化场景。

2. 与 fread 和 fwrite 的比较

freadfwrite 用于以二进制模式读取和写入数据块,它们通常比 fscanffprintf 更高效,尤其是在处理大量二进制数据时。fscanffprintf 以文本模式处理数据,会进行字符转换,例如将数字转换为文本格式或反之。如果需要处理二进制数据,如图片、音频等,freadfwrite 是更好的选择;而如果处理的是文本数据,并且需要进行格式化操作,fscanffprintf 则更为合适。

十一、实际应用场景

  1. 数据文件处理:在许多应用程序中,需要从配置文件或数据文件中读取参数或数据。fscanf 可以方便地从文本文件中按照特定格式读取数据,而 fprintf 可以将程序运行过程中生成的数据写入文件,以便后续分析或使用。
  2. 日志记录:在程序开发和调试过程中,日志记录是非常重要的。fprintf 可以将程序运行时的关键信息、错误信息等输出到日志文件中,方便开发人员追踪和调试程序。
  3. 数据转换和格式化输出:当需要将数据从一种格式转换为另一种格式并输出时,fprintf 可以通过灵活的格式字符串实现格式化输出。例如,将数值数据转换为特定格式的字符串,以便在报表或其他文档中使用。

十二、总结

fscanffprintf 是 C 语言中强大的文件输入输出函数,它们提供了灵活的格式化功能,适用于各种文件处理场景。通过深入理解它们的用法、格式字符串以及注意事项,可以有效地在 C 程序中进行文件数据的读取和写入操作。在实际编程中,合理运用这两个函数,并结合其他文件 I/O 函数,可以开发出高效、健壮的数据处理和文件管理程序。无论是处理简单的文本文件还是复杂的配置文件和数据文件,fscanffprintf 都能发挥重要作用。同时,要始终注意文件流的管理、格式匹配以及错误处理,以确保程序的正确性和稳定性。

希望通过本文的详细介绍和示例代码,读者能够对 C 语言中的 fscanffprintf 函数有更深入的理解,并在实际编程中熟练运用它们。