Linux 软件编程 - IO 编程

一、IO 核心概念:理解 "一切皆文件"

在 Linux 中,IO 操作的本质是对 "文件" 的操作 ------ 这里的 "文件" 不仅包括我们日常接触的文本 / 二进制文件,还涵盖了设备(键盘、鼠标、磁盘)、通信对象(管道、套接字)等。所有这些 "文件" 都通过统一的文件描述符或流进行管理,实现了 "屏蔽底层差异,统一操作接口" 的目标。

1.1 常见文件类型分类

不同类型的 "文件" 对应不同的 IO 场景,其标识和用途如下表所示:

文件标识 类型名称 核心用途 典型示例
b 块设备文件 按 "块" 读写,用于存储设备 硬盘、U 盘、分区
c 字符设备文件 按 "字符" 读写,用于交互设备 键盘、鼠标、终端(tty)
d 目录文件 存储文件 / 子目录的索引信息 /home、/etc 目录
- 普通文件 存储用户数据 文本文件(.txt)、二进制文件(.exe)
l 链接文件 指向其他文件的 "快捷方式" 软链接(ln -s 创建)
s 套接字文件 进程间网络通信 网络服务端 / 客户端通信
p 管道文件 本地进程间通信 匿名管道( )、命名管道

二、IO 接口分类:按需选择合适的操作方式

根据操作对象的不同,Linux IO 接口分为三类,核心差异在于是否有缓存适用场景

接口类型 操作对象 缓存特性 核心用途
标准 IO 普通文件(文本 / 二进制) 有缓存(高效) 日常文件读写(如配置文件、日志)
文件 IO 设备文件、通信文件(管道) 无缓存(实时) 硬件操作、实时数据交互
目录 IO 目录文件 无缓存 目录创建、删除、遍历(如 ls 功能)

三、标准 IO 详解:从基础到核心接口

标准 IO 由 C 标准库(<stdio.h>)提供,基于 "流(FILE*)" 实现对文件的操作,支持缓存管理、格式化读写等功能,适用于绝大多数普通文件场景。

3.1 标准 IO 的核心前提

1. 头文件

使用标准 IO 必须包含头文件:

复制代码
#include <stdio.h>
2. 普通文件的两种形式
  • ASCII 码文件 :内容由可显示的 ASCII 字符组成(如代码、文本),可通过cat直接查看;
  • 二进制文件:内容是二进制数据(如图片、音视频、压缩包),直接查看会显示乱码;
  • 注意:ASCII 码文件是特殊的二进制文件(仅包含 0~127 的 ASCII 值)。
3. 默认打开的 3 个流

程序启动时,系统会自动打开 3 个标准流,无需手动fopen

  • stdin:标准输入流(对应键盘),行缓存;
  • stdout:标准输出流(对应终端),行缓存;
  • stderr:标准错误流(对应终端),无缓存(错误信息需实时输出)。

3.2 标准 IO 的缓存机制

缓存是标准 IO 的核心优化 ------ 通过在内存中开辟缓存区,批量读写数据,减少与磁盘 / 终端的直接交互次数。标准 IO 的缓存分为三类:

