在Linux中,所有对设备和文件的操作都是使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向程序返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数,基于文件描述符的I/O操作是Linux中最常用的操作之一!
这篇文章需要C编译、Linux基础等相关知识。可以借鉴博主往期的文章,有超详细超全面的知识图谱、快捷键技巧等干货分享。
传送门:
史上最全的Linux常用命令汇总(超详细!超全面!)收藏这一篇就够了
GCC和GDB等GUN工具的使用(内附代码编译到运行的详细过程!)
概念:一组相关数据的有序集合
文件类型:
注意:操作系统不同,所支持的文件类型也不同
标准I/O由ANSI C标准定义——C库中一些定义好的用于输入和输出的一组函数
标准I/O可以通过缓冲机制减少系统调用,实现更高的效率
标准I/O中的流实际上是一个FILE结构体,用一个结构体类型来存放打开文件的相关信息,标准I/O的所有操作都是围绕FILE来惊醒操作的。
Windows
二进制流:换行符——\n
文本流: 换行符——\r\n
Linux
换行符——\n
全缓冲
当流的缓冲区无数据或无空间时才执行实际I/O操作
行缓冲
当输入和输出遇到换行符\n
时,进行I/O操作当流和一个终端关联时,就是典型的行缓冲(打印调试信息时,一定要加上换行符)
无缓冲
数据直接写入文件,流不进行缓冲
标准I/O预定义了3个流,程序运行时自动打开
标准输入流 | 0 | STDIN_FILENO | stdin |
---|---|---|---|
标准输出流 | 1 | STDOUT_FILENO | stdout |
标准错误流 | 2 | STDERR_FILENO | stderr |
下列函数可用于打开一个标准I/O流:
FILE *fopen(const char *path,const char *mode);
//path是打开文件的路径
//mode是打开方式
打开成功返回流指针,打开出错时返回NULL。
参数 | 作用 |
---|---|
r 或者rb |
以只读 方式打开,文件必须存在 |
r+ 或r+b |
以读写 方式打开文件,文件必须存在 |
w 或者wb |
以只写 方式打开文件,若文件存在则文件长度清为0,若文件不存在则创建 |
w+ 或w+b |
以读写 方式打开文件,其他同“w” |
a 或者ab |
以只写 方式打开文件,若文件不存在则创建;向文件写入的数据追加到文件末尾 |
a+ 或a+b |
以读写 方式打开文件。其他同"a" |
注意:当给定“b”参数时,表示以二进制方式打开文件,Linux下可以忽略该参数
#include
int main(int argc,char *argv[])
{
FILE *fp;
if ((fp=fopen("test.tet","r+"))==NULL)//如果没有文件路径,默认在当前文件路径下
{
printf("fopen error\n");//行缓冲,加换行符
return -1;
}
......
return 0;
}
注意:
rw-rw-rw-
)0666&~umask
)extern int errno;//存放错误号
void perror(const char*s);//perror先输出字符串s,再输出错误号对应的错误信息
char *strerror(int errno);//strerror根据错误号返回对应的错误信息
示例1
#include
int main(int argc,char *argv[])
{
FILE *fp;
if((fp=fopen("test.txt","r+"))==NULL){
perror("fopen");
return -1;
}
......
return 0;
}
输出结果示例:
fopen:No such file or directory
示例2:
#include
#include
#include
int main(int argc,char *argv[])
{
FILE *fp;
if((fp=fopen("test.txt","r+"))==NULL){
printf("fopen:%s\n",strerror(errno));
return -1;
}
......
return 0;
}
int fclose(FILE *stream);
注意:
流支持不同的读写方式:
#include
int fgetc(FILE *stream);
int getc(FILE *stream);//成功时返回读入的字符,失败返回EOF
int getchar(void);//等同于fgetc(stdin)
#include
int main(int argc,char *argv[])
{
FILE *fp;
int ch,count=0;
if((fp=fopen(argv[1],"r"))==NULL)
{
perror("fopen");
return -1;
}
while((ch==fgetc(fp))!=EOF){//流的末尾时返回EOF
count++;
}
printf("total %d毕业特色\n",count);
return 0;
}
char *gets(char *s);//一般不建议使用,没有指定缓冲区的大小,很可能引起缓冲区溢出
char *fgets(char *s,int size,FILE *stream);
示例:
#include
#define N 6
int main(int argc,char *argv[])
{
char buf[N];
fgets(buf,N,stdin);从标准输入读取
printf("%s",buf);
return 0;
}
运行以上程序发现,当键盘输入为abcd
时:
当遇到换行符时,不会继续向下读取,而是在换行符后加一个\0
键盘输入为abcdef
时:
由于只能读取N-1个字符(5个),所以只能读取a~e,最后一个补\0
。只有当下一次再调用fgets时缓存区中的剩余内容将读入到buf中。
//参数1:缓冲区的首地址;参数2:流中读取每个对象占用的大小;参数3:读取多少个对象
size_t fread(void *ptr,size_t size,size_t n,FILE *fp);
size_t fwrite(const void *ptr,size_t size,size_t n, FILE *fp);
示例:
int s[10];
if (fread(s,sizeof(int),10,fp)<0)//从fp中读取10个整形对象和整形大小到s中
{
perror("fread");
return -1;
}
struct student{
int no;
char name[8];
float score;
}s[]={{1,'yang',96},{2,'huang',99}};
fwrite(s,sizeof(struct student),2,fp);
下列函数用来输出一个字符:
int fputc(int c,FILE *stream);//参数1:要输出的字符,参数2:向那个流中输出
int putc(int c,FILE *stream);
int putchar(int c);
示例:
#include
int main(int argc,int argv[])
{
FILE *fp;
int ch;
if((fp=fopen(argv[1],"w"))==NULL){
perro("fopen");
return -1;
}
for(ch='a';ch<='z';ch++){
fputc(ch,fp);
}
return 0;
}
int puts(const char*s);
int fputs(const char *s,FILE *stream);//写入缓冲区的字符串写到流中
\n
#include
int main(int argc,int argv[])
{
FILE *fp;
char buf[]="hello world";
if((fp=fopen(argv[1],"a"))==NULL){//只写的方式打开流
perro("fopen");
return -1;
}
fputs(buf,fp);//把存放在buf中的字符串写到流中
return 0;
}
注意:输出的字符串可以包含\n,也可以不包含
#include
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
int ch;
if (argc<3)
{
printf("Usage:%s \n" ,argv[0]);
return -1;
}
if((fps=fopen(argv[1],"r"))==NULL)
{
perror("fopen src file");
return -1;
}
if((fpd=fopen(argv[2],"w"))==NULL)
{
perror("fopen dst file");
return -1;
}
while((ch=fgetc(fps))!=EOF)
{
fputc(ch,fpd);
}
fclose(fps);
fclose(fpd);
return 0;
}
#include
#define N 64
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
char buf[N];
int n;
if(argc<3)
{
printf("usage: %s \n" ,argv[0]);
return -1;
}
if((fps=fopen(argv[1],"r"))==NULL)
{
perro("fopen src file");
return -1;
}
if((fpd=fopen(argv[2],"w"))==NULL)
{
perro("fopen src file");
return -1;
}
while((n=fread(buf,1,N,fps))>0)
{
fwrite(buf,1,N,fpd);//1=sizeof(char)
}
return 0;
}
#include
int fflush(FILE *fp);
#include
int main()
{
FILE *fp;
if((fp=fopen("test.txt","w"))==NULL)
{
perror("fopen");
return -1;
}
fputc('a',fp);//会把a写到缓冲区,但是不会写入到实际文件中
fflush('a',fp);//刷新,强制刷新流并写入到文件中
while(1);
return 0;
}
#include
long ftell(FILE *stream);
long fseek(FILE *stream,long offset,int whence);//offset正时在基准点后面定位,负数时基准点前面定位
void remind(FILE *stream);
示例:文件末尾追加’a’
#include
int main()
{
FILE *fp=fopen("test.txt","r+");
fseek(fp,0,SEEK_END);//定位到文件末尾
fputc('a',fp);
return 0;
}
示例:获取文件长度
#include
int main()
{
FILE *fp;
if ((fp==fopen("test.txt","r+"))==NULL){
perror("fopen");
return -1;
}
fseek(fp,0,SEEK_END);
printf("length is %d\n",ftell(fp));
}
#include
int ferror(FILE *stream);//判断流是否结束
int feof(FILE *stream);//判断流是否结束
#include
int printf(const char *fmt,...);
int fprintf(FILE *stream,const char *fmt,...);//把一个字符串输出到指定的流中
int sprintf(char *s,const char *fmt,...);//把一个字符串输出到特定的缓冲区中
示例:指定格式“年-月-日”分别写入文件和缓冲区
#include
int main()
{
int year,month,date;
FILE *fp;
char buf[64];
year=2020;month=6;date=5;
fp=fopen("test.txt","a+");
fprintf(fp,"%d-%d-%d\n"),year,month,date);
sprintf(buf,"%d-%d-%d\n",year,month,date);//buf为缓冲区首地址
return 0;
}
不积小流无以成江河,不积跬步无以至千里。而我想要成为万里羊,就必须坚持学习来获取更多知识,用知识来改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,记得“点赞” “评论” “收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。