【Linux C 编程】标准 IO 详解与实战:从基础接口到文件操作实战

一、标准 IO 概述

标准 IO(Standard I/O)是 C 语言提供的一套文件操作接口,封装了底层的系统调用(如 read/write),并引入缓存机制提升 IO 效率。相比系统调用,标准 IO 具有跨平台性强、使用简单、缓存优化等优势,是 Linux C 编程中文件操作的首选方式。

1.1 核心头文件

所有标准 IO 接口均定义在<stdio.h>头文件中,使用前需引入:

c

运行

复制代码
#include <stdio.h>

1.2 文件类型基础

在 Linux 系统中,文件可分为多种类型,标准 IO 可操作的核心类型包括:

表格

文件类型 标识 说明 典型场景
普通文件 - 存储数据的基础文件 文本、图片、音视频
字符设备文件 c 按字符交互的设备 键盘、终端、鼠标
块设备文件 b 按块读写的存储设备 硬盘、U 盘
目录文件 d 存放文件信息的文件夹 文件夹目录
链接文件 l 指向其他文件的引用 软链接 / 硬链接
套接字文件 s 进程间网络通信 网络编程
管道文件 p 进程间本地通信 匿名管道 / 命名管道

1.3 普通文件的两种形式

  • ASCII 码文件:内容为可显示的 ASCII 字符(如代码、文本文件),本质是特殊的二进制文件;
  • 二进制文件:存放二进制数据(如图片、音视频、压缩包),无法直接通过终端解析显示。

二、标准 IO 的缓存机制

标准 IO 通过缓存减少系统调用次数,提升效率,缓存分为三类:

表格

缓存类型 大小 刷新条件 适用场景
全缓存 4096 字节(4K) 1. 缓存区满;2. 程序结束 /fclose;3. fflush 强制刷新 普通文件(如 txt、jpg)
行缓存 1024 字节(1K) 1. 缓存区满;2. 程序结束 /fclose;3. fflush 强制刷新;4. 遇到 \n 终端(stdin、stdout)
不缓存 0 字节 无缓存,直接写入 / 读取 错误输出(stderr)、人机交互

核心说明

  • 与文件关联的流默认是全缓存;
  • 与终端关联的流(stdin/stdout)默认是行缓存;
  • 标准错误流 stderr 默认不缓存,保证错误信息即时输出。

三、标准 IO 核心接口详解

3.1 文件打开与关闭

(1)fopen:打开文件并创建流

c

运行

复制代码
FILE *fopen(const char *pathname, const char *mode);
  • 功能:打开指定路径的文件,建立文件与流的关联;
  • 参数
    • pathname:文件路径(绝对 / 相对路径);
    • mode:打开模式(核心模式如下):

表格

模式 权限 文件不存在 文件存在
r 只读 报错(返回 NULL) 只读打开
r+ 读写 报错(返回 NULL) 读写打开
w 只写 创建新文件 清空内容后只写
w+ 写读 创建新文件 清空内容后写读
a 追加只写 创建新文件 从文件末尾追加写入
a+ 追加读写 创建新文件 读:任意位置;写:末尾追加
  • 返回值:成功返回文件流指针(FILE*),失败返回 NULL(需检查 errno)。
(2)fclose:关闭文件流

c

运行

复制代码
int fclose(FILE *stream);
  • 功能:关闭文件流,刷新缓存并释放资源;
  • 返回值:成功返回 0,失败返回 EOF(-1)。

注意:打开文件后必须关闭,否则会导致资源泄漏;多次关闭同一流会触发未定义行为。

3.2 字符级读写

(1)fputc:写入单个字符

c

运行

复制代码
int fputc(int c, FILE *stream);
  • 功能:向流中写入一个字符(c 为字符的 ASCII 码);
  • 返回值:成功返回写入字符的 ASCII 码,失败返回 EOF。
(2)fgetc:读取单个字符

c

运行

复制代码
int fgetc(FILE *stream);
  • 功能:从流中读取一个字符;
  • 返回值:成功返回字符 ASCII 码,失败 / 文件末尾返回 EOF。

等价简化

c

运行

复制代码
// 从标准输入读字符
ch = getchar();  // 等价于 ch = fgetc(stdin);
// 向标准输出写字符
putchar(ch);     // 等价于 fputc(ch, stdout);

3.3 字符串级读写

(1)fputs:写入字符串

c

运行

