Linux文件---标准IO编程

标准I/O编程

  • 1、打开文件(流)

使用fopen()/fdopen()/freopen()函数可以打开一个文件。其中fopen()是最常用的函数,fdopen()可以指定打开文件的文件描述符和模式,freopen()除可以指定打开的文件与模式外,还可以指定特定的I/O流。
函数fopen()
需要头文件:stdio.h
函数原型:FILE *fopen(const char *path,const char *mode)
函数参数:path:要打开的文件的路径及文件名
mode:文件打开方式,见下
函数返回值:成功:指向文件的FILE类型指针
失败:NULL
以下是mode参数允许使用的取值及说明:
r或rb 以只读的方式打开文件,该文件必须存在
r+或r+b 以可读可写的方式打开文件,该文件必须存在
w或wb 以只写的方式打开文件,若文件不存在则创建该文件;若文件存在则擦除文件原始内容,从文件开头开始操作文件
w+或w+b 以可读可写的方式打开文件,若文件不存在则创建该文件;若文件存在则擦除文件原始内容,从文件开头开始操作文件
a或ab 以附加的方式打开只写文件,若文件不存在则创建该文件;若文件存在,写入的数据追加在文件尾,即文件的原始内容会被保留
a+或a+b 以附加的方式打开可读可写文件,若文件不存在则创建该文件;若文件存在,写入的数据追加在文件尾,即文件的原始内容会被保留
注意:
⒈+的作用代表操作文件的方式是只读/写/附加(无+)还是同时读写(有+)
⒉b的作用代表操作的文件是ASCII文本文件(无b)还是二进制文件(有b)

  • 2、关闭文件(流)

使用fclose()函数可以关闭一个文件,该函数将缓冲区内的所有内容写入相关文件中并回收相应的系统资源
函数fclose()
函数原型:int fclose(FILE *stream)
函数参数:stream:已打开的流指针
函数返回值:成功:0
失败:EOF

示例:打开一个文件然后关闭
写一个程序,打开一个文件,然后关闭该文件。

#include<stdio.h>
#include<stdlib.h>
int main()
{
    FILE *fp;
    if((fp = fopen("hello.txt","w"))==NULL)//打开文件,之后判断是否打开成功
    {
        perror("cannot open file");
        exit(0);
    }
    //对文件的操作
    fclose(fp);//关闭文件
}

注意:由于打开文件fopen()函数可能会失败,所以我们打开文件后通常需要判断fopen()函数是否打开文件成功。判断的方法是将fopen的结果放入if()表达式中并判断该表达式得到的结果是否为NULL(空指针)。

示例:打开文件hello.txt,使用fprintf()向hello.txt中写入”HelloWorld”。其中打开文件部分使用命令行传参。

#include<stdio.h>
#include<stdlib.h>
int main(int argc,char *argv[])
{
    FILE *fp;
    if((fp = fopen(argv[1],"w"))==NULL)//打开文件,之后判断是否打开成功
    {
        perror("cannot open file");
        exit(0);
    }
    fprintf(fp,"%s","HelloWorld\n");
    fclose(fp);//关闭文件
}
  • 3、错误输出

在刚才的示例程序与练习程序中使用了perror这个函数。perror函数可以在程序出错的时候将错误信息输出到标准错误流stderr中。由于标准错误流不使用缓冲所以可以及时显示错误信息。
函数perror()
函数原型:void perror(const char *s)
函数参数:s:在标准错误流上输出的错误信息
函数返回值:无
在标准I/O函数执行的时候,若发生了错误(例如以r或r+打开一个不存在的文件),会将错误码保存在系统变量errno中。使用perror函数可以检测errno的错误码信息并输出对应的错误信息。
注意:errno的变量声明不在stdio.h中,而是在头文件errno.h中
除了使用perror来输出错误信息,也可以使用strerror函数手动获得错误码对应的错误信息
函数perror()
需要头文件:string.h、errno.h
函数原型:char *strerror(int errno)
函数参数:errno:返回的错误码
函数返回值:错误码对应的信息

