Linux 标准IO编程
一、基础文件操作
1. 文件打开/关闭 (01fopen.c)
// 文件打开模式: "r"(读), "w"(写), "a"(追加)
// "r+", "w+", "a+" (读写模式)
FILE* fp = fopen("1.txt", "w");
if(NULL == fp) { // 使用 NULL == fp 防止误写为 fp = NULL
printf("fopen error\n");
return 1;
}
// 单个字符写入
fputc('h', fp); // 写入字符'h'
// 关闭文件
fclose(fp); // 必须关闭,释放资源
2. 字符读写 (02fgetc.c, 03fgetc_cp.c)
// 读取字符(返回int,因为EOF=-1)
int c = fgetc(fp);
while(1) {
c = fgetc(fp);
if(EOF == c) { // 到达文件结尾
break;
}
printf("%c", c); // 输出字符
// fputc(c, fp_dst); // 复制到目标文件
}
二、字符串读写
3. 字符串写入 (05fputs.c)
char str[100] = "hello";
fputs(str, fp); // 写入字符串(不自动加\n)
fputs("world!\n", fp); // 可以写入字面量
4. 字符串读取 (06fgets.c, 13stdin.c)
// fgets特点:读取一行,包含换行符,自动加'\0'
char buf[100] = {0};
while(1) {
if(NULL != fgets(buf, sizeof(buf), fp)) {
printf("%s", buf); // buf中包含\n
} else {
break; // 读取失败或到达文件尾
}
}
// 处理输入中的换行符
char name[50] = {0};
fgets(name, sizeof(name), stdin); // 从标准输入读取
name[strlen(name)-1] = '\0'; // 去掉换行符
三、命令行参数处理
5. 参数基础 (04argc.c)
// 示例: ./a.out 1 2 3 4 5 6
// argc = 7 (程序名 + 6个参数)
// argv[0] = "./a.out"
// argv[1] = "1"
printf("argc is %d\n", argc);
for(int i = 0; i < argc; i++) {
printf("%d %s\n", i, argv[i]);
}
6. 参数验证 (07zuoye1.c)
if(argc < 2) { // 至少需要一个文件名参数
printf("usage: ./a.out filename\n");
return 1;
}
四、文件复制方法对比
7. 逐字符复制 (03fgetc_cp.c)
// 最慢但最简单
while(1) {
int c = fgetc(fp_src);
if(EOF == c) break;
fputc(c, fp_dst);
}
8. 逐行复制 (08fget_cp.c)
// 适合文本文件
while(1) {
char str[1024] = {0};
if(NULL == fgets(str, sizeof(str), fp_src)) {
break;
}
fputs(str, fp_dst);
}
9. 块复制 (12fread_cp.c)
// 最快,适合二进制文件
char str[1024] = {0};
while(1) {
size_t ret = fread(str, 1, sizeof(str), fp_src);
if(0 == ret) { // fread返回实际读取的字节数
break;
}
fwrite(str, ret, 1, fp_dst); // 写入实际读取的字节数
}
五、结构体读写
10. 结构体定义
typedef struct {
char name[50];
int age;
char address[30];
} PER;
11. 写入结构体 (10fwrite.c)
PER per = {"zhangsan", 20, "科技二路"};
// 方式1:按结构体数量写入
fwrite(&per, sizeof(per), 1, fp); // 写入1个结构体
// 方式2:按字节写入
fwrite(&per, 1, sizeof(per), fp); // 写入sizeof(per)个字节
12. 读取结构体 (11fread.c)
PER per = {0};
// 方式1:按结构体数量读取
size_t ret = fread(&per, sizeof(per), 1, fp);
// ret = 成功读取的结构体数量(0或1)
// 方式2:按字节读取
size_t ret = fread(&per, 1, sizeof(per), fp);
// ret = 成功读取的字节数
printf("ret is %lu, name:%s age:%d addr:%s\n",
ret, per.name, per.age, per.address);
六、文件指针操作
13. 文件定位 (17fseek.c, 18ftell.c)
// 三个定位宏:
// SEEK_SET - 文件开头
// SEEK_CUR - 当前位置
// SEEK_END - 文件末尾
// 移动到文件开头后19字节处
int ret = fseek(fp, 19, SEEK_SET);
if(ret != 0) { // fseek成功返回0
fprintf(stderr, "fseek error\n");
}
// 获取文件大小
fseek(fp, 0, SEEK_END); // 移动到文件末尾
long size = ftell(fp); // 返回当前位置(即文件大小)
printf("size is %ld\n", size);
// 重置到文件开头
rewind(fp); // 相当于 fseek(fp, 0, SEEK_SET)
七、缓冲区类型
14. 行缓冲 (14linebuff.c)
// stdout默认行缓冲(大小1024字节)
// 刷新条件:
// 1. 遇到'\n'
printf("hello\n"); // 立即输出
// 2. 缓冲区满(1024字节)
for(int i = 0; i < 1024; i++) {
fputc('a', stdout); // 缓冲区满时输出
}
// 3. 程序结束
printf("hello"); // 程序结束时输出
// 4. 手动刷新
printf("hello");
fflush(stdout); // 立即输出
15. 全缓冲 (15fullbuf.c)
// 文件操作默认全缓冲(大小4096字节)
FILE* fp = fopen("1.txt", "w");
// 刷新条件:
// 1. 缓冲区满(4096字节)
for(int i = 0; i < 4096; i++) {
fputc('a', fp); // 缓冲区满时写入文件
}
// 2. 程序结束
fputs("hello", fp); // 程序结束时写入
// 3. 手动刷新
fputs("hello", fp);
fflush(fp); // 立即写入文件
// 4. 关闭文件
fclose(fp); // 会刷新缓冲区
16. 无缓冲 (16stderr.c)
// stderr默认无缓冲(立即输出)
fprintf(stderr, "fopen error"); // 立即输出错误信息
// 适合输出错误信息,确保及时看到
八、实用工具函数
17. 统计小写字母 (09count_little.c)
int a[26] = {0}; // 26个小写字母的计数数组
while(1) {
int c = fgetc(fp);
if(EOF == c) break;
if(c >= 'a' && c <= 'z') {
a[c - 'a']++; // 对应字母计数加1
}
}
// 输出结果
for(int i = 0; i < 26; i++) {
printf("%c:%d\n", i + 'a', a[i]);
}
18. 统计文件行数 (07count_lin.c)
int count = 0;
while(1) {
char str[100] = {0};
if(NULL == fgets(str, sizeof(str), fp)) {
break;
}
if('\n' == str[strlen(str)-1]) { // 判断是否有换行符
count++;
}
}
printf("line num:%d\n", count);
九、三个标准流
19. 标准流指针
stdin - FILE*,标准输入(键盘),文件描述符 0
stdout - FILE*,标准输出(屏幕),文件描述符 1
stderr - FILE*,标准错误(屏幕),文件描述符 2
// 示例:从键盘读取,输出到屏幕
fgets(name, sizeof(name), stdin);
fputs(name, stdout);
fputs(name, stderr); // 立即输出错误信息
十、最佳实践总结
20. 编程模式
// 1. 打开文件并检查
FILE* fp = fopen(filename, mode);
if(NULL == fp) {
perror("fopen error"); // 使用perror显示系统错误
return 1;
}
// 2. 错误处理使用fprintf(stderr)
fprintf(stderr, "Error: %s\n", error_msg);
// 3. 文件指针操作后检查返回值
if(ret != 0) {
fprintf(stderr, "operation failed\n");
}
// 4. 确保关闭所有打开的文件
fclose(fp);
// 5. 命令行参数验证
if(argc < expected_args) {
printf("Usage: %s <args>\n", argv[0]);
return 1;
}
21. 性能建议
-
小文件:可以使用fgetc/fputc逐字符处理
-
文本文件:使用fgets/fputs逐行处理
-
二进制文件/大文件:使用fread/fwrite块处理
-
结构体数据:使用fwrite/fread整体读写
22. 常见错误
-
忘记检查fopen返回值
-
忘记关闭文件(fclose)
-
fgets后不处理换行符
-
缓冲区大小不足导致溢出
-
混淆"r"和"w"模式