Linux系统编程:标准I/O详解

张开发
2026/4/3 22:03:30 15 分钟阅读
Linux系统编程:标准I/O详解
在Linux下一切皆文件。掌握标准I/O轻松操作文件。前言在Linux系统中文件是极其重要的概念。无论是普通文件、目录、设备还是管道、套接字都被抽象为文件。对文件的操作是系统编程的基础。C语言提供了两套文件操作接口标准I/Ostdio.h基于流stream的缓冲式文件操作跨平台使用方便。系统调用unistd.h直接调用内核提供的open、read、write等函数无缓冲更底层。本文重点讲解标准I/O包括文件的打开、读写、关闭、定位、缓冲机制等助你熟练掌握文件操作。一、Linux下的文件类型在Linux中使用ls -l命令查看文件第一个字符表示文件类型类型符含义示例-普通文件regular file文本文件、二进制文件、可执行文件d目录文件directory文件夹c字符设备文件character device键盘、摄像头、串口b块设备文件block device硬盘、U盘按块读写l符号链接文件link软链接类似于快捷方式s套接字文件socket网络通信或本地进程间通信p管道文件pipe进程间通信FIFO无论哪种文件操作逻辑是类似的打开 → 读/写 → 关闭。二、标准I/O基本概念2.1 流Stream标准I/O使用流FILE *作为操作句柄。打开一个文件后返回一个FILE *指针后续所有操作都通过它进行。操作系统在程序启动时已经默认打开了三个流流含义对应文件描述符stdin标准输入0stdout标准输出1stderr标准错误输出22.2 缓冲机制标准I/O是带缓冲的目的是减少系统调用次数提高效率。缓冲分为三类全缓冲填满缓冲区才进行实际I/O普通文件默认。行缓冲遇到换行符\n或缓冲区满时才输出终端默认。无缓冲立即输出如stderr用于错误信息即时显示。刷新缓冲的条件缓冲区满。程序正常退出return、exit。调用fflush(fp)强制刷新。行缓冲遇到\n。三、标准I/O常用函数3.1 打开文件fopen#include stdio.h FILE *fopen(const char *pathname, const char *mode);pathname要打开的文件路径。mode打开模式。模式含义说明r只读文件必须存在r读写文件必须存在w只写文件存在则截断为0不存在则创建w读写文件存在则截断不存在则创建a追加文件不存在则创建写操作从文件末尾开始a读追加读从开头写从末尾返回值成功返回FILE *失败返回NULL并设置errno。3.2 关闭文件fcloseint fclose(FILE *stream);关闭文件刷新缓冲区释放资源。成功返回0失败返回EOF。3.3 字符读写fgetc / fputcint fgetc(FILE *stream); int fputc(int c, FILE *stream);fgetc从流中读取一个字符返回字符的ASCII值。到达文件末尾或出错返回EOF。fputc将一个字符写入流成功返回写入的字符失败返回EOF。示例实现cat命令#include stdio.h ​ int main(int argc, char *argv[]) { if (argc 2) return 1; FILE *fp fopen(argv[1], r); if (fp NULL) { perror(fopen); return 1; } int ch; while ((ch fgetc(fp)) ! EOF) { putchar(ch); } fclose(fp); return 0; }3.4 行读写fgets / fputschar *fgets(char *s, int size, FILE *stream); int fputs(const char *s, FILE *stream);fgets从stream读取最多size-1个字符存入s中遇到\n或EOF停止并自动添加\0。成功返回s失败或EOF返回NULL。fputs将字符串s输出到stream不自动添加换行符。成功返回非负整数失败返回EOF。注意fgets会保留行尾的\n而gets会丢弃但gets不安全已废弃。3.5 二进制读写fread / fwritesize_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);用于读写二进制数据结构体、数组等按对象为单位。size每个元素的大小字节。nmemb元素个数。返回值实际读/写的元素个数。示例读写结构体typedef struct { char name[20]; int age; float score; } Student; ​ // 写 Student stu {张三, 20, 95.5}; FILE *fp fopen(student.dat, wb); fwrite(stu, sizeof(Student), 1, fp); fclose(fp); ​ // 读 Student stu2; fp fopen(student.dat, rb); fread(stu2, sizeof(Student), 1, fp); printf(%s %d %.1f\n, stu2.name, stu2.age, stu2.score); fclose(fp);3.6 格式化读写printf / fprintf / sprintfint printf(const char *format, ...); int fprintf(FILE *stream, const char *format, ...); int sprintf(char *str, const char *format, ...);printf输出到标准输出stdout。fprintf输出到指定文件流。sprintf输出到字符串注意缓冲区溢出风险可用snprintf替代。示例fprintf(stdout, Hello %s\n, world); // 等同于 printf char buf[100]; sprintf(buf, %d : %s, 2026, hello); puts(buf);3.7 文件定位fseek / ftell / rewindint 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(fp, 0, SEEK_END); long size ftell(fp); rewind(fp); // 或 fseek(fp, 0, SEEK_SET);创建空洞文件先偏移到文件末尾之后再写入少量数据即可形成稀疏文件。3.8 错误与文件结尾判断fgetc等函数在出错或到达文件尾时返回EOF但无法区分。需要使用专用函数int feof(FILE *stream); // 到达文件尾返回非0 int ferror(FILE *stream); // 发生错误返回非0示例int ch; while ((ch fgetc(fp)) ! EOF) { // 处理字符 } if (feof(fp)) printf(到达文件末尾\n); else if (ferror(fp)) perror(读取错误);3.9 刷新缓冲区fflushint fflush(FILE *stream);将缓冲区内容立即写入文件对于输出流。如果参数为NULL则刷新所有输出流。3.10 错误输出perrorvoid perror(const char *s);输出s字符串后跟冒号和当前errno对应的错误信息。四、综合练习练习1实现cp命令#include stdio.h #include stdlib.h int main(int argc, char *argv[]) { if (argc ! 3) { fprintf(stderr, 用法%s src dst\n, argv[0]); exit(1); } FILE *src fopen(argv[1], rb); if (src NULL) { perror(打开源文件失败); exit(1); } FILE *dst fopen(argv[2], wb); if (dst NULL) { perror(打开目标文件失败); fclose(src); exit(1); } char buf[1024]; size_t n; while ((n fread(buf, 1, sizeof(buf), src)) 0) { fwrite(buf, 1, n, dst); } fclose(src); fclose(dst); return 0; }练习2统计文件字符数、行数、单词数简易wc#include stdio.h int main(int argc, char *argv[]) { if (argc 2) return 1; FILE *fp fopen(argv[1], r); if (fp NULL) { perror(fopen); return 1; } int lines 0, words 0, chars 0; int in_word 0; int ch; while ((ch fgetc(fp)) ! EOF) { chars; if (ch \n) lines; if (ch || ch \n || ch \t) { in_word 0; } else if (!in_word) { in_word 1; words; } } printf(行数%d 单词数%d 字符数%d\n, lines, words, chars); fclose(fp); return 0; }练习3将学生信息写入文件并读取#include stdio.h typedef struct { char name[20]; int sno; float score; } Student; int main() { Student students[3] { {张三, 1001, 88.5}, {李四, 1002, 92.0}, {王五, 1003, 76.5} }; FILE *fp fopen(students.dat, wb); if (fp NULL) { perror(fopen); return 1; } fwrite(students, sizeof(Student), 3, fp); fclose(fp); fp fopen(students.dat, rb); if (fp NULL) { perror(fopen); return 1; } Student stu; while (fread(stu, sizeof(Student), 1, fp) 1) { printf(%s %d %.1f\n, stu.name, stu.sno, stu.score); } fclose(fp); return 0; }五、总结函数功能特点fopen打开文件返回FILE *fclose关闭文件刷新缓冲fgetc/fputc字符读写一次一个字符fgets/fputs字符串读写按行处理fread/fwrite二进制读写按对象fprintf/fscanf格式化读写灵活转换fseek/ftell/rewind文件定位随机访问fflush刷新缓冲强制写入feof/ferror状态检测区分结尾和错误标准I/O是Linux下文件操作的基础掌握这些函数就能处理绝大多数文件读写任务。在后续的多任务编程、网络编程中文件描述符和流的概念会贯穿始终。标准I/O让文件操作变得优雅而高效。

更多文章