复制代码
int fputs(const char *s, FILE *stream);
  • 功能 :向流中写入以\0结尾的字符串(不自动加 \n);
  • 返回值:成功返回非负数,失败返回 EOF。
(2)fgets:读取字符串

c

运行

复制代码
char *fgets(char *s, int size, FILE *stream);
  • 功能 :从流中读取最多size-1个字符(留 1 位存\0),遇到\n或 EOF 停止;
  • 参数
    • s:存储字符串的缓冲区;
    • size:缓冲区大小;
  • 返回值:成功返回缓冲区地址,失败 / 文件末尾返回 NULL。

fgets 与 gets 的区别

  • gets 会丢弃读取到的\n,且无缓冲区大小限制(易缓冲区溢出);
  • fgets 保留读取到的\n,且通过 size 限制读取长度(安全)。

fgets 模拟 gets 功能(去除 \n)

c

运行

复制代码
fgets(str, sizeof(str), stdin);
str[strlen(str)-1] = '\0';  // 替换末尾的\n为字符串结束符

fputs 与 puts 的区别

  • puts 会自动在字符串末尾添加\n
  • fputs 仅写入字符串本身,不额外加\n

3.4 格式化读写

(1)fprintf:格式化写入

c

运行

复制代码
int fprintf(FILE *stream, const char *format, ...);
  • 功能:按指定格式向流中写入数据(类似 printf,printf 是 fprintf (stdout, ...) 的简化);
  • 返回值:成功返回写入的字符数,失败返回负数。
(2)fscanf:格式化读取

c

运行

复制代码
int fscanf(FILE *stream, const char *format, ...);
  • 功能:按指定格式从流中读取数据(类似 scanf,scanf 是 fscanf (stdin, ...) 的简化);
  • 返回值:成功返回匹配的项数,失败 / 文件末尾返回 EOF。

3.5 块级读写(二进制操作)

(1)fwrite:按块写入二进制数据

c

运行

复制代码
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能:将 ptr 指向的内存数据,按 "每个对象 size 字节,共 nmemb 个对象" 写入流;
  • 参数
    • ptr:待写入数据的内存首地址;
    • size:单个对象的字节大小;
    • nmemb:要写入的对象个数;
  • 返回值:成功返回实际写入的对象个数,失败返回 0。
(2)fread:按块读取二进制数据

c

运行

复制代码
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能:从流中读取 "每个对象 size 字节,共 nmemb 个对象",存入 ptr 指向的内存;
  • 返回值:成功返回实际读取的对象个数,失败 / 文件末尾返回 0。

3.6 文件偏移量操作

(1)fseek:调整文件偏移量

c

运行

复制代码
int fseek(FILE *stream, long offset, int whence);
  • 功能:移动文件指针(偏移量)到指定位置;
  • 参数
    • offset:偏移量(正数向后,负数向前);
    • whence:偏移基准:
      • SEEK_SET:文件开头;
      • SEEK_CUR:当前位置;
      • SEEK_END:文件末尾;
  • 返回值:成功返回 0,失败返回 - 1。
(2)ftell:获取当前偏移量

c

运行

复制代码
long ftell(FILE *stream);
  • 功能:返回当前文件指针相对于文件开头的偏移量(字节数);
  • 应用:结合 fseek (SEEK_END) 可获取文件大小。
(3)rewind:重置偏移量到文件开头

c

运行

复制代码
void rewind(FILE *stream);
  • 等价于fseek(stream, 0, SEEK_SET);

3.7 状态判断

(1)feof:判断是否到达文件末尾

c

运行

复制代码
int feof(FILE *stream);
  • 返回值:非 0 表示到达末尾,0 表示未到达。
(2)ferror:判断流是否出错

c

运行

复制代码
int ferror(FILE *stream);
  • 返回值:非 0 表示流出错,0 表示正常;
  • 出错后可通过clearerr(stream)清除错误状态。

四、标准 IO 实战案例

案例 1:利用 fread/fwrite 实现图片文件拷贝

c

运行

编译运行

bash

运行

案例 2:输入文件路径,打印文件大小

c

运行

运行:

案例 3:替换文件中指定字符串(string1→string2)

c

运行

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUF_SIZE 4096

