C语言输入输出 (I/O) 函数详解

Aerhuo 发布于 24 天前 58 次阅读


核心概念:流

在C语言中,所有的I/O操作都是通过“流”来完成的。流是一个字符序列,它可以是文件、物理设备(如键盘、屏幕)等。stdio.h 库预定义了三个标准流:

  • stdin: 标准输入流,默认连接到键盘。
  • stdout: 标准输出流,默认连接到屏幕。
  • stderr: 标准错误流,默认连接到屏幕。

格式化I/O函数

printf / fprintf / sprintf / snprintf (常用)

  • 函数原型:
    int printf(const char *format, ...);
    int fprintf(FILE *stream, const char *format, ...);
    int sprintf(char *str, const char *format, ...);
    int snprintf(char *str, size_t size, const char *format, ...);
    
  • 用途:
    • printf: 将格式化的数据输出到 stdout (屏幕)。
    • fprintf: 将格式化的数据输出到指定的 stream (文件)。
    • sprintf: 将格式化的数据输出到一个字符串 str 中。
    • snprintf: sprintf 的安全版本,最多向 str 写入 size 个字符,防止缓冲区溢出。
  • 示例代码:
    #include <stdio.h>
    
    int main() {
        char buffer[50];
        int year = 2024;
    
        // 输出到屏幕
        printf("Hello, World! Year: %dn", year);
    
        // 输出到文件
        FILE *fp = fopen("log.txt", "w");
        if (fp != NULL) {
            fprintf(fp, "Log entry for year %d.n", year);
            fclose(fp);
        }
    
        // 输出到字符串 (安全版本)
        snprintf(buffer, 50, "The year is %d.", year);
        puts(buffer);
    
        return 0;
    }
    
  • 注意事项:
    1. 类型匹配: 必须确保格式说明符(如 %d, %f, %s)与后面的参数类型和数量完全匹配。
    2. 缓冲区溢出: 绝对不要使用 sprintf!它不检查边界,极易造成缓冲区溢出。始终使用 snprintf

scanf / fscanf / sscanf (常用)

  • 函数原型:
    int scanf(const char *format, ...);
    int fscanf(FILE *stream, const char *format, ...);
    int sscanf(const char *str, const char *format, ...);
    
  • 用途:
    • scanf: 从 stdin (键盘) 读取格式化的输入。
    • fscanf: 从指定的 stream (文件) 读取格式化的输入。
    • sscanf: 从一个字符串 str 中解析格式化的输入。
  • 示例代码:
    #include <stdio.h>
    
    int main() {
        int age;
        char name[50];
        const char *data = "Charlie 30";
    
        // 从字符串解析
        sscanf(data, "%49s %d", name, &age);
        printf("从字符串读取 -> 姓名: %s, 年龄: %dn", name, age);
    
        return 0;
    }
    
  • 注意事项:
    1. 取地址符 &: 必须为非指针变量传递地址。
    2. 缓冲区溢出: 使用 %s 时必须指定最大宽度,如 %49s
    3. 检查返回值: 必须检查函数返回的成功赋值的项数。
    4. 输入缓冲区残留: scanffscanf 会将换行符 n 留在输入缓冲区中,可能干扰后续的字符/行读取。

字符I/O函数

fgetc / getc / getchar (常用)

  • 函数原型:
    int fgetc(FILE *stream);
    int getc(FILE *stream); // 通常实现为宏,更快但有副作用风险
    int getchar(void); // 等价于 getc(stdin)
    
  • 用途: 从指定流或 stdin 读取一个字符。

  • 注意事项: 返回类型是 int 而不是 char,以容纳文件结束符 EOF。必须用 int 变量接收返回值。

fputc / putc / putchar (常用)

  • 函数原型:

    int fputc(int character, FILE *stream);
    int putc(int character, FILE *stream); // 通常实现为宏
    int putchar(int character); // 等价于 putc(character, stdout)
    
  • 用途: 将一个字符写入到指定流或 stdout

ungetc (不常用)

  • 函数原型: int ungetc(int character, FILE *stream);
  • 用途: 将一个字符 character 推回到输入流 stream 中,以便下一次读取时能再次读到它。每个流只保证能成功推回一个字符。

行/字符串I/O函数

fgets (常用)

  • 函数原型: char *fgets(char *str, int num, FILE *stream);
  • 用途: 强烈推荐的安全函数。从 stream 读取最多 num-1 个字符到 str 中,或读到换行符为止。它会自动在末尾添加
  • 注意事项: 会将换行符 n 也读入字符串,通常需要手动移除。

fputs / puts (常用)

  • 函数原型:
    c
    int fputs(const char *str, FILE *stream);
    int puts(const char *str);
    * 用途:

    • fputs: 将字符串 str 写入到 stream 中,会自动添加换行。
    • puts: 将字符串 str 写入到 stdout,并自动在末尾添加一个换行符 n

