前言
在Linux C开发中,文件操作是基本功,分为标准IO(C库函数)和文件IO(系统调用)。本文重点讲解标准IO,标准IO是C标准库封装好的接口,移植性强,内部自带缓冲区。
内存读写速度快,但程序退出或者断电数据直接丢失;硬盘上的文件可以持久保存数据,但是读写速度慢。我们开发中经常需要通过文件完成数据持久化。
内存:速度快、断电丢失、空间小;
文件(外存硬盘):速度慢、断电保存、存储空间大。
Linux哲学:一切皆文件,普通文本、硬件设备、管道、套接字全部抽象成文件来操作。
一、Linux七种文件类型
使用命令 ls -l 文件名 可以查看文件的属性信息,第一位字符代表文件类型,Linux一共7种文件类型:
|
标识 |
文件类型 |
说明 |
|
b |
块设备文件 |
硬盘、SD 卡等存储设备 |
|
c |
字符设备文件 |
鼠标、键盘、显示器,按字节流访问硬件 |
|
d |
目录文件 |
对应文件夹 |
|
– |
普通文件 |
.c、.txt、可执行程序 a.out、图片视频 |
|
l |
软连接文件 |
类似 Windows 快捷方式(符号链接) |
|
s |
套接字 socket 文件 |
用于本地进程间、网络编程 |
|
p |
管道文件 |
进程间通信使用 |
二、IO分类:标准IO vs 文件IO
IO全称Input/Output输入输出。
1. 标准IO:属于C标准库函数,头文件<stdio.h>,例如printf`、scanf`、fopen,可跨平台移植,自带用户态缓冲区。
2. 文件IO:Linux内核提供系统调用,属于操作系统接口,只在Linux/Unix下使用,没有用户缓冲区。
man手册使用小技巧
man 1 xxx:查看Linux命令
man 2 xxx:查看系统调用(open等)
man 3 xxx:查看C库函数(fopen等)
标准IO操作流程
打开文件 → 读/写文件 → 关闭文件

上图:标准IO读写流程,程序通过FILE文件流指针,在内存缓冲区和硬盘文件之间交互。
FILE* 文件流指针
标准IO所有操作都是围绕`FILE*`文件流指针,FILE结构体内部维护缓冲区、文件状态等信息。
Linux程序启动,系统默认帮我们打开3个流,无需手动fopen:
|
流指针 |
含义 |
对应设备 |
|
stdin |
标准输入流 |
键盘 |
|
stdout |
标准输出流 |
终端显示屏 |
|
stderr |
标准错误流 |
终端显示屏 |
示例:putchar('a');等价于 fputc('a', stdout);
三、标准IO核心API详解
1. fopen 打开文件
FILE *fopen(const char *pathname, const char *mode);
功能:打开文件,返回文件流指针
pathname:文件路径+文件名
mode:打开模式
|
mode |
作用 |
|
"r" |
只读,文件必须存在,否则打开失败 |
|
"r+" |
读写打开,文件必须存在 |
|
"w" |
只写;不存在就创建;文件存在直接清空原有内容 |
|
"w+" |
读写;不存在创建;存在清空文件 |
|
"a" |
追加只写;不存在创建;写数据永远在文件末尾 |
|
"a+" |
读写;不存在创建;读从文件开头,写只能追加到末尾 |
返回值:成功返回FILE*指针;失败返回NULL,一定要做判空处理。
2. fclose 关闭文件
int fclose(FILE *stream);
功能:关闭已经打开的文件流
重要:打开的文件必须关闭,否则产生**文件描述符泄露**,耗尽系统资源。
返回:成功返回0;失败返回`EOF(-1)`
3. fputc:写入单个字符
int fputc(int c, FILE *stream);
c:要写入字符的ASCII码
stream:文件流指针
返回:成功返回写入字符;失败返回`EOF(-1)`
4. fgetc:读取单个字符
int fgetc(FILE *stream);
从文件读取一个字符;读到文件末尾或者出错返回`EOF(-1)`。
#include <stdio.h>
int main(int argc, const char *argv[])
{
// 判断命令行参数个数
if (argc < 3)
{
printf("Usage: ./a.out srcfile dstfile\\n");
return -1;
}
FILE *fpsrc = fopen(argv[1], "r");
FILE *fpdst = fopen(argv[2], "w");
if (NULL == fpsrc || NULL == fpdst)
{
printf("fopen error\\n");
return -1;
}
int ret = 0;
while(1)
{
ret = fgetc(fpsrc);
if(EOF == ret)
break;
fputc(ret, fpdst);
}
fclose(fpsrc);
fclose(fpdst);
return 0;
}
编译运行:./a.out test.txt bak.txt,就可以完成文件拷贝。
补充:main函数命令行参数
int main(int argc, const char *argv[])
argc:argument count,命令行参数总个数
argv:argument vector,字符串指针数组,保存每一个参数字符串
示例执行命令:./mycp 1.txt 2.txt
argc = 3
argv[0] = "./mycp" 程序本身名字
argv[1] = "1.txt" 源文件
argv[2] = "2.txt" 目标文件
注意:argv[0]永远代表程序名。
5. fputs 写字符串
int fputs(const char *s, FILE *stream);
将字符串写入文件,不会写入字符串结束符`'\\0'`**。
返回:成功返回大于等于0;失败返回EOF。
查看文件原始内容命令:od -c filename,以字符格式打印文件十六进制内容。
6. fgets 读取一行字符串
char *fgets(char *s, int size, FILE *stream);
s:读取出来数据存放的缓冲区
size:期望读取字节,**最多读取size1个字节,末尾自动补'\\0'**
遇到换行符`\\n`就结束一行读取;到达文件末尾返回NULL。
注意:fgets会把文件中的换行符`\\n`一并读到缓冲区。
四、易错点总结
1. fopen一定要判断返回NULL,防止打开失败;
2. fopen打开后必须配对fclose,防止文件描述符泄露;
3. "w"/"w+"模式打开会直接清空文件,开发不要误操作;
4. a+模式读从文件开头,写只能追加在末尾;
5. fgets最多读size1字节,会自动预留位置存放`\\0`;
6. EOF值为1,fgetc接收返回值变量要用int类型,不要用char。



