Linux下标准I/O的这些操作必须懂
在Linux中,处处皆文件。而且Linux时基于C语言开发法的,所以学习文件IO的操作非常有必要。文中主要介绍了文件IO的基本概念,一些函数的用法、原理、注意事项。以及详细介绍了文件I/O如何通过函数来操作、使用方法、注意事项,和配合详细恰当的示例来帮助理解文件IO
在Linux中,所有对设备和文件的操作都是使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向程序返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数,基于文件描述符的I/O操作是Linux中最常用的操作之一!
这篇文章需要C编译、Linux基础等相关知识。可以借鉴博主往期的文章,有超详细超全面的知识图谱、快捷键技巧等干货分享。
传送门:
史上最全的Linux常用命令汇总(超详细!超全面!)收藏这一篇就够了
GCC和GDB等GUN工具的使用(内附代码编译到运行的详细过程!)
文件基础
概念:一组相关数据的有序集合
文件类型:
- 常规文件 — r (routine)
- 目录文件 — d (directory)
- 字符设备文件 — c (char)
- 块设备文件 — b (block)
- 管道文件 — p (pipe)
- 套接字文件 — s (socket)
- 符号链接文件 — l (link)
注意:操作系统不同,所支持的文件类型也不同
标准I/O
标准I/O由ANSI C标准定义——C库中一些定义好的用于输入和输出的一组函数
标准I/O可以通过缓冲机制减少系统调用,实现更高的效率
流(stream)
标准I/O中的流实际上是一个FILE结构体,用一个结构体类型来存放打开文件的相关信息,标准I/O的所有操作都是围绕FILE来惊醒操作的。
流的分类:
Windows
二进制流:换行符——\n
文本流: 换行符——\r\n
Linux
换行符——\n
流的缓冲类型:
全缓冲
当流的缓冲区无数据或无空间时才执行实际I/O操作
行缓冲
当输入和输出遇到换行符\n
时,进行I/O操作当流和一个终端关联时,就是典型的行缓冲(打印调试信息时,一定要加上换行符)
无缓冲
数据直接写入文件,流不进行缓冲
标准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。
mode参数
参数 | 作用 |
---|---|
r 或者rb | 以只读 方式打开,文件必须存在 |
r+ 或r+b | 以读写 方式打开文件,文件必须存在 |
w 或者wb | 以只写 方式打开文件,若文件存在则文件长度清为0,若文件不存在则创建 |
w+ 或w+b | 以读写 方式打开文件,其他同“w” |
a 或者ab | 以只写 方式打开文件,若文件不存在则创建;向文件写入的数据追加到文件末尾 |
a+ 或a+b | 以读写 方式打开文件。其他同"a" |
注意:当给定“b”参数时,表示以二进制方式打开文件,Linux下可以忽略该参数
fopen示例:
#include <stdio.h>
int main(int argc,char *argv[])
{
FILE *fp;
if ((fp=fopen("test.tet","r+"))==NULL)//如果没有文件路径,默认在当前文件路径下
{
printf("fopen error\n");//行缓冲,加换行符
return -1;
}
......
return 0;
}
注意:
- fopen()创建文件访问权限是0666(
rw-rw-rw-
) - Linux系统中umask设定会影响文件的访问权限,其规则为(
0666&~umask
) - 用户可以通过umask函数修改相关设定
- 如果希望usmak不影响文件权限,给umask赋值为0就可以
处理错误信息:
extern int errno;//存放错误号
void perror(const char*s);//perror先输出字符串s,再输出错误号对应的错误信息
char *strerror(int errno);//strerror根据错误号返回对应的错误信息
示例1
#include<stdio.h>
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<stdio.h>
#include<string.h>
#include<errno.h>
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);
注意:
- fclose()调用成功返回0,失败返回EOF,并设置errno
- 流关闭时自动刷新缓冲中的数据并释放缓冲区
- 当一个程序正常终止时,所有打开的流都会被关闭
- 流关闭后就不能执行任何操作
读写流
流支持不同的读写方式:
- 读写一个字符:fgetc()/fputc()一次读/写一个字符(效率低)
- 读写一行:fgets()和fputs()一次读/写一行(适合文本文件)
- 读写若干个对象:fread()/fwrite()每次读/写若干个对象,而每个对象具有相同的长度
fgrtc——示例(读入字符):
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);//成功时返回读入的字符,失败返回EOF
int getchar(void);//等同于fgetc(stdin)
#include<stdio.h>
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);
- 成功时返回S,到文件末尾或者出错时返回NULL
- gets不推荐使用,没有指定缓冲区大小,容易造成缓冲区溢出
- 遇到‘\n’或者已输入size-1个字符时返回,总是包含‘\0’
示例:
#include <stdio.h>
#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);
- 成功时返回读写对象的个数,出错时返回EOF。
- 既可以读写文本文件,也可以读写数据文件。
示例:
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);
- 成功时返回写入的字符;出错时返回EOF
- putchar( c )等同于fput(c,stdout)
示例:
#include<stdio.h>
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);//写入缓冲区的字符串写到流中
- 成功时返回输出字符串个数;出错时返回EOF
- puts将缓冲区中的字符串输出到stdout,并追加
\n
- fputs将缓冲区s中的字符串输出到stream
fput示例
#include<stdio.h>
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,也可以不包含
利用输入流&输出流进行文件复制——fgetc&fputc
#include <stdio.h>
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
int ch;
if (argc<3)
{
printf("Usage:%s <src_file> <dst_file>\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;
}
利用输入流&输出流进行文件复制——freadc&fwritec
#include <stdio.h>
#define N 64
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
char buf[N];
int n;
if(argc<3)
{
printf("usage: %s <src_file> <dst_file>\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 <stdio.h>
int fflush(FILE *fp);
- 成功时返回0;出错时返回EOF
- 将流缓冲区中的数据写到实际文件中
- Linux下只能刷新输出缓冲区
#include <stdio.h>
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 <stdio.h>
long ftell(FILE *stream);
long fseek(FILE *stream,long offset,int whence);//offset正时在基准点后面定位,负数时基准点前面定位
void remind(FILE *stream);
- ftell()成功时返回流的当前读写位置,出错时返回EOF
- fseek()定位一个流,成功时返回0,出错时返回EOF
- whence参数:SEEK_SET/SEEK_CUR/SEEK_END
- offset参数:偏移量,可正可负
- rewind()将流定位到文件开始位置
- 读写流时,当前读写位置自动后移
示例:文件末尾追加’a’
#include<stdio.h>
int main()
{
FILE *fp=fopen("test.txt","r+");
fseek(fp,0,SEEK_END);//定位到文件末尾
fputc('a',fp);
return 0;
}
示例:获取文件长度
#include <stdio.h>
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 <stdio.h>
int ferror(FILE *stream);//判断流是否结束
int feof(FILE *stream);//判断流是否结束
- ferror()返回1表示流出错,负责返回0
- feof()返回1表示文件已经到末尾,负责返回0
格式化输出
#include <stdio.h>
int printf(const char *fmt,...);
int fprintf(FILE *stream,const char *fmt,...);//把一个字符串输出到指定的流中
int sprintf(char *s,const char *fmt,...);//把一个字符串输出到特定的缓冲区中
示例:指定格式“年-月-日”分别写入文件和缓冲区
#include <stdio.h>
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;
}
不积小流无以成江河,不积跬步无以至千里。而我想要成为万里羊,就必须坚持学习来获取更多知识,用知识来改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,记得“点赞” “评论” “收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
更多推荐
所有评论(0)