示例:不使用perror,使用strerror输出错误信息

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
    FILE *fp;
    if((fp = fopen("hello1.txt","r"))==NULL)//故意打开一个不存在的文件
    {
        printf("打开了一个不存在的文件:%s\n",strerror(errno));
        exit(0);
    }
    fclose(fp);//关闭文件
}
  • 4、按字符输入/输出

按字符输入:

函数getc()、fgetc()、getchar()
需要头文件:stdio.h
函数原型:int getc(FILE *stream)
int fgetc(FILE *stream)
int getchar(void)
函数参数:stream:输入文件流
函数返回值:成功:读到的字符
失败:EOF
getc()函数和fgetc()函数是从一个指定的流中读取一个字符,getchar()函数是从stdin中读取一个字符。

/***********有关EOF************************/
在C语言(或者更精确的说是在C标准库中)EOF表示一个文本文件的结束符(end of file),这个宏定义在头文件stdio.h中,其值为-1,因为在ASCII表的编码(0~255)中没有-1编码。EOF通常被当做文件结束的标志,还有很多的文件处理相关的函数使用EOF作为函数出错的返回值。
但是要注意的是,EOF只能作为文本文件(流)的结束符,因为若该文件(流)是二进制形式文件则会有-1的出现,因此无法使用EOF来表征文件结束。为解决这个问题,在C语言中提供了一个feof()函数,若遇到文件结尾,函数feof()返回1,否则返回0。这个函数既可以判断二进制文件也可以判断文本文件。

按字符输出:

函数putc()、fputc()、putchar()
需要头文件:stdio.h
函数原型:int putc(int c,FILE *stream)
int fputc(int c,FILE *stream)
int putchar(int c)
函数参数:c:待输出的字符(的ASCII码)
stream:输入文件流
函数返回值:成功:输出字符c
失败:EOF
putc()函数和fputc()函数是从一个指定的流中输出一个字符,putchar()函数是从stdout中输出一个字符。

示例:从文件hello.txt中读取字符然后输出到显示器上

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int c;
    FILE *fp;
    if((fp = fopen("hello.txt","r+"))==NULL)//打开文件,之后判断是否打开成功
    {
        perror("cannot open file");
        exit(0);
    }
    c = fgetc(fp);
    while(c!=EOF)
    {
        putchar(c);
        c = fgetc(fp);
    }
    fclose(fp);
    return 0;
}
  • 5、按行输入/输出

当然,如果我们每次都按字符一个一个字符操作的话,程序执行效率会大大降低。因此标准I/O中提供了按行输入/输出的操作函数。

按行输入:

函数gets()、fgets()
需要头文件:stdio.h
函数原型:char *gets(char *s)
char *fgets(char *s,int size,FILE *stream)
函数参数:s:存放输入字符的缓冲区地址
size:输入的字符串长度
stream:输入文件流
函数返回值:成功:s
失败或读到文件尾:NULL
gets()的执行逻辑是寻找该输入流的’\n’并将’\n’作为输入结束符,但是若输入流数据超过存储空间大小的话会覆盖掉超出部分的内存数据,因此gets()函数十分容易造成缓冲区的溢出,不推荐使用。而fgets()函数的第二个参数指定了一次读取的最大字符数量。当fgets()读取到’\n’或已经读取了size-1个字符后就会返回,并在整个读到的数据后面添加’\0’作为字符串结束符。因此fgets()的读取大小保证了不会造成缓冲区溢出,但是也意味着fgets()函数可能不会读取到完整的一行(即可能无法读取该行的结束符’\n’)。

按行输出:

函数puts()、fputs()
需要头文件:stdio.h
函数原型:int puts(const char *s)
int fputs(conse char *s,FILE *stream)
函数参数:s:存放输出字符的缓冲区地址
stream:输出文件流
函数返回值:成功:非负数
失败:EOF

  • 6、格式化输入/输出