缓存类型 缓存大小 刷新条件(数据写入目标) 适用场景
全缓存 4KB(默认) 1. 缓存区满;2. 调用fclose/ 程序结束;3. 手动fflush 普通文件(如.log)
行缓存 1KB(默认) 1. 缓存区满;2. 遇到\n;3. 调用fclose/ 程序结束;4. 手动fflush 终端交互(stdin/stdout
不缓存 0KB 无缓存,数据直接写入目标 错误输出(stderr

示例 :用printf("Hello")打印时,若未加\n,stdout(行缓存)不会立即输出;加上\n或调用fflush(stdout),数据才会显示到终端。

3.3 标准 IO 核心函数接口

标准 IO 提供了一套完整的文件操作函数,从 "打开 - 读写 - 关闭" 到 "定位 - 刷新",覆盖所有常见场景。以下是最常用的函数详解:

1. 文件打开:fopen
  • 原型FILE *fopen(const char *pathname, const char *mode);
  • 功能:打开指定路径的文件,并创建一个 "流(FILE*)" 用于后续操作;
  • 参数
    • pathname:文件路径(如./test.txt/home/user/data.bin);
    • mode:打开模式(决定读写权限和文件不存在时的行为),常见模式如下:
mode 读写权限 文件不存在时 文件存在时 适用场景
r 只读 报错(NULL) 打开文件 读取配置文件
r+ 读写 报错(NULL) 打开文件(不清空) 读写已存在的文件
w 只写 创建文件 清空文件内容 新建日志文件(覆盖旧内容)
w+ 读写 创建文件 清空文件内容 新建并读写文件
a 追加只写 创建文件 指针定位到文件尾 追加日志(不覆盖旧内容)
a+ 追加读写 创建文件 指针定位到文件尾 追加并读取日志
  • 返回值 :成功返回FILE*流指针;失败返回NULL(需用perror查看错误原因)。
2. 文件关闭:fclose
  • 原型int fclose(FILE *stream);
  • 功能 :关闭流,释放缓存和文件资源(必须调用,避免内存泄漏和数据丢失);
  • 参数streamfopen返回的流指针;
  • 返回值 :成功返回 0;失败返回EOF(-1)。
3. 字符读写:fgetc / fputc

适用于逐字符读写(如统计文件字符数)。

  • fgetc(读字符)

    • 原型:int fgetc(FILE *stream);
    • 功能:从流中读取一个字符(返回 ASCII 码值);
    • 返回值:成功返回字符 ASCII 码;失败 / 文件末尾返回EOF(-1);
    • 等价:getchar()fgetc(stdin)(从键盘读字符)。
  • fputc(写字符)

    • 原型:int fputc(int c, FILE *stream);
    • 功能:将字符c(ASCII 码)写入流;
    • 返回值:成功返回字符c;失败返回EOF
    • 等价:putchar(c)fputc(c, stdout)(向终端写字符)。
4. 字符串读写:fgets / fputs

适用于逐行读写(如读取配置文件的行内容)。

  • fgets(读字符串)

    • 原型:char *fgets(char *s, int size, FILE *stream);

    • 功能:从流中读取最多size-1个字符(留 1 个位置存\0),遇到\nEOF停止;

    • 关键特性:会保留输入中的\n(若一行未读完,下次继续读);

    • 示例(模拟gets功能,去掉\n):

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

    • 原型:int fputs(const char *s, FILE *stream);
    • 功能:将字符串s写入流(不自动添加\n);
    • 对比:puts(s)会自动在末尾加\n,而fputs不会。
5. 格式化读写:fscanf / fprintf

适用于结构化数据读写(如读写 "姓名 + 年龄" 这类格式化信息)。

  • fprintf(格式化写)

    • 原型:int fprintf(FILE *stream, const char *format, ...);

    • 功能:按format格式将数据写入流(类似printf,但输出到文件而非终端);

    • 示例:向test.txt写入用户信息:

      复制代码
      FILE *fp = fopen("test.txt", "w");
      fprintf(fp, "Name: %s, Age: %d\n", "ZhangSan", 20); // 写入文件
      fclose(fp);
  • fscanf(格式化读)

    • 原型:int fscanf(FILE *stream, const char *format, ...);

    • 功能:按format格式从流中读取数据(类似scanf,但输入来自文件而非键盘);

    • 返回值:成功返回匹配的参数个数;失败 / EOF 返回EOF

    • 示例:从test.txt读取用户信息:

      复制代码
      char name[32];
      int age;
      FILE *fp = fopen("test.txt", "r");
      fscanf(fp, "Name: %s, Age: %d", name, &age); // 读取数据
      printf("Read: %s, %d\n", name, age); // 输出:ZhangSan, 20
      fclose(fp);
6. 文件定位:fseek / rewind / ftell

用于调整文件读写指针的位置(如 "跳转到文件第 100 字节处读写")。

  • fseek:设置指针位置,原型:int fseek(FILE *stream, long offset, int whence);

    • offset:偏移量(正数向后移,负数向前移);
    • whence:基准位置:SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾);
    • 示例:跳转到文件开头向后 10 字节处:fseek(fp, 10, SEEK_SET);
  • rewind:将指针重置到文件开头,等价于fseek(fp, 0, SEEK_SET);

  • ftell:获取当前指针相对于文件开头的偏移量(字节数),原型:long ftell(FILE *stream);

四、标准 IO 实践案例

案例 1:文件拷贝(将 A 文件内容复制到 B 文件)

需求:从终端接收两个文件路径(源文件 A、目标文件 B),将 A 的内容拷贝到 B。

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

int main() {
    char src_path[128] = {0}; // 源文件路径
    char dest_path[128] = {0}; // 目标文件路径
    FILE *src_fp = NULL, *dest_fp = NULL;
    int ch;

    // 1. 从终端获取文件路径
    printf("请输入源文件路径:");
    fgets(src_path, sizeof(src_path), stdin);
    src_path[strlen(src_path)-1] = '\0'; // 去掉fgets保留的\n

    printf("请输入目标文件路径:");
    fgets(dest_path, sizeof(dest_path), stdin);
    dest_path[strlen(dest_path)-1] = '\0';

    // 2. 打开源文件(只读)和目标文件(只写,不存在则创建)
    src_fp = fopen(src_path, "r");
    if (src_fp == NULL) {
        perror("打开源文件失败");
        return -1;
    }

    dest_fp = fopen(dest_path, "w");
    if (dest_fp == NULL) {
        perror("打开目标文件失败");
        fclose(src_fp); // 避免内存泄漏
        return -1;
    }

    // 3. 逐字符拷贝:读源文件→写目标文件
    while ((ch = fgetc(src_fp)) != EOF) {
        fputc(ch, dest_fp);
    }

    // 4. 关闭文件
    fclose(src_fp);
    fclose(dest_fp);
    printf("文件拷贝完成!\n");

    return 0;
}

案例 2:统计文件中出现次数最多的字符

需求:从终端接收文件路径,统计文件中所有 ASCII 字符的出现次数,并输出出现最多的字符及其次数。

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

int main() {
    char file_path[128] = {0};
    FILE *fp = NULL;
    int ch;
    int count[128] = {0}; // 索引0~127对应ASCII码,值为出现次数
    int max_count = 0;
    char max_char = 0;
    int i;

    // 1. 获取文件路径
    printf("请输入文件路径:");
    fgets(file_path, sizeof(file_path), stdin);
    file_path[strlen(file_path)-1] = '\0';

    // 2. 打开文件
    fp = fopen(file_path, "r");
    if (fp == NULL) {
        perror("打开文件失败");
        return -1;
    }

    // 3. 统计字符出现次数
    while ((ch = fgetc(fp)) != EOF) {
        if (ch >= 0 && ch < 128) { // 只统计标准ASCII字符
            count[ch]++;
        }
    }

    // 4. 找到出现次数最多的字符
    for (i = 0; i < 128; i++) {
        if (count[i] > max_count) {
            max_count = count[i];
            max_char = (char)i;
        }
    }

    // 5. 输出结果
    printf("出现次数最多的字符:'%c'(ASCII:%d)\n", max_char, (int)max_char);
    printf("出现次数:%d\n", max_count);

    // 6. 关闭文件
    fclose(fp);
    return 0;
}
相关推荐
Coder_Boy_7 小时前
技术发展的核心规律是「加法打底,减法优化,重构平衡」
人工智能·spring boot·spring·重构
wdfk_prog13 小时前
[Linux]学习笔记系列 -- [drivers][input]input
linux·笔记·学习
七夜zippoe14 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
盟接之桥14 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
忆~遂愿14 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能
湘-枫叶情缘14 小时前
1990:种下那棵不落叶的树-第6集 圆明园的对话
linux·系统架构
Fcy64815 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满15 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠16 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
Gary Studio16 小时前
rk芯片驱动编写
linux·学习