频道栏目
首页 > 程序开发 > 软件开发 > C语言 > 正文
C语言基本教程 第12课:文件输入输出(IO)
2016-10-24 09:17:00         来源:NTSK13  
收藏   我要投稿
我们对文件的概念已经非常熟悉了,比如常见的 Word 文档、txt 文件、源文件等。文件是数据源的一种,最主要的作用是保存数据。

在操作系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。例如,通常把显示器称为标准输出文件,printf 就是向这个文件输出,把键盘称为标准输入文件,scanf 就是从这个文件获取数据。
常见硬件设备与文件的对应关系
文件 硬件设备
stdin 标准输入文件,一般指键盘;scanf()、getchar() 等函数默认从 stdin 获取输入。
stdout 标准输出文件,一般指显示器;printf()、putchar() 等函数默认向 stdout 输出数据。
stderr 标准错误文件,一般指显示器;perror() 等函数默认向 stderr输出数据(后续会讲到)。
stdprn 标准打印文件,一般指打印机。
我们不去探讨硬件设备是如何被映射成文件的,大家只需要记住,在C语言中硬件设备可以看成文件,有些输入输出函数不需要你指明到底读写哪个文件,系统已经为它们设置了默认的文件,当然你也可以更改,例如让 printf 向磁盘上的文件输出数据。
操作文件的正确流程为:打开文件 --> 读写文件 --> 关闭文件。文件在进行读写操作之前要先打开,使用完毕要关闭。

所谓打开文件,就是获取文件的有关信息,例如文件名、文件状态、当前读写位置等,这些信息会被保存到一个 FILE 类型的结构体变量中。关闭文件就是断开与文件之间的联系,释放结构体变量,同时禁止再对该文件进行操作。

在C语言中,文件有多种读写方式,可以一个字符一个字符地读取,也可以读取一整行,还可以读取若干个字节。文件的读写位置也非常灵活,可以从文件开头读取,也可以从中间位置读取。

文件流

在《载入内存,让程序运行起来》一文中提到,所有的文件(保存在磁盘)都要载入内存才能处理,所有的数据必须写入文件(磁盘)才不会丢失。数据在文件和内存之间传递的过程叫做文件流,类似水从一个地方流动到另一个地方。数据从文件复制到内存的过程叫做输入流,从内存保存到文件的过程叫做输出流。

文件是数据源的一种,除了文件,还有数据库、网络、键盘等;数据传递到内存也就是保存到C语言的变量(例如整数、字符串、数组、缓冲区等)。我们把数据在数据源和程序(内存)之间传递的过程叫做数据流(Data Stream)。相应的,数据从数据源到程序(内存)的过程叫做输入流(Input Stream),从程序(内存)到数据源的过程叫做输出流(Output Stream)。

输入输出(Input output,IO)是指程序(内存)与外部设备(键盘、显示器、磁盘、其他计算机等)进行交互的操作。几乎所有的程序都有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息,或者是把信息传递给外界。

我们可以说,打开文件就是打开了一个流。

在C语言中,文件操作都是由库函数来完成的,这节介绍文件的打开和关闭。

文件的打开(fopen函数)

fopen() 函数用来打开一个文件,它的原型为:
FILE *fopen(char *filename, char *mode);
filename为文件名(包括文件路径),mode为打开方式,它们都是字符串。fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个FILE类型的结构体变量中,然后将该变量的地址返回。
FILE是在stdio.h头文件中定义的一个结构体,用来保存文件信息。
如果希望接收 fopen() 的返回值,就需要定义一个 FILE 类型的指针。例如:
FILE *fp = ("demo.txt", "r");
表示以“只读”方式打开当前目录下的 demo.txt 文件,并使 fp 指向该文件,这样就可以通过 fp 来操作 demo.txt 了。fp 通常被称为文件指针。又如:
FILE *fp = fopen("D:\\demo.txt","rb");
表示以二进制方式打开 D 盘下的 demo.txt 文件,允许读和写。

打开方式(mode)有多种,见下表:

打开方式 说明
r 以只读方式打开文件,只允许读取,不允许写入。该文件必须存在。
r+ 以读/写方式打开文件,允许读取和写入。该文件必须存在。
rb+ 以读/写方式打开一个二进制文件,允许读/写数据。
rt+ 以读/写方式打开一个文本文件,允许读和写。
w 以只写方式打开文件,若文件存在则长度清为0,即该文件内容消失,若不存在则创建该文件。
w+ 以读/写方式打开文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a 以追加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF符保留)。
a+ 以追加方式打开可读/写的文件。若文件不存在,则会建立该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的EOF符 不保留)。
wb 以只写方式打开或新建一个二进制文件,只允许写数据。
wb+ 以读/写方式打开或建立一个二进制文件,允许读和写。
wt+ 以读/写方式打开或建立一个文本文件,允许读写。
at+ 以读/写方式打开一个文本文件,允许读或在文本末追加数据。
ab+ 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。