// 替换字符串函数
void replace_str(char *buf, const char *old_str, const char *new_str) {
    char temp[BUF_SIZE];
    char *pos;

    // 清空临时缓冲区
    temp[0] = '\0';
    // 查找旧字符串首次出现的位置
    pos = strstr(buf, old_str);

    while (pos != NULL) {
        // 复制旧字符串前的内容
        strncat(temp, buf, pos - buf);
        // 拼接新字符串
        strcat(temp, new_str);
        // 移动指针到旧字符串末尾
        buf = pos + strlen(old_str);
        // 继续查找下一个旧字符串
        pos = strstr(buf, old_str);
    }
    // 拼接剩余内容
    strcat(temp, buf);
    // 复制回原缓冲区
    strcpy(buf, temp);
}

int main(int argc, char *argv[]) {
    if (argc != 4) {
        fprintf(stderr, "用法:%s 文件名 旧字符串 新字符串\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    FILE *fp = fopen(argv[1], "r+");
    if (fp == NULL) {
        perror("打开文件失败");
        exit(EXIT_FAILURE);
    }

    char buf[BUF_SIZE];
    size_t read_len = fread(buf, 1, BUF_SIZE-1, fp);
    if (read_len == 0) {
        perror("读取文件失败");
        fclose(fp);
        exit(EXIT_FAILURE);
    }
    buf[read_len] = '\0';  // 确保字符串结束

    // 替换字符串
    replace_str(buf, argv[2], argv[3]);

    // 移动指针到文件开头,覆盖写入
    rewind(fp);
    fwrite(buf, 1, strlen(buf), fp);

    printf("字符串替换完成!\n");

    fclose(fp);
    return 0;
}

编译运行

bash

运行

复制代码
gcc -o replace replace.c
./replace test.txt "old" "new"

五、标准 IO 使用注意事项

  1. 必须检查返回值:fopen、fread、fwrite 等接口的返回值是判断操作是否成功的关键,忽略返回值易导致隐藏错误;
  2. 二进制模式打开非文本文件 :操作图片、音视频等二进制文件时,需用rb/wb模式,避免 Windows 下换行符(\n↔\r\n)转换导致数据损坏;
  3. 避免缓冲区溢出:使用 fgets、fread 时严格限制缓冲区大小,禁止使用 gets(已被废弃);
  4. 及时关闭文件流:程序退出前必须 fclose,否则缓存中的数据可能丢失;
  5. 区分 EOF 的含义:EOF(-1)是宏定义,不是文件中的实际字符,仅用于标识读取结束 / 失败。

六、总结

标准 IO 是 Linux C 编程中文件操作的核心工具,其核心优势在于缓存机制和跨平台性。掌握 fopen/fclose 的打开模式、fread/fwrite 的块操作、fseek 的偏移量控制,以及缓存机制的特性,是实现高效、稳定文件操作的关键。本文通过基础讲解 + 实战案例,覆盖了标准 IO 的核心接口和常见应用场景,可作为日常开发的参考手册。

核心要点

  • 普通文件用全缓存,终端用行缓存,错误输出无缓存;
  • 二进制文件操作必须用rb/wb模式;
  • 块级读写(fread/fwrite)是二进制文件操作的首选;
  • 所有 IO 操作必须检查返回值,避免隐藏错误。
相关推荐
Roc.Chang3 小时前
Ubuntu 下 VLC 无法启动(Segmentation fault)终极解决方案
linux·ubuntu·vlc·媒体播放
松涛和鸣3 小时前
72、IMX6ULL驱动实战:设备树(DTS/DTB)+ GPIO子系统+Platform总线
linux·服务器·arm开发·数据库·单片机
简单中的复杂4 小时前
【避坑指南】RK3576 Linux SDK 编译:解决 Buildroot 卡死在 host-gcc-final 的终极方案
linux·嵌入式硬件
wVelpro4 小时前
如何在Pycharm 2025.3 版本实现虚拟环境“Make available to all projects”
linux·ide·pycharm
程序员老舅5 小时前
C++高并发精髓:无锁队列深度解析
linux·c++·内存管理·c/c++·原子操作·无锁队列
雨中风华5 小时前
Linux, macOS系统实现远程目录访问(等同于windows平台xFsRedir软件的目录重定向)
linux·windows·macos
季明洵5 小时前
C语言实现单链表
c语言·开发语言·数据结构·算法·链表
浅念-5 小时前
C语言编译与链接全流程:从源码到可执行程序的幕后之旅
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
爱吃生蚝的于勒6 小时前
【Linux】进程信号之捕捉(三)
linux·运维·服务器·c语言·数据结构·c++·学习