云计算百科
云计算领域专业知识百科平台

Linux软件编程:IO编程(1)

📘 一、Linux IO编程核心概念

1.1 "一切皆是文件"的理念

在Linux系统中,所有输入输出操作都统一为文件操作,具体分为以下几类:

文件类型标识符说明典型设备
块设备文件 b 按块访问的存储设备 硬盘、U盘
字符设备文件 c 按字符访问的设备 键盘、鼠标、终端
目录文件 d 存储文件信息的容器 文件夹
普通文件 常规数据文件 文本、二进制文件
链接文件 l 指向其他文件的快捷方式 符号链接、硬链接
套接字文件 s 进程间通信文件 网络socket
管道文件 p 进程间通信文件 匿名/命名管道

1.2 IO操作的统一性

核心思想:无论操作什么类型的"文件",都使用相似的IO接口。

1.3 IO接口分类体系

c

Linux IO系统
├── 标准IO(带缓存)
│ ├── 适用于:普通文件
│ ├── 特点:高效、带缓冲机制
│ └── 头文件:#include <stdio.h>

├── 文件IO(无缓存)
│ ├── 适用于:设备文件、通信文件
│ ├── 特点:直接、实时
│ └── 头文件:#include <unistd.h>、#include <fcntl.h>

└── 目录IO
├── 适用于:目录文件
└── 函数:opendir()、readdir()、closedir()

📂 二、标准IO详细解析

2.1 标准IO的基本特性

2.1.1 文件类型区分
  • ASCII码文件:内容为可显示的ASCII字符

    • 示例:代码文件、配置文件、日志文件

  • 二进制文件:存储二进制数据

    • 示例:图片、音频、视频、压缩包

  • 注意:ASCII文件是二进制文件的特殊形式

2.1.2 标准IO操作流程

text

打开文件 (fopen)

读写操作 (多种函数)

关闭文件 (fclose)

2.2 默认打开的流(预定义文件指针)

流名称文件指针文件描述符缓存类型用途
标准输入 stdin 0 行缓存 键盘输入
标准输出 stdout 1 行缓存 屏幕输出
标准错误 stderr 2 无缓存 错误输出

2.3 缓存机制详解

2.3.1 全缓存(Full Buffering)
  • 缓存大小:4096字节(4KB)

  • 刷新条件:

  • 缓存区满时自动刷新

  • 调用fflush()函数强制刷新

  • 程序正常结束时自动刷新

  • 调用fclose()关闭文件时

  • 应用场景:普通文件操作

2.3.2 行缓存(Line Buffering)
  • 缓存大小:1024字节(1KB)

  • 刷新条件:

  • 遇到换行符\\n时刷新

  • 缓存区满时刷新

  • 调用fflush()函数强制刷新

  • 程序正常结束

  • 应用场景:终端输入输出(stdin/stdout)

2.3.3 无缓存(Unbuffered)
  • 缓存大小:0字节

  • 特点:立即输出,不缓存

  • 应用场景:错误输出(stderr)、实时日志

2.4 标准IO函数全集(详细参数说明)

2.4.1 文件打开/关闭函数

fopen() 函数

c

/**
* 功能:打开文件并建立流
* 参数:
* pathname:文件路径
* mode:打开模式
* 返回值:
* 成功:FILE* 指针
* 失败:NULL
*/
FILE *fopen(const char *pathname, const char *mode);

mode参数详细说明:

模式含义文件不存在时文件存在时初始位置
"r" 只读 返回NULL 打开成功 文件开头
"r+" 读写 返回NULL 打开成功 文件开头
"w" 只写 创建新文件 清空内容 文件开头
"w+" 写读 创建新文件 清空内容 文件开头
"a" 追加只写 创建新文件 保留内容 文件末尾
"a+" 追加读写 创建新文件 保留内容 文件末尾

fclose() 函数

c

/**
* 功能:关闭文件流
* 参数:
* stream:要关闭的文件流指针
* 返回值:
* 成功:0
* 失败:EOF(-1)
*/
int fclose(FILE *stream);

2.4.2 字符输入输出函数

fputc() 函数

c

/**
* 功能:向流中写入一个字符
* 参数:
* c:要写入的字符(ASCII码值)
* stream:目标文件流
* 返回值:
* 成功:写入字符的ASCII码值
* 失败:EOF(-1)
*/
int fputc(int c, FILE *stream);

// 等价关系
putchar(ch) ⇔ fputc(ch, stdout)

fgetc() 函数

c

/**
* 功能:从流中读取一个字符
* 参数:
* stream:源文件流
* 返回值:
* 成功:读取字符的ASCII码值
* 失败/文件结束:EOF(-1)
*/
int fgetc(FILE *stream);

// 等价关系
ch = getchar() ⇔ ch = fgetc(stdin)

2.4.3 字符串输入输出函数

fputs() 函数

c

/**
* 功能:向流中写入字符串
* 参数:
* s:要写入的字符串(以'\\0'结尾)
* stream:目标文件流
* 返回值:
* 成功:非负整数
* 失败:EOF(-1)
*/
int fputs(const char *s, FILE *stream);