几点说明

1) 文件打开方式由r、w、a、t、b、+ 六个字符拼成,各字符的含义是:
  • r(read):读w(write):写a(append):追加t(text):文本文件,可省略不写b(banary):二进制文件+:读和写
    2) 如果没有“b”字符,文件以文本方式打开。

    3) 凡用“r”打开一个文件时,该文件必须已经存在。

    4) 在打开一个文件时,如果出错,fopen将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:
    
    
    1. if( (fp=fopen("D:\\demo.txt","rb") == NULL ){
    2. printf("Error on open D:\\demo.txt file!");
    3. getch();
    4. exit(1);
    5. }
    这段程序的意义是,如果返回的指针为空,表示不能打开D盘根目录下的 demo.txt 文件,并给出提示信息“error on open D:\\demo.txt file!”。第3行getch()的功能是从键盘输入一个字符,但不在屏幕上显示。在这里,该行的作用是等待,只有当用户从键盘敲任一键时,程序才继续执行,因此用户可利用这个等待时间阅读出错提示。敲键后执行exit(1)退出程序。

    5) 把一个文本文件读入内存时,要将ASCII码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。

    6) 标准输入文件 stdin(键盘)、标准输出文件 stdout(显示器)、标准错误文件 stderr(显示器)是由系统打开的,可直接使用。

    文件关闭(fclose函数)

    文件一旦使用完毕,应该用fclose()函数把文件关闭,以释放相关资源,避免数据丢失。fclose() 的原型为:
    int fclose(FILE *fp);
    fp 为文件指针。例如:
    fclose(fp);
    文件正常关闭时,fclose() 的返回值为0,如果返回非零值则表示有错误发生。

     

    在C语言中,读写文件比较灵活,既可以每次读写一个字符,也可以读写一个字符串,甚至是任意字节的数据(数据块)。本节介绍以字符形式读写文件。

    以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符。主要使用两个函数:fgetc()和fputc()。

    字符读取函数 fgetc

    fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符。它的原型为:
    int fgetc (FILE *fp);
    fp 为文件指针。fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF。

    EOF 是 end of file 的缩写,表示文件末尾,是在 stdio.h 中定义的宏,它的值是一个负数,往往是 -1。返回值类型之所以为 int,就是为了容纳这个负数(char不能是负数)。
    EOF 不绝对是 -1,也可以是其他负数,这要看编译器的实现。
    fgetc() 使用举例:
    
    
    1. char ch;
    2. FILE *fp = fopen("D:\\demo.txt", "r+");
    3. ch = fgetc(fp);
    表示从D:\\demo.txt文件中读取一个字符,并保存到变量ch中。

    在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节。在文件打开时,该指针总是指向文件的第一个字节。使用fgetc 函数后,该指针会向后移动一个字节,所以可以连续多次使用fgetc读取多个字符。

    注意:这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是透明的。

    【示例】在屏幕上显示 D:\\demo.txt 文件的内容。
    
    
    1. #include
    2. int main(){
    3. FILE *fp;
    4. char ch;
    5.  
    6. //如果文件不存在,给出提示并退出
    7. if( (fp=fopen("D:\\demo.txt","rt")) == NULL ){
    8. printf("Cannot open file, press any key to exit!");
    9. getch();
    10. exit(1);
    11. }
    12.  
    13. //每次读取一个字节,直到读取完毕
    14. while( (ch=fgetc(fp)) != EOF ){
    15. putchar(ch);
    16. }
    17. putchar('\n'); //输出换行符
    18. fclose(fp);
    19. return 0;
    20. }
    在D盘下创建demo.txt文件,输入任意内容并保存,运行程序,就会看到刚才输入的内容全部都显示在屏幕上。

    该程序的功能是从文件中逐个读取字符,在屏幕上显示,直到读取完毕。

    程序第14行是关键,while 循环的条件为(ch=fgetc(fp)) != EOF。fget() 每次从位置指针所在的位置读取一个字符,并保存到变量 ch,位置指针向后移动一个字节。当文件指针移动到文件末尾时,fget() 就无法读取字符了,于是返回 EOF,表示文件读取结束了。

    对EOF的说明

    EOF 本来表示文件末尾,意味着读取结束,但是很多函数在读取出错时也返回 EOF,那么当返回EOF时,到底是文件读取完毕了还是读取出错了?我们可以借助 stdio.h 中的两个函数来判断,分别是 feof() 和 ferror()。

    feof() 函数用来判断文件内部指针是否指向了文件末尾,它的原型是:
    int feof ( FILE * fp );
    当指向文件末尾时返回非零值,否则返回零值。

    ferror() 函数用来判断文件操作是否出错,它的原型是:
    int ferror ( FILE *fp );
    出错时返回非零值,否则返回零值。

    需要说明的是,文件出错是非常少见的情况,上面的示例基本能够保证将文件内的数据读取完毕。如果追求完美,也可以加上判断并给出提示:
    
    
    1. #include
    2. int main(){
    3. FILE *fp;
    4. char ch;
    5.  
    6. //如果文件不存在,给出提示并退出
    7. if( (fp=fopen("D:\\demo.txt","rt")) == NULL ){
    8. printf("Cannot open file, press any key to exit!");
    9. getch();
    10. exit(1);
    11. }
    12.  
    13. //每次读取一个字节,直到读取完毕
    14. while( (ch=fgetc(fp)) != EOF ){
    15. putchar(ch);
    16. }
    17. putchar('\n'); //输出换行符
    18.  
    19. if(ferror(fp)){
    20. puts("读取出错");
    21. }else{
    22. puts("读取成功");
    23. }
    24.  
    25. fclose(fp);
    26. return 0;
    27. }
    这样,不管是出错还是正常读取,都能够做到心中有数。

    字符写入函数fputc

    fputc 是 file output char 的所以,意思是向指定的文件中写入一个字符。调用的形式为:
    int fputc ( int ch, FILE *fp );
    ch 为要写入的字符,fp 为文件指针。fputc() 写入成功时返回写入的字符,失败时返回EOF,返回值类型为 int 也是为了容纳这个负数。例如:
    fputc('a', fp);
    或者:
    char ch = 'a';
    fputc(ch, fp);
    表示把字符 'a' 写入fp所指向的文件中。

    两点说明

    1) 被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,并将写入的字符放在文件开头。如需保留原有文件内容,并把写入的字符放在文件末尾,就必须以追加方式打开文件。不管以何种方式打开,被写入的文件若不存在时则创建该文件。

    2) 每写入一个字符,文件内部位置指针向后移动一个字节。

    【示例】从键盘输入一行字符,写入文件。
    
    
    1. #include
    2. int main(){
    3. FILE *fp;
    4. char ch;
    5.  
    6. //判断文件是否成功打开
    7. if( (fp=fopen("D:\\demo.txt","wt+")) == NULL ){
    8. printf("Cannot open file, press any key to exit!\n");
    9. getch();
    10. exit(1);
    11. }
    12.  
    13. printf("Input a string:\n");
    14. //每次从键盘读取一个字符并写入文件
    15. while ( (ch=getchar()) != '\n' ){
    16. fputc(ch,fp);
    17. }
    18. fclose(fp);
    19. return 0;
    20. }
    运行程序,输入一行字符并按回车键结束,打开D盘下的demo.txt文件,就可以看到刚才输入的内容。

    程序每次从键盘读取一个字符并写入文件,直到按下回车键,while 条件不成立,结束读取。

     

    fgetc() 和 fputc() 函数每次只能读写一个字符,速度较慢;实际开发中往往是每次读写一个字符串或者一个数据块,这样能明显提高效率。

    读字符串函数fgets

    fgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中,它的原型为:
    char *fgets ( char *str, int n, FILE *fp );
    str 为字符数组,n 为要读取的字符数目,fp 为文件指针。

    返回值:读取成功时返回字符数组首地址,也即 str;读取失败时返回 NULL;如果开始读取时文件内部指针已经指向了文件末尾,那么将读取不到任何字符,也返回 NULL。

    注意,读取到的字符串会在末尾自动添加 '\0',n 个字符也包括 '\0'。也就是说,实际只读取到了 n-1 个字符,如果希望读取 100 个字符,n 的值应该为 101。例如:

    
    
    1. #define N 101
    2. char str[N];
    3. FILE *fp = fopen("D:\\demo.txt", "r");
    4. fgets(str, N, fp);
    表示从 D:\\demo.txt 中读取100个字符,并保存到字符数组str中。

    需要重点说明的是,在读取到 n-1 个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。这就意味着,不管n的值多大,fgets() 最多只能读取一行数据,不能跨行。在C语言中,没有按行读取文件的函数,我们可以借助 fgets(),将n的值设置地足够大,每次就可以读取到一行数据。

     

点击复制链接 与好友分享!回本站首页
上一篇:C语言基本教程 第11课:预处理的介绍
下一篇:C-结构
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站