C输入输出
文件的输入与输出
当C语言的程序执行完毕后,所有存储在内存中的数据都会消失,如果需要将运行结果存储在不会消失的存储介质上(如硬盘等),就必须通过文件的方式保存。 在C语言中,数据流(Stream)的主要作用是作为程序与周边设备的数据传输通道,文件的处理正是通过数据流的方式存取数据。 C语言的文件处理函数主要分为两类:有缓冲区的输入与输出、无缓冲区的输入与输出。
缓冲区简介
“缓冲区”(Buffer)就是在程序执行时所提供的额外内存,可用来暂时存放准备处理的数据。缓冲区的设置是出于存取效率的考虑,因为内存的访问速度比硬盘驱动器快得多。有无缓冲区的差别在于输入输出时的操作。 两者的差别在于读写过程是否先经过缓冲区。有缓冲区的输入与输出在读取函数执行时会先到缓冲区检查是否有符合的数据;当写入函数写入时,也会先将数据写至缓冲区。
无缓冲区的输入与输出在执行相关函数时会直接将数据输入与输出至文件或设备上。 如果使用标准I/O函数(包含在stdio.h头文件中)进行输入和输出,系统就会自动设置缓冲区。在进行文件读取时,其实并不会直接对硬盘进行存取,而是先打开数据流,将硬盘上的文件信息放置到缓冲区,程序再从缓冲区中读取所需的数据。
fopen()函数与fclose()函数
在进行文件操作与管理之前,大家必须先了解C语言中通过FILE类型的指针操作文件的开关和读写。FILE是一种指针类型,声明方式如下:
FILE *stream;
FILE所定义的指针变量用来指向当前stream的位置,所以C语言中有关文件输入输出的函数多数必须搭配声明此数据类型。
接着进行文件的存取,首先必须打开数据流,进行打开文件的操作。也就是说,所有文件的读写操作都必须先打开文件。打开文件的语句如下:
FILE * fopen ( const char * filename const char * mode );
- Filename:指定文件名。
- mode:打开文件的模式,文件打开模式的字符串在文本文件的存取上主要以6种模式为主
文件处理完毕后,最好记得关闭文件。当我们使用fopen()打开文件后,文件数据会先复制到缓冲区中,而我们所下达的读取或写入操作都是针对缓冲区进行存取而不是针对硬盘,只有在使用fclose()关闭文件时,缓冲区中的数据才会写入硬盘中。
也就是说,执行完文件的读写后,明确地通过fclose()关闭活动的文件才不会发生文件被锁定或者数据记录不完整的情况。文件关闭语句如下:
int fclose ( FILE * stream );
- Stream:指向数据流对象的指针。 当数据流被正确关闭时,返回数值为0;如果数据流关闭错误,就引发错误或者返回EOF。 EOF(End Of File)是表示数据结尾的常数,值为-1,定义在stdio.h头文件中。
下面的范例程序用于示范fopen()函数与fclose()函数的用法,也就是通过判断指针变量是否为NULL来确认文件是否存在。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main ()
05 {
06 FILE * pFile; /*声明一个文件指针类型的变量,变量名称为pFile*/
07
08 pFile = fopen ("fileIO.txt","r"); /* 以读取模式打开文件 */
09 if (pFile!=NULL){ /* 当指针不为Null时 */
10 printf("文件读取成功\n"); /* 表示读取成功 */
11 fclose (pFile); /* 打开成功后记得关闭 */
12 }
13 else
14 printf("文件读取失败\n"); /* 当指针为Null时,表示失败 */
15
16 system("pause");
17 return 0;
18 }
- 第6行:声明一个文件指针类型的变量,变量名称为pFile。
- 第8行:以读取模式打开文件。
- 第9行:通过判断指针变量是否为NULL来确认文件是否存在。
- 第11行:打开文件后,在程序结束前应通过fclose()函数关闭文件。
putc()函数与getc()函数
如果想将字符逐一写入文件中,就可以使用putc()函数。若写入字符失败,则返回EOF,否则返回所写入的字符。putc()函数只会将参数中的字符写入数据流,一次一个字符。其语句如下:
int putc (int character, FILE * stream );
- Character:字符代表的ASCII码。
- Stream:指向数据流对象的指针。
下面的范例程序用于示范使用putc()函数写入文件,写入字符的ASCII为65,代表英文字母A,程序执行完毕后可打开fileIO.txt查看结果。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main ()
05 {
06 FILE * pFile; /*声明一个文件指针类型的变量,变量名称为pFile */
07
08 pFile = fopen ("fileIO.txt","w"); /*写入模式打开文件*/
09 if (pFile!=NULL)
10 {
11 putc (65,pFile); /*写入一个字符,ASCII为65 */
12 fclose (pFile);
13 printf ("字符写入成功\n") ;
14 }
15
16 system("pause");
17 return 0;
18 }
- 第6行:声明一个文件指针类型的变量,变量名称为pFile。
- 第8行:以写入模式打开文件。
- 第11行:用putc()函数写入一个字符,ASCII为65。
getc()函数可从文件数据流中一次读取一个字符,然后将读取指针移动至下一个字符,读取字符正常时,返回该字符的ASCII码。读取完后指针会指向下一个地址,并且逐步将文件内容读出。当需要更有效率地读取或处理数据时,可使用此函数。其语句如下:
int getc ( FILE * stream ); // stream:指向数据流对象的指针。
下面的范例程序示范使用一个循环与getc()函数,每次读取字符后,通过printf()函数将字符打印出来。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main ()
05 {
06 FILE * pFile; /*声明一个文件指针类型的变量,变量名称为pFile*/
07 int i;
08 char c;
09
10 pFile = fopen ("fileIO.txt","r"); /*以读取模式打开文件*/
11 if (pFile!=NULL)
12 {
13 while ( c != EOF){
14 c = getc (pFile);
15 printf ("%c",c);
16 }
17 printf ("\n");
18
19 fclose (pFile); /*关闭文件 */
20 printf ("字符读取成功\n") ;
21 }
22
23 system("pause");
24 return 0;
25 }
- 第6行:声明一个文件指针类型的变量,变量名称为pFile。
- 第10行:以读取模式打开文件。
- 第13~16行:使用while循环与EOF值进行条件判断,并逐字读出文件中的数据。
fputs()函数与fgets()函数
我们也可以使用fputs()函数将整个字符串写入文件中,使用格式如下:
fputs("写入字符串", 文件指针变量);
例如:
File *fptr;
char str[20];
…..
fputs(str,fptr);
下面的范例程序示范使用fputs()函数写入数据至文件中,并以添加模式打开文件。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main ()
05 {
06 FILE * pFile; /*声明一个文件指针类型的变量,变量名称为pFile */
07
08 pFile = fopen ("fileIO.txt","a"); /*以添加模式打开文件*/
09 if (pFile!=NULL)
10 {
11 fputs("添加数据",pFile); /*把数据写入指针变量指向的位置 */
12 fclose(pFile); /*关闭文件 */
13 printf("文件添加成功\n") ;
14 }
15
16 system("pause");
17 return 0;
18 }
- 第6行:声明一个文件指针类型的变量,变量名称为pFile。
- 第8行:以添加模式打开文件。
- 第11行:用fputs()函数把数据写入指定的文件中。
如果要读取文件中的一个字符串,就可以使用fgets()函数,使用格式如下:
fgets("读出字符串", 字符串长度,文件指针变量);
例如:
File *fptr;
char str[20];
int length;
…
fgets(str,length,fptr);
其中,str是字符串读取之后的暂存区;length是读取的长度,单位是字节。fgets()函数所读入的length有两种情况,一种是读取指定length-1的字符串,因为最后必须加上结尾字符(\0);另一种是当length-1的长度包括换行字符\n或EOF字符时,只能读取到这些字符为止。
fprintf()函数与fscanf()函数
除了单纯以字符或字符串方式写入文件外,也可以像使用printf()与scanf()函数一样,将要写入的数据以特定格式写入文件中,这些格式存取函数就是fprintf()与fscanf()函数。
首先,介绍写入文件的fprintf()函数,与printf()函数的不同处在于printf()函数输出到屏幕上,而fprintf()函数可指定输出到特定的数据流对象中。其语句如下:
int fprintf ( FILE * stream, const char * format, ... );
- stream:指向数据流对象的指针。
- format:格式化的字符串,同printf()函数一致。
例如:
File *fptr;
int math,eng;
float average;
fprintf(fptr, "%d\t%d\t%f\n",math,enf,average);
fscanf()函数与scanf()函数也相当类似,只是scanf()函数是从用户的键盘输入取得数据,而fscanf()函数是从文件中读取所指定的数据,也就是从数据流读取数据。通过此函数设置好参数,反向将数据引用(reference)到指定的变量中。其语句如下:
int fscanf ( FILE * stream, const char * format, ... );
- stream:指向数据流对象的指针。
- Format:格式化字符串
例如:
File *fptr;
int math,eng;
float average;
fprintf(fptr, "%d\t%d\t%f\n",&math,&enf,&average);
下面的范例程序示范简单使用格式化写入函数将特定数据写入文件,再由fscanf()函数通过变量指针把数据流中特定类型的数据读出。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main ()
05 {
06 FILE *pFile;
07 int length=10,width=5,height=30;
08 /*变量声明*/
09 pFile=fopen("fileIO.txt","w+");
10 /*以读取或写入模式打开文件*/
11 if(pFile != NULL){
12
13 fprintf(pFile,"%d %d %d",length,width,height);
14 /*写入数据*/
15 fscanf(pFile,"%d %d %d",&length,&width,&height);
16 /*读取数据*/
17 printf("长: %d \n宽: %d \n高: %d",length,width,height);
18
19 fclose(pFile);
20 }else
21 printf("fileIO.txt打开有误");
22
23 system("pause");
24 return 0;
25 }
- 第6行:声明一个文件指针类型的变量,变量名称为pFile。
- 第9行:以读取或写入模式打开文件。
- 第13行:用fprintf()函数写入数据。
- 第15行:用scanf()函数读取数据。
fwrite()函数与fread()函数
也可以使用区块的方式将数据写入数据流,这样适合大范围写入数据。当准备写入数据流的数据范围是一个内存区块时,通过此函数可方便定义写入的范围。例如,数据可能先存储在变量、数组或结构中,使用fwrite()函数时就可将变量、数组或结构的内存地址传送给它。其语句如下:
size_t fwrite ( const void * p, size_t s, size_t c, FILE * stream );
- p:数据区块的指针。
- s:每个元素的数据大小。
- c:总数据量。
- stream:指向数据流对象的指针。
例如:
File *fptr;
char str[20];
int count;
fwrite(str,sizeof(char),count,fptr);
下面的范例程序介绍如何使用区块指针方式将数据写入数据流,我们将使用fwrite()函数将数据写入数据流对象。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06 FILE * pFile;
07 char buffer[10];/*声明字符数组*/
08
09 pFile = fopen ( "fileIO.txt" , "w" );
10 if(pFile!=NULL){
11 printf("请输入您的出生年月日(yyyy/MM/dd)?");
12 gets(buffer);/*使用gets()函数取得用户输入的数据*/
13 fwrite (buffer , 1 , sizeof(buffer) , pFile );
14 /*以区块方式写入数据*/
15 fclose (pFile);
16 }
17
18 system("pause");
19 return 0;
20 }
- 第6行:声明一个文件指针类型的变量,变量名称为pFile。
- 第9行:以写入模式打开文件。
- 第12行:使用gets()函数取得用户输入的数据。
- 第13行:使用fwrite()函数以区块方式写入数据。
如果想读取fwrite()函数所写入的数据内容,就必须采取fread()函数读取文件,这样才能正确读出有意义的信息。也就是从数据流对象中将数据读入指定的内存区块,当需要以区块方式读取数据流数据至内存时,就可以使用此函数。其语句如下:
size_t fread ( void * p, size_t s, size_t c, FILE * stream );
- p:数据区块的指针。
- s:每个元素的数据大小。
- c:总数据量。
- stream:指向数据流对象的指针。
下面的范例程序用于示范当fread()函数读取数据流时直接指定字符类型指针以存放读入的数据。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06 FILE *pFile;
07 char c[30];
08
09 int n;
10
11 pFile = fopen("fileIO.txt", "r");
12 if(pFile!=NULL) {
13 printf("文件打开成功\n");
14
15 n = fread(c, 1, 10, pFile); /*读取数据流数据,填入指定的指针。 */
16 c[n] = '\0'; /*设置最后一个字符为\0 */
17
18 printf("%s\n", c); /*打印出字符串 */
19 printf("读出的字符数: %d\n\n", n);
20
21 fclose(pFile); /*关闭数据流 */
22
23 system("pause");
24 return 0;
25 }else {
26 printf("文件打开错误\n");
27 return 1;
28 }
29 }
- 第6行:声明一个字符类型的指针变量。
- 第7行:声明固定长度的字符数组。
- 第15行:以fread()函数返回值表示读取数据的长度。
- 第16行:在字符串数组中加入NULL字符表示结束。
fseek函数与rewind()函数
每次使用文件存取函数,文件读取指针都会往下一个位置移动。例如,使用fgetc()函数读取完毕后会移动一个字节,而在fgets()函数中,由于length长度为10,因此一次会读取9个字节长度(因为最后一个字节必须填入\0),这种读取方式称之为顺序式读取。其实,在文件中可以指定文件读取指针的位置,从文件中任意位置读出或写入数据时,可以借助fseek()函数操作读取指针。其语句如下:
int fseek ( FILE * stream, long int os, int o );
- Stream:指向数据流对象的指针。
- os:位移量。
- o:开始的位置。
位移量的单位是字节,是由指针起始点向前或向后的位移量。起点参数是指针起始点设置位移量的计算起点,共有3种宏常数
例如:
File *fptr;
fseek(fptr,10,SEEK_SET); /* 从文件开头向后计算10个字节 */
fseek(fptr,10,SEEK_CUR); /* 从当前的指针位置向后计算10个字节 */
fseek((fptr,10,SEEK_END); /* 从文件尾端向前计算10个字节 */
下面的范例程序用来示范fseek()函数的基本声明与用法,使用fseek()函数每次跳跃3个间格读取已创建完成的文件。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06 FILE *pFile;
07 int i;
08 char c;
09
10 pFile = fopen("fileIO.txt", "w");
11 if(pFile!=NULL) {
12 for(i='A';i<='Z';i++){
13 putc(i,pFile);
14 } /*在fileIO.txt文件中创建A~Z的数据*/
15
16 fclose(pFile);
17 }
18
19 pFile = fopen("fileIO.txt", "r");
20 if(pFile!=NULL) {
21 for(i=1;i<=5;i++) {
22 c=getc(pFile) ;
23 printf("%c",c) ;
24 fseek(pFile,3,SEEK_CUR);/*使用fseek()函数,每次跳跃3个间格*/
25 } /*使用for循环,共执行5次*/
26
27 fclose(pFile);
28 }
29
30 system("pause");
31 return 0;
32 }
- 第10~14行:在fileIO.txt文件中创建A~Z的数据。
- 第21~25行:使用for循环,共执行5次。
- 第24行:使用fseek()函数,每次跳跃3个间格。
每次使用文件存取函数,文件读取指针都会往下一个位置移动,如果想将文件读取指针返回文件的开头,就可以使用rewind()函数。其语句如下:
void rewind ( FILE * stream);
stream:指向数据流对象的指针。
rewind()函数的功能等同下列程序语句:
fseek ( stream , 0L , SEEK_SET );
无缓冲区的输入与输出
无缓冲区的输入与输出起源于UNIX系统,一般通过相关函数存取文件,指令会直接读写文件,所以会有频繁的硬件读写操作。和缓冲区的输入与输出相比,无缓冲区的效率比较差,且耗资源。在操作系统(OS)底层的实际行为上,有缓冲区的输入与输出在最后确定关闭或存取时才会对数据执行批次操作。从某种程度来说,有缓冲区的输入与输出比无缓冲区的输入与输出模式有效率,因为多数数据可能在读写过程中会进行运算,以判断是否需要进行删除和修改等操作,有缓冲区的输入与输出会在内存中执行这些操作,而不是直接对硬件进行读写。
基本上,使用无缓冲区的输入与输出函数时需包含fcntl.h头文件,所以在程序代码最上方需加入以下包含语句:
#include<fcntl.h>
open()函数与close()函数
在open()函数中可使用一种以上的打开模式常数,彼此间加上“|”即可。除了文件名外,也不需要用双引号括住。声明方式如下:
int open(char *filename, int mode, int access);
- filename:要打开的文件名。
- mode:要打开文件的模式。
- access:存取模式,指定存取方式,一般情况下设置为0即可。
O_WRONLY| O_APPEND /* 打开文件,但只能写入附加数据 */ O_RDONLY| O_TEXT /* 打开只读的文本文件 */
下面的范例程序用来示范:如果打开成功,open()函数会返回一个int值,返回-1表示失败,返回其他值表示成功。
01 #include <stdio.h> 02 #include <fcntl.h> 03 04 int main() 05 { 06 const char *filename="fileIO.txt"; 07 int intRst; 08 intRst=open(filename,O_RDONLY,0); 09 /*打开指定文件名的文件,模式为只读*/ 10 if(intRst==-1) 11 { 12 printf ("file open fail \n"); 13 /*返回-1表示失败*/ 14 } 15 else 16 { 17 printf ("file open success \n"); 18 /*本程序执行时,已经存在fileIO.txt文件,所以结果是成功*/ 19 } 20 21 system("pause"); 22 return 0; 23 }
- 第8行:打开指定文件名的文件,模式为只读。
- 第10行:返回为-1,表示失败。
- 第17行:在程序执行时已经存在fileIO.txt文件,所以结果是成功。
close()函数主要用来关闭一个已打开的文件。一般搭配open()函数使用。当使用open()函数打开文件时,会返回一个int类型的文件代码,通过close()函数可关闭此代码所代表的文件。声明方式如下:
int close(int fileID);
fileID:表示文件代码。 例如:
close(fpt1);
read()函数与write()函数
无缓冲区文件处理函数的写入与读取函数分别为write()与read(),定义与fread()、fwrite()函数类似,可以一次性处理整个区块的数据。其中,read()函数主要用来读出文件中的数据,声明方式如下:
int read(int fileID,void *buff,int length);
- fileID:准备读取的文件代码。
- *buff:存放读入数据的暂存区指针。
- length:读入数据的长度。
例如:
bytes=read(fptl, buffer, sizeof(buffer));/* 从fpt1文件,每次读取256个字节,bytes为实际返回读取字节 */
下面的范例程序示范read()函数读取文件的使用方式,当read()读取成功时,返回读取的数据长度,失败时返回-1。
01 #include <stdio.h>
02 #include <fcntl.h>
03 #define length 512 /*定义一个常数,代表读取长度*/
04
05 int main()
06 {
07 int fileID;
08 char buff[length];
09 const char* filename="fileIO.txt";
10 fileID = open(filename,O_RDONLY,0);
11 /*声明int类型值记录打开文件的文件ID码*/
12
13 if(fileID!=-1)/*确认文件打开成功*/
14 {
15 if(read(fileID,buff,length)>0)/*确认文件读取成功*/
16 {
17 printf("%s \n",buff);
18 }
19 }
20 close(fileID);
21
22 system("pause");
23 return 0;
24 }
- 第3行:定义一个常数,代表读取长度。
- 第10行:声明int类型值记录打开文件的文件ID码。
- 第13行:确认文件打开成功。第15行确认文件读取成功。
write()函数主要用来将数据写入文件,声明方式如下:
int write(int fileID,void *buff,int length);
- fileID:准备要写入的文件代码。
- *buff:存放写入数据的暂存区指针。
- length:写入数据的长度。
例如:
write(fpt1, buffer, sizeof(buffer));
/* 在fpt1文件中,每次写入256个字节 */
下面的范例程序示范write()函数写入文件的使用方式,当write()函数写入成功时返回0,失败时返回-1。
01 #include <fcntl.h>
02 #include <stdio.h>
03 #include <stdlib.h>
04
05 int main()
06 {
07 int fileID;
08 const char *filename;/*定义一个文件名变量*/
09 char *buff;/*定义一个准备写入数据的变量*/
10 int intRst;
11
12 buff="1234567890";
13 filename="fileIO.txt";
14
15 fileID = open(filename,O_CREAT | O_RDWR);
16 /*打开指定文件名的文件,若没有,则创建此文件,并可供读写*/
17 intRst=write(fileID,buff,10);/*将数据写入文件*/
18 if(intRst!=-1)/*判断数据是否写入成功*/
19 printf("data write success\n");
20
21 close(fileID);
22 system("pause");
23 return 0;
24 }
- 第8行:定义一个文件名变量。
- 第9行:定义一个准备写入数据的变量。
- 第15行:打开指定文件的文件名,若没有,则创建此文件,并可供读写。
- 第17行:使用write()函数将数据写入文件。
lseek()函数
无缓冲区随机文件存取方式也可以配合文件指针位置在文件中移动,作为随机存取数据的模式。C语言提供了lseek()函数来移动与操作读取指针,到指针所指定的新位置读取或写入数据。lseek()函数使用的方法与概念类似于fseek(),只是使用的地方不同,fseek()适用于有缓冲区的输入与输出,lseek()适用于无缓冲区的输入与输出。
声明方式如下:
int lseek(int fileID,long offset,int position);
- fileID:文件代码。
- offset:偏移量。根据position的位置偏移,不可超过64K。
- position:偏移的起始位置。
下面的范例程序示范可以通过lseek()函数偏移文件指针当前的位置,当lseek()函数执行成功时返回offset值,失败时返回-1。
01 #include <fcntl.h>
02 #include <stdio.h>
03 #include <stdlib.h>
04
05 int main()
06 {
07 int fileID,i;
08 const char *filename;
09 char buff[]={'A','B','C','D','E'};/*定义一个字符数组*/
10
11 filename="fileIO.txt";
12
13 fileID = open(filename,O_CREAT | O_RDWR);
14 /*打开指定文件名的文件,若没有,则创建此文件,并可供读写*/
15 for(i=1;i<=4;i++)/*循环共执行4次*/
16 {
17 write(fileID,buff,5);/*写入buff代表的数据到文件中*/
18 lseek(fileID,-i,SEEK_CUR);/*设置文件读写指针从当前位置偏移-i*/
19 write(fileID,"-",1);
20 }
21
22 close(fileID);
23
24 system("pause");
25
26 return 0;
27 }
- 第9行:定义一个字符数组。
- 第13行:打开指定文件名的文件,若没有,则创建此文件,并可供读写。
- 第15~20行:共执行4次循环。
- 第17行:写入buff代表的数据至文件。