gets (已废弃)

  • 警告: 此函数极度危险,绝对不要使用! 已在 C11 标准中被彻底移除。

文件I/O与定位函数

fopen / fclose (常用)

  • 函数原型:
    c
    FILE *fopen(const char *filename, const char *mode);
    int fclose(FILE *stream);
  • 用途: 打开和关闭文件。fopen 失败返回 NULLfclose 成功返回 0。必须配对使用。

freopen (不常用)

  • 函数原型: FILE *freopen(const char *filename, const char *mode, FILE *stream);
  • 用途: 重新关联一个已打开的流到另一个文件或设备。常用于重定向 stdin, stdout

fseek / ftell / rewind (常用)

  • 函数原型:
    c
    int fseek(FILE *stream, long offset, int whence);
    long ftell(FILE *stream);
    void rewind(FILE *stream);
  • 用途:

    • fseek: 在文件流中定位读写指针。whence 可为 SEEK_SET (文件头), SEEK_CUR (当前位置), SEEK_END (文件尾)。
    • ftell: 返回当前读写指针相对于文件头的位置(字节数)。
    • rewind: 将读写指针移回文件开头,等价于 fseek(stream, 0L, SEEK_SET)
  • 示例代码:
    fseek(fp, 0, SEEK_END); // 定位到文件末尾
    long file_size = ftell(fp); // 获取文件大小
    rewind(fp); // 回到文件开头
    

feof / ferror / clearerr (常用)

  • 函数原型:
    c
    int feof(FILE *stream);
    int ferror(FILE *stream);
    void clearerr(FILE *stream);
  • 用途:
    • feof: 检查文件流的文件结束指示符是否被设置。
    • ferror: 检查文件流的错误指示符是否被设置。
    • clearerr: 清除文件结束和错误指示符。
  • 注意事项: feof 只在尝试读取文件末尾之后才返回真。正确的循环读取文件方式是检查读取函数的返回值,而不是用 while(!feof(fp)) 作为循环条件。

remove / rename (较常用)

  • 函数原型:
    c
    int remove(const char *filename);
    int rename(const char *old_filename, const char *new_filename);
  • 用途: 删除文件和重命名文件。

直接/二进制I/O函数

fread / fwrite (常用)

  • 函数原型:
    c
    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 用途: 用于读写二进制数据块。
  • 注意事项: 返回成功读/写的项目数 (nmemb)。必须检查此返回值。读写二进制数据时应使用二进制模式 ("rb", "wb")。

格式化I/O说明符大全

常用说明符

说明符 参数类型 输出/输入描述
%d, %i int 带符号十进制整数。%iscanf 中可自动识别八/十六进制前缀。
%u unsigned int 无符号十进制整数。
%o unsigned int 无符号八进制整数。
%x, %X unsigned int 无符号十六进制整数 (abcdef / ABCDEF)。
%f, %F double 十进制浮点数。
%e, %E double 科学记数法浮点数 (e / E)。
%g, %G double 自动选择 %f%e 中更紧凑的形式。
%c int 单个字符。
%s char* 字符串。
%p void* 指针地址(以十六进制表示)。
%% (无) 打印一个 % 字符本身。

不常用说明符

说明符 参数类型 输出/输入描述
%a, %A double 十六进制浮点数 (p / P 表示指数)。
%n int* (仅printf家族) 将已输出的字符数写入到对应的指针参数中,不输出任何字符。

修饰符 (与说明符组合使用)

  1. 标志 (Flags)

    标志 作用
    - 左对齐(默认是右对齐)。
    + 即使是正数,也强制显示 + 号。
    (空格) 若为正数,则在前面留一个空格。
    # o, x, X 添加前缀 0, 0x, 0X;对浮点数强制显示小数点。
    0 用前导零填充字段宽度,而非空格。
  2. 宽度 (Width)

    宽度 作用
    number 指定最小字段宽度。若实际值较短,则用空格或0填充。
    * 字段宽度由一个 int 类型的参数动态指定。
  3. 精度 (Precision)

    精度 作用
    .number 对整数,指定最少输出的数字位数;对浮点数,指定小数点后的位数;对字符串,指定最大输出的字符数。
    .* 精度由一个 int 类型的参数动态指定。
  4. 长度 (Length) - 非常重要

    长度 用于修饰整数/浮点数类型
    hh signed/unsigned char
    h short
    l long
    ll long long
    L long double
    z size_t (例如 %zu)
    t ptrdiff_t (例如 %td)
    j intmax_t / uintmax_t

scanf 专用修饰符

修饰符 作用
* 赋值抑制符。读取数据但不赋值给任何变量。例如 scanf("%d %*c %d", &a, &b); 会读取一个整数、一个字符、一个整数,但中间的字符会被忽略。
[...] 字符集。只读取 [] 中包含的字符。[^...] 表示读取不包含在 [] 中的字符。例如 scanf("%[a-zA-Z]", name); 只会读取英文字母。