刚才的fgetc()/fputc()/fgets()/fputs()以及相关的函数都是将数据作为字符类型进行操作,但是如果我们想将数字相关类型(int,float类型等)读/写文件显然是不可以的。例如我们想在某文件中写入float类型3.5,则不能使用fputc()/fputs()。这时我们可以使用我们熟悉的printf()/scanf()函数以及它们的同族函数fprintf()/fscanf()实现数据的格式化读/写。

格式化输入:

函数scanf()、fscanf()、sscanf()
需要头文件:stdio.h
函数原型:int scanf(const char *format,…);
int fscanf(FILE *fp,const char *format,…);
int sscanf(char *buf,const char *format,…);
函数参数:format:输入的格式
fp:待输入的流
buf:待输入的缓冲区
函数返回值:成功:读到的数据个数
失败:EOF

#include<stdio.h>
#include<stdlib.h>
int main(int argc, const char *argv[])
{
    FILE *fp1,*fp2;
    if((fp1=fopen("scanftest.txt","w+b"))==NULL)
    {
        perror("cannot open file");
        exit(0);
    }
    fprintf(fp1,"%d %d %d %d %s",1,2,3,4,"HelloWorld\n");
    fclose(fp1);
    /*文件写入数据完毕*/
    /*开始读取文件数据*/
    int a,b,c,d;
    char str[32];
    if((fp2=fopen("scanftest.txt","r+"))==NULL)
    {
        perror("cannot open file");
        exit(0);
    }
    fscanf(fp2,"%d%d%d%d%s",&a,&b,&c,&d,str);
    printf("a is %d\nb is %d\nc is %d\nd is %d\nstr:%s\n",a,b,c,d,str);
    fclose(fp2);
    return 0;
}
  • sscanf()与sprintf()

sscanf()与sprintf()函数的第一个参数都是字符型指针。sscanf()函数可以在字符串中读出指定格式的数据,sprintf()函数可以将数据按指定格式写入到某字符数组中。

示例:使用sscanf()函数在一个字符串中读出指定的数据

#include<stdio.h>
#include<stdlib.h>
int main(int argc, const char *argv[])
{
    FILE *fp;
    int a,b,c,d;
    if((fp=fopen("scanftest.txt","w+b"))==NULL)
    {
        perror("cannot open file");
        exit(0);
    }
    sscanf(argv[1],"%d.%d.%d.%d",&a,&b,&c,&d);
    printf("a is %d\nb is %d\nc is %d\nd is %d\n",a,b,c,d);
    fclose(fp);
    return 0;
}

编译后执行a.out 192.168.1.20,则可将ip地址(字符串)内的数据读出并写入变量a,b,c,d(int型)中。

  • 7、指定大小读/写文件

格式化输入/输出函数fscanf()/fprintf()使用比较方便,程序也简单易懂,但是fscanf()/fprintf()的读/写效率低下。一般在程序开发过程中,更多的使用fread()/fwrite()函数来一次读/写几个数据。
函数fread()
需要头文件:stdio.h
函数原型:size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
函数参数:ptr:存放读入数据的缓冲区
size:读取的每个数据项的大小(单位字节)
nmemb:读取的数据个数
stream:要读取的流
函数返回值:成功:实际读到的nmemb数目
失败:0

函数fwrite()
需要头文件:stdio.h
函数原型:size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream);
函数参数:ptr:存放写入数据的缓冲区
size:写入的每个数据项的大小(单位字节)
nmemb:写入的数据个数
stream:要写入的流
函数返回值:成功:实际写入的nmemb数目
失败:0

示例1:使用fread()函数一次性读取1000个字节的数据

#include<stdio.h>
#include<stdlib.h>
int main(int argc, const char *argv[])
{
    FILE *fp;
    char buf[1000];
    int i;
    if((fp=fopen(argv[1],"r+"))==NULL)
    {
        perror("cannot open file");
        exit(0);
    }
    fread(buf,sizeof(char),1000,fp);
    for(i=0;i<1000;i++)
    {
        putchar(buf[i]);
    }
    fclose(fp);
    return 0;
}