fgets() 函数

c

/**
* 功能:从流中读取字符串
* 参数:
* s:存储读取结果的缓冲区
* size:最多读取的字符数(包括结尾的'\\0')
* stream:源文件流
* 返回值:
* 成功:缓冲区地址s
* 失败/文件结束:NULL
*/
char *fgets(char *s, int size, FILE *stream);

重要区别说明:

c

// fgets vs gets
char str[100];
fgets(str, sizeof(str), stdin); // 保留换行符'\\n'
gets(str); // 不保留换行符

// 使用fgets模拟gets的功能
fgets(str, sizeof(str), stdin);
str[strlen(str) – 1] = '\\0'; // 去掉换行符

// fputs vs puts
fputs("Hello", stdout); // 不添加换行符
puts("Hello"); // 自动添加换行符

2.4.4 格式化输入输出函数

fprintf() 函数

c

/**
* 功能:向流中写入格式化字符串
* 参数:
* stream:目标文件流
* format:格式化字符串
* …:可变参数(要输出的变量)
* 返回值:
* 成功:输出的字符数
* 失败:负数
*/
int fprintf(FILE *stream, const char *format, …);

// 示例
fprintf(stdout, "Name: %s, Age: %d\\n", name, age);
fprintf(file, "Data: %f, %f, %f\\n", x, y, z);

fscanf() 函数

c

/**
* 功能:从流中读取格式化数据
* 参数:
* stream:源文件流
* format:格式化字符串
* …:可变参数(存储读取结果的变量地址)
* 返回值:
* 成功:成功匹配并赋值的参数个数
* 失败/文件结束:EOF(-1)
*/
int fscanf(FILE *stream, const char *format, …);

// 示例
int count = fscanf(file, "%d %s %f", &id, name, &score);

2.5 文件操作完整流程示例

示例1:完整文件读写流程

c

#include <stdio.h>
#include <stdlib.h>

int main() {
FILE *fp = NULL;
char buffer[100];

// 1. 打开文件
fp = fopen("example.txt", "w+");
if (fp == NULL) {
perror("Error opening file");
return EXIT_FAILURE;
}

// 2. 写入数据(多种方式)
fputs("Hello, World!\\n", fp);
fprintf(fp, "This is line %d\\n", 2);
fputc('A', fp);
fputc('\\n', fp);

// 3. 重置文件指针到开头
rewind(fp);

// 4. 读取数据
printf("File content:\\n");
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}

// 5. 关闭文件
fclose(fp);

return EXIT_SUCCESS;
}

🔧 三、IO编程练习题详解

3.1 练习1:文件拷贝程序

要求:从终端接收两个文件路径,将第一个文件内容拷贝到第二个文件

c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
FILE *src, *dst;
int ch;

// 检查参数数量
if (argc != 3) {
fprintf(stderr, "Usage: %s <source> <destination>\\n", argv[0]);
return 1;
}

// 打开源文件
src = fopen(argv[1], "r");
if (src == NULL) {
perror("Error opening source file");
return 1;
}

// 打开目标文件
dst = fopen(argv[2], "w");
if (dst == NULL) {
perror("Error opening destination file");
fclose(src);
return 1;
}

// 拷贝内容
while ((ch = fgetc(src)) != EOF) {
fputc(ch, dst);
}

// 检查是否读取错误
if (ferror(src)) {
fprintf(stderr, "Error reading from source file\\n");
}

// 关闭文件
fclose(src);
fclose(dst);

printf("File copied successfully from %s to %s\\n", argv[1], argv[2]);
return 0;
}

编译与运行:

bash

gcc file_copy.c -o file_copy
./file_copy source.txt destination.txt

3.2 练习2:字符频率统计程序

要求:统计文件中出现次数最多的字符及其出现次数

c

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

#define ASCII_SIZE 256

int main(int argc, char *argv[]) {
FILE *fp;
int freq[ASCII_SIZE] = {0};
int ch;
int max_index = 0;

// 检查参数
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\\n", argv[0]);
return 1;
}

// 打开文件
fp = fopen(argv[1], "r");
if (fp == NULL) {
perror("Error opening file");
return 1;
}

// 统计字符频率
while ((ch = fgetc(fp)) != EOF) {
// 只统计可打印字符
if (isprint(ch)) {
freq[ch]++;
if (freq[ch] > freq[max_index]) {
max_index = ch;
}
}
}

// 显示结果
if (freq[max_index] > 0) {
printf("Most frequent character: '%c' (ASCII: %d)\\n",
max_index, max_index);
printf("Frequency: %d times\\n", freq[max_index]);

// 可选:显示所有字符频率
printf("\\nCharacter frequency table:\\n");
printf("Char | Count\\n");
printf("—–|——\\n");
for (int i = 0; i < ASCII_SIZE; i++) {
if (freq[i] > 0 && isprint(i)) {
printf(" %c | %d\\n", i, freq[i]);
}
}
} else {
printf("File is empty or contains no printable characters.\\n");
}

