Linux 学习笔记(十五):【C语言系统编程】标准IO详解与实战代码
一、 核心概念:什么是标准 IO 与“流”?
1. I/O 与内存参考点
-
I/O (Input/Output):输入输出,是以计算机的内存为参考点的。
-
如果从内存中把数据发送出去(写到文件、显示到屏幕),称为输出 (Output)。
-
如果从外部把数据写入内存(读键盘、读文件),称为输入 (Input)。
-
-
在 Linux 操作系统当中,一切皆文件,I/O 都是对文件的操作。键盘是标准输入设备(
/dev/input,对应文件描述符0),显示器是标准输出设备(对应1)。
2. 标准 IO 是什么?
-
标准 IO 是 C 标准库的一部分。任何支持标准 C 的系统都可以使用标准 IO 来实现文件存储,具有极好的跨平台兼容性。
-
底层关系:标准 IO 在 UNIX/Linux 上,其实是对文件 IO(系统调用,如
open,read,write)的封装。它通过内部维护一个结构体(即FILE结构体)来管理计算机上所有的文件,并提供了缓冲机制,效率更高。
3. 关键概念:流 (Stream)
在 C 语言中,操作文件用的都是 FILE *fp(文件流指针)。为什么叫“流”?
-
流:数据从文件当中流入和流出所体现出来的字节流,就叫做流。
-
文本流 (Text Stream):数据以字符的形式组成,通常带有换行符处理(如在某些系统中会将
\n自动转换为\r\n)。 -
二进制流 (Binary Stream):数据按原样(字节)传输,不需要任何转换。图片、音频、视频等必须使用二进制流。
二、 文件操作的“三步曲”与核心函数
文件操作必须遵循:打开文件 -> 读写操作 -> 关闭文件 (释放资源)。
1. 打开与关闭
c
FILE *fopen(const char *path, const char *mode); // 成功返回指针,失败返回 NULL int fclose(FILE *stream); // 成功返回 0,失败返回 EOF
-
常用
mode:-
r:只读(文件不存在报错)。 -
w:只写(不存在则创建,存在则清 0 覆盖)。 -
a:追加写(在文件末尾追加)。 -
加上
+表示可读可写,加上b表示二进制模式(Linux 下b和r/w无区别,但在 Windows 下不同)。
-
2. 按字符读写 fgetc / fputc
c
int fputc(int c, FILE *stream); // 成功返回写入的字符 ASCII,失败返回 EOF int fgetc(FILE *stream); // 成功返回读到的 ASCII,读到末尾返回 EOF
【注意】:fgetc 的返回值必须是 int,千万别用 char,否则在判断 EOF(通常是 -1)时容易引发死循环!
3. 按字符串读写 fgets / fputs
c
int fputs(const char *s, FILE *stream); char *fgets(char *s, int size, FILE *stream);
【注意】:fgets 遇到换行符 \n 或读够 size-1 个字符就会停止,并且它会把换行符 \n 也存入你的字符串中!
4. 自定义大小读写(二进制安全)fread / fwrite
处理结构体或大段二进制数据时专用:
c
size_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);
三、 实战代码演练
以下是我今天跑通的代码实战,按难度递增整理:
实战1:体验命令行参数 (argc 与 argv)
c
#include <stdio.h>
// 运行方式: ./a.out 1.txt 2.txt 3 4 5 (此时 argc = 6)
int main(int argc, char *argv[])
{
printf("argc:%d\n", argc);
for(int i = 0; i < argc; i++) {
printf("i:%d %s\n", i, argv[i]);
}
return 0;
}
实战2:最基础的打开、写入与关闭
c
#include <stdio.h>
int main(int argc, char **argv)
{
FILE* fp = fopen("1.txt", "w"); // "w"模式会清空原文件
if(NULL == fp) {
printf("fopen error\n");
return 1;
}
fputc('o', fp);
fputc('k', fp);
fclose(fp);
return 0;
}
实战3:单字符读取文本
c
#include <stdio.h>
int main(int argc, char **argv)
{
FILE* fp = fopen("1.txt", "r");
if(NULL == fp) return 1;
while(1) {
int c = fgetc(fp);
if(EOF == c) break; // 判断是否读到文件末尾
printf("%c", c);
}
printf("\n");
fclose(fp);
return 0;
}
实战4:单字符实现文件拷贝(文本与二进制通用)
c
#include <stdio.h>
int main(int argc, char **argv)
{
FILE* srcfp = fopen("/home/linux/1.png", "r");
FILE* dstfp = fopen("2.png", "w");
if(NULL == srcfp || NULL == dstfp) {
printf("fopen src or dst error\n");
return 1;
}
while(1) {
int c = fgetc(srcfp);
if(EOF == c) break;
fputc(c, dstfp);
}
fclose(srcfp);
fclose(dstfp);
return 0;
}
实战5:按字符串读写(体会中文字符截断)
c
#include <stdio.h>
int main(int argc, char **argv)
{
FILE* fp = fopen("1.txt", "w");
if(NULL == fp) return 1;
fputs("how are you\n", fp);
char buf[256] = "你好\n"; // UTF-8编码下,一个汉字通常占3个字节
fputs(buf, fp);
fclose(fp);
return 0;
}
【测试读取】:
c
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *fp = fopen("1.txt", "r");
if (NULL == fp) return 1;
while (1) {
char s[256] = {0}; // 每次循环都初始化为0,防止脏数据
// 注意这里 size = 5,说明每次最多读 4 个字符(留1个给 '\0')
char *ret = fgets(s, 5, fp);
if (NULL == ret) break;
printf("%s", s);
}
fclose(fp);
return 0;
}
实战6:综合应用 - 手写一个简易的 Linux cat 命令
c
#include <stdio.h>
int main(int argc, char *argv[])
{
if(argc < 2) {
printf("usage: ./a.out filename\n");
return 1;
}
FILE* fp = fopen(argv[1], "r");
if(NULL == fp) return 1;
while(1) {
char buf[256] = {0};
// 一次性读一整行(最多255字符)
if(NULL == fgets(buf, sizeof(buf), fp)) break;
printf("%s", buf);
}
fclose(fp);
return 0;
}
四、 新手易错点
在练习编写一个“统计文件中字母出现次数”的代码时,我踩了无数个雷。大家引以为戒:
[错误1] sizeof(指针) 误以为是 malloc 的大小
c
// 错误代码 char *n = malloc(256 * sizeof(char)); ret = fgets(n, sizeof(n), fp);
崩溃现场:我以为 sizeof(n) 会返回 256,结果 n 是个指针,64位系统上 sizeof(n) 永远是 8!导致 fgets 每次最多只能读 7 个字符,读取大文件时直接乱套。
正确做法:宏定义容量,如 #define BUF_SIZE 256,明确传参。
[错误2] 逻辑判断 || 写成了全选
c
// 错误代码
if('a' <= ret || ret <= 'z') {
i = ret - 'a';
s[i]++; // 数组越界!
}
崩溃现场:数学里表示区间是 a <= x <= z,但在 C 语言里必须用 &&。我写成了 ||,导致不管读进来的是大写字母、数字还是空格,条件永远为真!传入空格的 ASCII 码时,数组下标直接变成负数,程序报 Segmentation fault。
正确做法:if (ret >= 'a' && ret <= 'z')。
[错误3] malloc 放在了 while 循环里
c
// 错误代码
while(1) {
char *s = malloc(5 * sizeof(char));
// 下次循环又重新 malloc,上一块内存永久丢失!
}
free(s); // 循环外根本找不到 s 了
崩溃现场:内存泄漏。如果文件很大,程序跑着跑着内存就爆了。而且作用域错误,循环外根本 free 不掉。
正确做法:在循环外申请内存,循环内复用(或者在循环内用完立即 free)。
[错误4] 循环内数组没清空导致的“脏数据”
如果在 while 循环里使用了 char buf[256] = {0}; 没问题。但如果你把数组定义在循环外,读取较短的字符串后,残留的旧数据可能会影响下一次打印。
正确做法:每次读取前,使用 memset(buf, 0, sizeof(buf)) 清空缓冲区。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)