示例2:将一个int型数组{0,1,2,3,4,5,6,7,8,9}写入一个文件中

#include<stdio.h>
#include<stdlib.h>
int main(int argc, const char *argv[])
{
    FILE *fp;
    int a[10]={1,2,3,4,5,6,7,8,9,10};
    int i;
    if((fp=fopen(argv[1],"w+"))==NULL)
    {
        perror("cannot open file");
        exit(0);
    }
    fwrite(a,sizeof(a[0]),sizeof(a)/sizeof(a[0]),fp);
    fclose(fp);
    return 0;
}

注意:
⒈fread()函数和fwrite()函数会将流当做二进制流的形式进行读/写,因此使用fread()/fwrite()操作的文件使用vim打开可能会出现乱码情况。
⒉fread()函数结束时,无法自动判断导致fread()函数结束的原因是读取到了文件末尾还是发生了读写错误。这时需要手动判断发生的情况。可以观察最后一次fread()的返回值,或使用feof()/ferror()函数判断。

示例:使用标准I/O的fread()/fwrite()函数实现文件的复制
提示:分别打开两个文件,一个源文件一个目标文件,循环从源文件中使用fread()取出数据,然后使用fwrite()函数写入目标文件中。注意fread()函数的循环结束条件。

#include<stdio.h>
#include<stdlib.h>
#define MAX 128
int main(int argc, const char *argv[])
{
    FILE *fp1,*fp2;
    char buf[MAX];
    int n;
    if(argc<3)
    {
        printf("arguments are too few, Usage:%s <src_file> <dst_file>\n",argv[0]);
        exit(0);
    }
    if((fp1=fopen(argv[1],"r"))==NULL)
    {
        perror("cannot open file1");
        fclose(fp1);
        exit(0);
    }
    if((fp2=fopen(argv[2],"w"))==NULL)
    {
        perror("cannot open file2");
        fclose(fp2);
        exit(0);
    }
    while((n=fread(buf,sizeof(buf[0]),sizeof(buf),fp1))>0)
    {
        fwrite(buf,sizeof(buf[0]),n,fp2);
    }
    fclose(fp1);
    fclose(fp2);
    return 0;
}
  • 8、流的定位

每次使用流打开文件并对文件进行操作后,都会让操作文件数据的位置发生偏移。在打开流的时候,偏移位置为0(即文件开头),随着读写的进行,偏移位置会不断向后,每次偏移量自动增加实际读写的大小。可以使用fseek()函数和ftell()函数对当前流的偏移量进行定位操作
函数fseek()
需要头文件:stdio.h
函数原型:int fseek(FILE *stream,long offset,int whence);
函数参数:stream:要定位的流
offset:相对于基准点whence的偏移量,正数表示向前(向文件尾方向)移动,负数表示向后(向文件头方向)移动,0表示不移动
whence:基准点(取值见下)
函数返回值:成功:0,改变读写位置
失败:EOF,不改变读写位置
其中第三个参数whence的取值如下:
SEEK_SET:代表文件起始位置,数字表示为0
SEEK_CUR:代表文件当前的读写位置,数字表示为1
SEEK_END:代表文件结束位置,数字表示为2
使用fseek()函数可以定位流的读写位置,通过偏移量+基准值的计算将读写位置移动到指定位置,其中第二个参数offset的值为正时表示向后移动,为负时表示向前移动,0表示不动。

示例:读取一个文件的最后10个字节

#include<stdio.h>
#include<stdlib.h>
#define MAX 10
int main(int argc, const char *argv[])
{
    FILE *fp;
    char buf[MAX];
    int n;
    if(argc<2)
    {
        printf("arguments are too few\n");
        exit(0);
    }
    if((fp=fopen(argv[1],"r"))==NULL)
    {
        perror("cannot open file1");
        exit(0);
    }
    fseek(fp,-10,SEEK_END);
    fread(buf,sizeof(buf[0]),MAX,fp);
    for(n=0;n<MAX;n++)
    {
        putchar(buf[n]);
    }
    fclose(fp);
    return 0;
}