fclose(fp);
return 0;
}

编译与运行:

bash

gcc char_freq.c -o char_freq
./char_freq example.txt

3.3 扩展练习:带缓存的批量拷贝(优化版)

c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define BUFFER_SIZE 4096 // 4KB缓冲区,匹配全缓存大小

int main(int argc, char *argv[]) {
FILE *src, *dst;
char buffer[BUFFER_SIZE];
size_t bytes_read, total_bytes = 0;
clock_t start, end;
double cpu_time_used;

if (argc != 3) {
fprintf(stderr, "Usage: %s <source> <destination>\\n", argv[0]);
return 1;
}

start = clock();

src = fopen(argv[1], "rb"); // 二进制模式读取
if (!src) {
perror("Source file error");
return 1;
}

dst = fopen(argv[2], "wb"); // 二进制模式写入
if (!dst) {
perror("Destination file error");
fclose(src);
return 1;
}

// 使用缓冲区批量读写
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src)) > 0) {
fwrite(buffer, 1, bytes_read, dst);
total_bytes += bytes_read;
}

// 检查错误
if (ferror(src)) {
fprintf(stderr, "Error reading source file\\n");
}
if (ferror(dst)) {
fprintf(stderr, "Error writing to destination file\\n");
}

fclose(src);
fclose(dst);

end = clock();
cpu_time_used = ((double)(end – start)) / CLOCKS_PER_SEC;

printf("Copy completed successfully!\\n");
printf("Total bytes copied: %ld\\n", total_bytes);
printf("Time used: %.6f seconds\\n", cpu_time_used);
printf("Transfer rate: %.2f KB/s\\n",
total_bytes / 1024.0 / cpu_time_used);

return 0;
}

📊 四、IO编程实践技巧与注意事项

4.1 错误处理最佳实践

c

// 不好的写法
FILE *fp = fopen("file.txt", "r");
// 直接使用fp,不检查是否成功

// 好的写法
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
// 使用perror提供详细信息
perror("Error opening file");

// 或者使用strerror
fprintf(stderr, "Error: %s\\n", strerror(errno));

return EXIT_FAILURE;
}

4.2 资源管理(防止内存/文件泄漏)

c

// 安全地打开多个文件
FILE *fp1 = NULL, *fp2 = NULL, *fp3 = NULL;

fp1 = fopen("file1.txt", "r");
if (!fp1) goto cleanup;

fp2 = fopen("file2.txt", "r");
if (!fp2) goto cleanup;

fp3 = fopen("file3.txt", "w");
if (!fp3) goto cleanup;

// 正常处理逻辑…

cleanup:
// 安全关闭所有打开的文件
if (fp1) fclose(fp1);
if (fp2) fclose(fp2);
if (fp3) fclose(fp3);

4.3 缓存刷新策略

c

// 需要实时写入日志的场景
FILE *log_file = fopen("app.log", "a");
if (log_file) {
// 设置行缓存
setvbuf(log_file, NULL, _IOLBF, 0);

fprintf(log_file, "Event 1 occurred\\n");
fflush(log_file); // 立即写入,不等待缓存满

fprintf(log_file, "Event 2 occurred\\n");
fclose(log_file);
}

📝 五、总结与学习建议

5.1 核心要点回顾

  • 统一文件模型:Linux将所有I/O对象抽象为文件

  • 三层I/O体系:标准I/O(缓存)、文件I/O(无缓存)、目录I/O

  • 缓存机制:理解全缓存、行缓存、无缓存的区别与应用场景

  • 错误处理:始终检查I/O函数返回值,使用perror/strerror

  • 5.2 学习路径建议

    text

    第一阶段:基础掌握
    ├── 理解"一切皆是文件"理念
    ├── 掌握标准I/O基本函数(fopen/fclose/fputc/fgetc)
    └── 完成简单文件操作练习

    第二阶段:熟练应用
    ├── 掌握格式化I/O(fprintf/fscanf)
    ├── 理解缓存机制与刷新策略
    ├── 学习二进制文件操作
    └── 完成复杂文件处理任务

    第三阶段:高级优化
    ├── 性能分析与调优
    ├── 错误处理与资源管理
    ├── 跨平台兼容性处理
    └── 实际项目应用

    5.3 推荐练习项目

  • 实现一个简单的文本编辑器:支持打开、编辑、保存文件

  • 日志系统:实现带缓冲的日志记录,支持日志轮转

  • 配置文件解析器:读取/写入INI、JSON或XML格式配置

  • 数据格式转换工具:在不同格式(CSV、二进制、文本)间转换

  • 5.4 常见陷阱与解决方案

    问题原因解决方案
    文件末尾多出空行 fgets保留换行符 手动去除\\n
    写入内容不立即显示 缓存未刷新 使用fflush或设置无缓存
    大文件复制慢 单字符复制效率低 使用缓冲区批量复制
    二进制文件损坏 文本模式处理二进制 使用"rb"、"wb"模式
    跨平台换行符问题 Windows/Linux差异 统一处理或使用二进制模式
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux软件编程:IO编程(1)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!