在Linux中,所有对设备和文件的操作都是使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向程序返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数,基于文件描述符的I/O操作是Linux中最常用的操作之一!
       这篇文章需要C编译、Linux基础等相关知识。可以借鉴博主往期的文章,有超详细超全面的知识图谱、快捷键技巧等干货分享。
       传送门:
       史上最全的Linux常用命令汇总(超详细!超全面!)收藏这一篇就够了
       GCC和GDB等GUN工具的使用(内附代码编译到运行的详细过程!)

文件基础

       概念:一组相关数据的有序集合
       文件类型

  1. 常规文件 — r (routine)
  2. 目录文件 — d (directory)
  3. 字符设备文件 — c (char)
  4. 块设备文件 — b (block)
  5. 管道文件 — p (pipe)
  6. 套接字文件 — s (socket)
  7. 符号链接文件 — 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个流,程序运行时自动打开

标准输入流0STDIN_FILENOstdin
标准输出流1STDOUT_FILENOstdout
标准错误流2STDERR_FILENOstderr

打开流

下列函数可用于打开一个标准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
  • 流关闭时自动刷新缓冲中的数据并释放缓冲区
  • 当一个程序正常终止时,所有打开的流都会被关闭
  • 流关闭后就不能执行任何操作

读写流

流支持不同的读写方式:

  1. 读写一个字符:fgetc()/fputc()一次读/写一个字符(效率低)
  2. 读写一行:fgets()和fputs()一次读/写一行(适合文本文件)
  3. 读写若干个对象: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;
}

       不积小流无以成江河,不积跬步无以至千里。而我想要成为万里羊,就必须坚持学习来获取更多知识,用知识来改变命运,用博客见证成长,用行动证明我在努力。
       如果我的博客对你有帮助、如果你喜欢我的博客内容,记得“点赞” “评论” “收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
在这里插入图片描述

Logo

更多推荐