若想知道当前的读写位置的偏移量,则可以使用ftell()函数
函数ftell()
需要头文件:stdio.h
函数原型:int ftell(FILE *stream);
函数参数:stream:要定位的流
函数返回值:成功:返回当前的读写位置
失败:EOF
练习:使用fseek()函数和ftell()函数求一个文件的大小。
提示:先使用fseek()定位到文件末尾,再使用ftell()得到值。

#include<stdio.h>
#include<stdlib.h>
#define MAX 10
int main(int argc, const char *argv[])
{
    FILE *fp;
    if(argc<2)
    {
        printf("arguments are too few\n");
        exit(0);
    }
    if((fp=fopen(argv[1],"r"))==NULL)
    {
        perror("cannot open file1");
        exit(0);
    }
    fseek(fp,0,SEEK_END);
    printf("File is %ld\n",ftell(fp));
    fclose(fp);
    return 0;
}

可以使用ls -l指令与该程序的执行结果进行对比。

  • 9、其他常用标准I/O操作函数

①刷新缓冲区fflush()
函数fflush()
需要头文件:stdio.h
函数原型:int fflush(FILE *stream);
函数参数:stream:操作的流
函数返回值:成功:0
失败:EOF
fflush()函数会清除(原意是“冲刷”)该流内的输出缓冲区并立即将输出缓冲区的数据写回,即强迫将缓冲区内的数据写回流stream指定的文件中。若fflush()的参数为0(或NULL)则会刷新所有已经打开的流的输出缓冲区。
注意:
⒈fflush()函数可能会执行失败,当fflush()函数执行失败时会返回EOF,这可能由于缓冲区数据意外丢失或其他未知原因。因此当某些重要文件需要设置时,若使用fflush()刷新缓冲区失败,则应考虑使用文件I/O相关操作(open()、close()、read()、write()等)来代替标准I/O操作。
⒉请不要试图使用fflush()刷新stdin!
在C标准和POSIX标准中,fflush()仅定义了刷新输出流的行为,对于输入流stdin的fflush()操作是“未定义”(undefined),因此不同操作系统不同编译器对fflush(stdin)的操作都不会相同。fflush(stdin)操作只对部分编译器(例如VC++)等有效,而现在的大多数编译器(gcc等)是无效的。按C99标准规定的fflush()函数定义来说,fflush()函数是不允许刷新stdin的。
对于fflush(stdin)操作,该操作未被标准定义,行为不确定,不同系统不同编译器操作不相同(可移植性不好),因此极为不推荐使用fflush()刷新标准输入流stdin。
②判断文件是否已经结束feof()
函数feof()
需要头文件:stdio.h
函数原型:int feof(FILE *stream);
函数参数:stream:操作的流
函数返回值:文件结束:非0的值
文件未结束:0
feof()函数可以检测流上的文件结束符,若文件结束则返回一个非0值,否则返回0。
文件结束符EOF的数值是0xFF(十进制为-1),在文本文件中我们可以通过fgetc()是否读取到EOF来判断文件是否结尾(见fgetc()的示例程序)。而在二进制文件中可以有数值-1,因此就无法使用fgetc()读取EOF的方法来判断文件是否结尾。这时我们可以使用feof()函数来判断,feof()函数既可以判断文本文件又可以判断二进制文件。
feof()的常用用法有
if(feof(stream))//判断该流是否已结尾
或者
while(!feof(stream))//循环操作该流直至文件结尾
等。
③回到文件开头rewind()
函数rewind()
需要头文件:stdio.h
函数原型:void rewind(FILE *stream);
函数参数:stream:操作的流
函数返回值:无
rewind()函数会将当前读写位置返回至文件开头(rewind原意为“(磁带等)回滚,倒带”),其等价于
(void)fseek(stream, 0L, SEEK_SET)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值