标准IO:字符、行 读写

标准IO:字符、行 读写

1. 文件读写的三种基本方式

C 语言标准 I/O(Standard I/O)库提供了三种主要的文件读写方式:

  • 字符方式(Character-wise):一次读/写一个字符。
  • 行方式(Line-wise):一次读/写一行文本。
  • 对象方式(Object-wise):以结构体或自定义数据块为单位进行读写,模拟“对象”操作。

注:此处的“对象”并非面向对象编程中的概念,而是指将数据视为一个整体(如 struct)进行操作。


2. 字符输入接口(Character Input)

2.1 fgetc() 函数

  • 功能:从指定流中读取一个字符。
  • 函数原型
    int fgetc(FILE *stream);
    
  • 参数FILE *stream —— 已打开的文件流(由 fopen() 返回)。
  • 返回值
    • 成功时返回读取字符的 int 类型值(实际为 unsigned char 转换而来)。
    • 遇到文件结尾(EOF)或出错时返回 EOF(通常为 -1)。
  • 关键点
    • 返回类型为 int 而非 char,是为了能表示所有可能的字节值(0–255)以及 EOF(-1)。
    • 使用前需确保文件以读模式(如 "r")打开。

2.2 getc()

  • 功能:与 fgetc() 功能相同。
  • 区别getc() 是宏(macro),而 fgetc() 是函数。
  • 用法
    int c = getc(fp); // fp 为 FILE* 类型
    

2.3 getchar() 函数

  • 功能:从标准输入(stdin)读取一个字符。
  • 等价于fgetc(stdin)getc(stdin)
  • 典型用法
    int c = getchar();
    

2.4 使用示例与注意事项

#include <stdio.h>

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

    int c;
    while ((c = fgetc(fp)) != EOF) {
        printf("%c", c);
    }
    printf("\nFgetc reached EOF normally.\n");

    fclose(fp);
    return 0;
}
  • 必须检查 EOF:用于判断是否到达文件末尾或发生错误。
  • EOF 不是字符:它是整型常量(通常为 -1),不应存入字符数组。

3. 字符输出接口(Character Output)

3.1 fputc() 函数

  • 功能:向指定流写入一个字符。
  • 原型
    int fputc(int c, FILE *stream);
    
  • 返回值
    • 成功时返回写入的字符(作为 unsigned charint)。
    • 失败时返回 EOF

3.2 putc()

  • 功能同 fputc(),但实现为宏。

3.3 putchar() 函数

  • 功能:向标准输出(stdout)写入一个字符。
  • 等价于fputc(c, stdout)

3.4 常见错误:bad file descriptor

  • 原因:文件打开模式错误。
    • 若需写入,必须使用写模式(如 "w""a""w+" 等)。
    • 若以只读模式("r")打开却尝试写入,将导致错误。
  • 解决方法:检查 fopen() 的第二个参数是否匹配操作意图。

3.5 示例

FILE *fp = fopen("test.txt", "a"); // 追加模式,保留原内容
if (fp == NULL) { /* error handling */ }

int ret = fputc('A', fp);
if (ret == EOF) {
    printf("Write failed!\n");
}
fclose(fp);
  • 使用 "w" 模式会清空原文件;"a" 模式则在末尾追加。

4. 行输入接口:fgets()

4.1 函数原型

char *fgets(char *s, int size, FILE *stream);

4.2 功能与安全机制

  • stream 中最多读取 size - 1 个字符,存入 s
  • 自动在末尾添加 \0,确保字符串安全。
  • 若遇到换行符 \n 或 EOF,则提前结束,并将 \n 保留在缓冲区中(若空间足够)。
  • 不会发生缓冲区溢出(相比危险的 gets())。

4.3 返回值

  • 成功时返回 s(即传入的缓冲区地址)。
  • 遇到 EOF 或出错时返回 NULL

4.4 关键行为分析

输入内容(含回车) size = 6 buffer 内容
ABCD\n 6 'A','B','C','D','\n','\0'
ABCDE\n 6 'A','B','C','D','E','\0'\n 被截断)
ABCDEF\n 6 第一次读:'A','B','C','D','E','\0';第二次读:'F','\n','\0',...

注意:fgets() 可能需要多次调用才能读完超长行。

4.5 示例

#define N 32
char buffer[N];
while (fgets(buffer, N, fp) != NULL) {
    printf("Read: %s", buffer); // buffer 已含 \n
}

5. 行输出接口:fputs()puts()

5.1 fputs()

  • 原型
    int fputs(const char *s, FILE *stream);
    
  • 功能:将字符串 s(不含自动 \0)写入流。
  • 不自动添加换行符 \n
  • 返回值:成功返回非负值,失败返回 EOF

5.2 puts()

  • 原型
    int puts(const char *s);
    
  • 功能:将字符串写入 stdout自动追加 \n
  • 等价于fputs(s, stdout); putchar('\n');

5.3 对比总结

函数 目标流 自动加 \n 安全性
fputs 指定流 安全
puts stdout 安全

二者均不会导致缓冲区溢出,因只输出已有字符串。


6. 实践应用案例

6.1 文件复制(字符级)

// charcopy.c
#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <src> <dst>\n", argv[0]);
        return 1;
    }

    FILE *src = fopen(argv[1], "r");
    if (!src) { perror("fopen src"); return 1; }

    FILE *dst = fopen(argv[2], "w"); // 覆盖模式
    if (!dst) {
        perror("fopen dst");
        fclose(src);
        return 1;
    }

    int c;
    while ((c = fgetc(src)) != EOF) {
        if (fputc(c, dst) == EOF) {
            perror("fputc");
            break;
        }
    }

    fclose(src);
    fclose(dst);
    printf("Copy completed.\n");
    return 0;
}

6.2 统计文件行数

#include <stdio.h>
#include <string.h>

#define N 1024

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        return 1;
    }

    FILE *fp = fopen(argv[1], "r");
    if (!fp) { perror("fopen"); return 1; }

    char buffer[N];
    int count = 0;

    while (fgets(buffer, N, fp) != NULL) {
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len - 1] == '\n') {
            count++; // 完整一行(含 \n)
        }
        // 注意:最后一行若无 \n,是否计入需根据需求决定
    }

    printf("Total lines: %d\n", count);
    fclose(fp);
    return 0;
}
  • 判断完整行:检查 buffer 末尾是否为 \n
  • 使用 strlen() 获取有效长度(不含 \0)。

7. 关键总结

  • 始终检查返回值fopenfgetcfputcfgets 等均需错误处理。
  • 正确使用文件模式
    • 读:"r"
    • 写(覆盖):"w"
    • 追加:"a"
    • 读写:"r+", "w+", "a+"
  • 避免 gets():使用 fgets() 替代,防止缓冲区溢出。
  • 资源管理:打开的文件务必 fclose(),尤其在错误路径中也要关闭已打开的文件。
  • EOF 处理fgetc() 返回 int 是为了兼容 EOF,勿用 char 接收。
  • 行处理注意fgets() 保留 \nfputs() 不添加 \n,而 puts() 会添加。
标准IO:打开关闭和错误处理 2026-02-27
标准IO:对象、格式化读写 2026-02-28

评论区