文件打開之后,就可以對它進行讀寫了。常用的讀寫函數如下所述。
13.4.1 fputc 函數和 fgetc 函數(putc 函數和 getc 函數)
1. fputc 函數
把一個字符寫到磁盤文件上去。其一般調用形式為
fputc (ch,fp);
其中ch 是要輸出的字符,它可以是一個字符常量,也可以是一個字符變量.
fp 是文件指針變量。fputc (ch,fp) 函數的作用是將字符(ch的值)輸出到所指向的文件中去。fputc 函數也帶回一個值:如果輸出成功,則返回值就是輸出的字符;如果輸出失敗,則返回一個EOF(即—1)。EOF 是在 stdio.h 文件中定義的符號常量,值為—1。
在第4章介紹過 putchar 函數,其實 putchar 是從 fputc 函數派生出來的。putchar(c) 是在 stdio.h 文件中用預處理命令 #define 定義的宏:
#define putchar(c) fputc(c,stdout)
前面已敘述,stdout 是系統定義的文件指針變量,它與終端輸出相聯.
fputc(c,stdout)的作用是將 c 的值輸出到終端.用宏putchar(c)比寫fputc(c,stdout)
簡單一些。從用戶的角度,可以把 putchar(c) 看作函數而不必嚴格地稱它為宏。
2.fgetc 函數
從指定的文件讀入一個字符,該文件必須是以讀或讀寫方式打開的。fgetc 函數的調用形式為: ch=fgetc(fp);
fp 為文件型指針變量,ch 為字符變量。fgetc 函數帶回一個字符,賦給 ch。
如果在執行 fgetc 函數讀字符時遇到文件結束符,函數返回一個文件結束標志EOF(即—1)。如果想從一個磁盤文件順序讀入字符并在屏幕上顯示出來,可以用:
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
注意:EOF 不是可輸出字符,因此不能在屏幕上顯示。由于字符的 ASCII 碼不可能出現—1,因此 EOF 定義為—1是合適的。當讀入的字符值等于—1(即EOF)時,表示讀入的已不是正常的字符而是文件結束符。但以上只適用于讀文本文件的情況?,F在 ANSI C 已允許用緩沖文件系統處理二進制文件,而讀入某一個字節中的二進制數據的值有可能是—1,而這又恰好是EOF的值.
這就出現了需要讀入有用數據而卻被處理為“文件結束”的情況。為了解決這個問題,ANSI C 提供一個 feof 函數來判斷文件是否真的結束。feof(fp)用來測試 fp 所指向的文件當前狀態是否“文件結束”。如果是文件結束,函數feof(fp)的值為1(真);否則為0(假)。
如果想順序讀入一個二進制文件中的數據,可以用:
while(! feof(fp))
{
c=fgetc(fp);
……
}
當未遇文件結束,feof(fp)的值為0,! feof(fp) 的值為1,讀入一個字節的數據賦給整型變量c,并接著對其進行所需的處理。直到遇文件結束,feof(fp)值為1,! feof(fp) 值為0,不再執行 while 循環.
這種方法也適用于文本文件。
3. fputc 和 fgetc函數使用舉例
在掌握了以上幾種函數以后,可以編制一些簡單的使用文件的程序。
例13 .1 從鍵盤輸入一些字符,逐個把它們送到磁盤上去,直到輸入一個“#”為止。程序如下: (本例有錯誤)
#include "stdio.h"
#include "stdlib.h"
void main()
{
FILE * fp;
char ch,filename[10];
scanf("%s",filename);
if((fp=fopen(filename,"W"))==NULL)
{
printf("cannot open file\n");
exit(0);
}
ch=getchar();
ch=getchar();
while(ch!='#')
{
fputc(ch,fp);
putchar(ch);
ch=getchar();
}
putchar(10);
fclose(fp);
}
運行結果是:
輸入:file1.c
cannot open file
(不能打開文件)
文件名由鍵盤輸入,賦給字符數組 filename。 fopen 函數中的第一個參數“文件名” 可以直接寫成字符串常量形式(如file1.c),也可以用字符數組名,在字符數組中存放文件名(如本例所用的方法)。本例運行時,從鍵盤輸入磁盤文件名 “file1.c” ,然后輸入要寫入該磁盤文件的字符“ computer and c”, '#' 是表示輸入結束,程序將 “ computer and c” 寫到以命名的磁盤文件中,同時在屏幕上顯示這些字符 ,以便核對。exit 是標準 C 的庫函數,作用是使程序終止,用此函數應當加入 stdlib 頭文件。
例13.2 將一個磁盤文件中的信息復制到另一個磁盤文件中。
(能運行,不能復制 )
#include<stdlib.h>
#include<stdio.h>
void main()
{
FILE*in,*out;
char ch,infile[10],outfile[10];
printf("Enter the infile name:\n");
scanf("%s",infile);
printf("Enter the infile name:\n");
scanf("%s",outfile);
if((in=fopen(infile,"r"))==NULL)
{
printf("can not open infile\n");
exit(0);
}
if((out=fopen(outfile,"w"))==NULL)
{
printf("can not open outfile\n");
exit(0);
}
while(! feof(in)) fputc(fgetc(in),out);
fclose(in);
fclose(out);
}
運行情況如下:
Enter the infile name:
file1.txt (輸入原有磁盤文件名)
Enter the infile name:
file2.txt (輸入新復制的磁盤文件名)
程序運行結果是將 file1.txt 文件中的內容復制到 file2.txt 中去。
以上程序是按文本文件方式處理的。也可以用此程序來復制一個二進制文件,只需將兩個 fopen 函數中的 r 和 w 分別改為 rb 和 wb 即可。
也可以在輸入命令行時把兩個文件名一起輸入。這時要用到 main 函數的參數。程序可改為:
#include<stdlib.h>
#include<stdio.h>
void main(int agc,char *argv[])
{
FILE * in,* out;
char ch;
if(argc !=3)
{
printf("You forgot to enter a filename\n");
exit(0);
}
if((in=fopen(argv[1],"r"))==NULL)
{
printf("cannot open infile\n");
exit(0);
}
if((out=fopen(argv[2],"w"))==NULL)
{
printf("cannot open infile\n");
exit(0);
}
while(! feof(in)) fputc(fgetc(in),out);
fclose(in);
fclose(out);
}
假若本程序的原文件名為 1.c 經編譯連接后得到的可執行文件名為 1.exe ,則在DOS命令工作方式下,可以輸入以下的命令行:C>1 file1.c file2.c 即在輸入可執行文件名后,再輸入兩個參數 file1.c 和 file2.c ,分別輸入到 argv[1]和 argv[2]中,argv[0]的內容為a,argc 的值等于3(因為此命令行共有3個參數) 。如果輸入的參數少于3個,則程序會輸出:“You forgot to enter a filename”
(你忘了輸入一個文件名)。程序執行結果是將 file1.c 中的信息復制到 file2.c 中??梢杂靡韵旅铗炞C:
C>type file1.c
computer and c
(這是 file1.c 文件中的信息)
C>type file2.c
computer and c
(這是 file2.c 文件中的信息??梢?file1.c已復制到 file2.c 中了)。
最后說明一點,為了書寫方便,系統把 fputc 和 fgetc 定義為宏名putc 和getc:
#define putc(ch,fp) fputc(ch,fp)
#define getc(fp) fgetc(fp)
這是在 stdio.h 中定義的。因此,用 putc 和 fputc 及用 getc 和 fgetc 是一樣的。一般可以把它們作為相同的函數來對待。
13.4.2 fead 函數和 fwrite 函數
用 getc 和 putc 函數可以用來讀寫文件中的一個字符。但是常常要求一次讀入一組數據(例如,一個實數或一個結構體變量的值),ANSI C 標準提出設置兩個函數(fead 函數和 fwrite 函數),用來讀寫一個數據塊。它們的一般調用形式為:
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
其中:buffer:是一個指針.對 fread 來說,它是讀入數據的存放地址. 對fwrite 來說,
是要輸出數據的地址(以上指的是起始地址)。
size : 要讀寫的字節數。
count: 要進行讀寫多少個 size 字節的數據項。
fp: 文件型指針。
如果文件以二進制形式打開,用 fead 和 fwrite 函數就可以讀寫任何類型的信息,例如: fead(f,4,2,fp); 其中 f 是一個實型數組名。一個實型變量占4個字節。這個函數從所指向的文件讀入2個4個字節的數據,存儲到數組 f 中。
如果有一個如下的結構體類型:
struct student_type
{
char name[10];
int num;
int age;
char addr[30];
}stud[40];
結構體數組 stud 有40個元素,每一個元素用來存放一個學生的數據(包括姓名、學號、年齡、地址)。假設學生的數據已存放在磁盤文件中,可以用下面的 for 語句和 fread 函數讀入40個學生的數據:
for(i=0;i<40;i++)
fread(&stud[i],sizeof(struct student_type),1,fp);
同樣,以下 for 語句和 fwrite 函數可以將內存中的學生數據輸出到磁盤文件中去:
for(i=0;i<40;i++)
fwrite(&stud[i],sizeof(struct student_type),1,fp);
如果 fead 和 fwrite 調用成功,則函數返回值為 count 的值,既輸入或輸出數據項的完整個數。
下面寫出一個完整的程序。
例13.3 從鍵盤輸入4個學生的有關數據,然后把它們轉存到磁盤文件上去。
#include "stdio.h"
#define SIZE 4
struct student_type
{char name[10];
int age;
int num;
char addr[15];
}stud[SIZE];
void save()
{
FILE * fp;
int i;
if((fp=fopen("stu_list","wb"))==NULL)
{
printf("cannot open file\n");
return;
}
for(i=0;i<SIZE;i++)
if(fwrite(&stud[i],sizeof(struct student_type),1,fp)!=1)
printf("file write error\n");
fclose(fp);
}
void main()
{
int i;
for(i=0;i<SIZE;i++)
scanf("%s%d%d%s",stud[i].name,&stud[i].age,&stud[i].num,stud[i].addr);
save();
}
在main 函數中,從終端鍵盤輸入4個學生的數據,然后調用 save 函數,將這些數據輸出到以 " stu_list "命名的磁盤文件中。fwrite 函數的作用是將一個
長度為29字節數據塊送到 stu_list 文件中(一個 student_type 類型結構體變量的長度為它的成員長度之和,即10+2+2+15=29)。
運行情況如下:
輸入4個學生的姓名、學號、年齡和地址:
Zhang 1001 18 room_101
Fun 1002 18 room_102
Tan 1003 18 room_103
Lin 1004 21 room_104
程序運行時,屏幕上并無輸出任何信息,只是將從鍵盤輸入的數據送到此盤文件上。為了驗證在磁盤文件 “ stu_list ”中是否已存在此數據,可以用以下程序從 stu_list 文件中讀入數據,然后在屏幕上輸出。
#include "stdio.h"
#define SIZE 4
struct student_type
{
int age;
int num;
char addr[15];
char name[10];
}stud[SIZE];
void main()
{
int i;
FILE * fp;
fp=fopen("stu_list","rb");
for(i=0;i<SIZE;i++)
{
fread(&stud[i],sizeof(struct student_type),1,fp);
printf("%-10s %4d %4d %-15s\n",stud[i].name,&stud[i].age,&stud[i].num,stud[i].addr);
}
fclose(fp);
}
請注意:輸入輸出數據的狀況。從鍵盤輸入4個學生的數據是 ASCII 碼,也就是文本文件。在送到計算機內存時,回車和換行符轉換成一個換行符。再從內存以 “wb”方式(二進制寫)輸出到 stu_list 文件,此時不發生字符轉換,按內存中存儲形式原樣輸出到磁盤文件上。在上面驗證程序中,又用“fread ”函數從 stu_list 文件向內存讀入數據,注意此時用的是“rb” 方式,即二進制方式,數據按原樣輸入,也不發生字符轉換。也就是這時候內存中的數據恢復到第一個程序向 “ stu_list ” 輸出以前的情況。最后在驗證程序中,用printf 函數輸出到屏幕,printf 是格式輸出函數,輸出 ASCII 碼,在屏幕上顯示字符。換行符又轉換為回車加換行符。
如果企圖從 “ stu_list ”文件中以 “r ”方式讀入數據就會出錯。
fread 和 fwrire 函數一般用于二進制文件的輸入輸出。因為它們是按數據塊的長度來處理輸入輸出的,在字符發生轉換的情況下很可能出現與原設想的情況不同。 例如,寫成:fread(&stud[i],sizeof(struct student_type),1,stdin); 企圖從終端鍵盤輸入數據,這在語法上并不存在錯誤,編譯能通過。如果用以下形式輸入數據: Zhang 1001 18 room_101
……
由于 fread 函數要求一次輸入29個字節(而不問這些字節的內容),因此輸入數據中的空格也作為輸入數據而不作為數據間的分隔符了。連空格也存儲到 stud[i] 中了,顯然是不對的。
這個題目要求的是從鍵盤輸入數據,如果已有的數據已經以二進制形式存儲在一個磁盤文件 stu_dat 中,要求從其中讀入數據并輸出到 stu_list 文件中,可以編寫一個 load 函數,從磁盤文件中讀二進制數據。
#include "stdio.h"
#define SIZE 4
struct student_type
{
int age;
int num;
char addr[15];
char name[10];
}stud[SIZE];
void load()
{
FILE * fp;
int i;
if((fp=fopen("stu_dat","rb"))==NULL)
{
printf("cannot open file\n");
return;
}
for(i=0;i<SIZE;i++)
if(fread(&stud[i],sizeof(struct student_type),1,fp)!=1)
{ if(feof(fp))
{ fclose(fp);
return;
}
printf("file write error\n");
}
fclose(fp);
}
main()
{
load();
save();
}
13.4.3 fprintf (從文件中輸出) 函數和 fscanf (從文件中讀入) 函數
fprintf 函數、fscanf 函數 與 printf 函數、scanf 函數作用相仿,都是格式讀寫函數。只有一點不同: fprintf 函數、fscanf 函數的讀寫對象不是終端而是磁盤文件。它們的一般調用方式為:
fprintf (文件指針,格式字符串,輸出表列);
fscanf (文件指針,格式字符串,輸入表列);
例如:
fprintf(fp,"%d,%6.2f",a,b);
它的作用是將整型變量 a 和實型變量 b 的值按 %d 和 %6.2f 的格式輸出到 fp 指向的文件上。如果 i=3,t=4.5 則輸出到磁盤文件上的是以下的字符串:
3, 4.50
同樣,用以下函數可以從磁盤文件上讀入 ASCII 字符:
fscanf(fp,"%d,%f",&a,&b);
磁盤文件上如果有這樣的字符:3, 4.5 即將磁盤文件中的數據 3送給變量 a,4.5 送給變量 b。
用 fprintf 和 fscanf 函數對磁盤文件讀寫,使用方便,容易理解,但由于在輸入時要將 ASCII 碼轉換為二進制形式,在輸出時又要將二進制形式轉換成字符,花費時間比較多。因此,在內存與磁盤頻繁交換數據的情況下,最好不用 fprintf 和 fscanf 函數,而用 fread(從文件中讀) 和 fwrite(往文件中寫) 函數。
13.4.4 其它讀寫函數
1. putw 和 getw 函數
大多數 C 編譯系統都提供另外兩個數:putw 和 getw 函數 ,用來對磁盤文件讀寫一個字(整數)。例如: putw(10,fp); 它的作用是將整數 10 輸出到 fp指向的文件。而 i=getw(fp); 的作用是從磁盤文件讀一個整數到內存,賦給整型變量 i。
如果所用的 C 編譯系統的庫函數中不包括 putw 和 getw 函數,可以自己定義這兩個函數。putw 函數如下:
putw(int i,FILE * fp)
{
char * s; 圖1: i
s=&i; 00000000 00001010
putc(s[0],fp); s[0] s[1]
putw(s[1],fp);
return(i);
}
當調用 putw 函數時,如果用 putw(10,fp); 語句, 形參 i 得到實參傳來的值 10, 在 putw 函數中將 i 的地址賦予指針變量 s ,而 s 是指向字符變量的指針變量,因此 s 指向 i 的第 1 個字節,s+1 指向 i 的第 2 個字節。由于 * (s+0)就是 s[0],* (s+1)就是 s[1],因此,s[0]、s[1]分別對應的第 1 個字節和第 2 個字節。順序輸出s[0]、s[1]就相當于輸出了 i 的兩個字節中的內容,見圖1.
getw 函數如下:
getw(FILE * fp)
{
char * s;
int i;
s=char * &i; /*使 s 指向 i 的起始地址 */
s[0]=getc(fp);
s[1]=getc(fp);
return(i);
}
putw 和 getw 函數并不是 ANSI C 標準定義的函數。許多 C 編譯系統都提供這兩個,但有的不以 putw 和 getw 命名此兩函數,而用其它函數名,用時要注意。
2. 讀寫其它類型數據
如果用 ANSI C 提供的 fread 和 fwrite函數,讀寫任何類型數據都是十分方便的。如果所用的系統不提供這兩個函數,用戶只好自己定義所需函數。例如,可以定義一個向磁盤文件寫一個實數(用二進制方式)函數 putfloat:
putfloat(float num,FILE * fp)
{
char * s;
int count;
s=(char *) #
for(count=0;count<4,count++)
putc(s[count],fp);
}
同樣可以編寫出讀寫任何類型數據的函數。
3. fgets 函數和 fputs 函數
fgets 函數的作用是從指定文件讀入一個字符串。例如:fgets (str,a,fp); a 為要求得到的字符,但只從 fp指向的文件輸入 a-1 個字符,然后在最后加一個‘ \0 ’字符,因此得到的字符串共有 a 個字符,把它們放到字符數組 str 中.如果
在讀完 a-1個字符之前遇到換行符或 EOF ,讀入即結束。 fgets 函數返回值為 str 的首地址。
fputs 函數的作用是從指定文件輸出一個字符串。例如:fputs("Wolong",fp);
把字符串 “ Wolong ” 輸出到 fp 指向的文件。fputs 函數中第一個參數可以是字符串常量、字符數組名或字符指針。 字符串末尾的‘ \0 ’ 不輸出。若輸出成功,函數值為 0;失敗時,為 EOF 。
這兩個函數類似以前介紹過的 gets 和 puts 函數, 只是 fgets 和 fputs 函數以指定的文件為讀寫對象。
fgets(從文件中獲取字符串)
fputs(往文件中寫字符串)
如果所用的 C 編譯系統的庫函數中不包括 putw 和 getw 函數,可以自己定義這兩個函數。putw 函數如下:
putw(int i,FILE * fp)
{
char * s; 圖1: i
s=&i; 00000000 00001010
putc(s[0],fp); s[0] s[1]
putw(s[1],fp);
return(i);
}
當調用 putw 函數時,如果用 putw(10,fp); 語句, 形參 i 得到實參傳來的值 10, 在 putw 函數中將 i 的地址賦予指針變量 s ,而 s 是指向字符變量的指針變量,因此 s 指向 i 的第 1 個字節,s+1 指向 i 的第 2 個字節。由于 * (s+0)就是 s[0],* (s+1)就是 s[1],因此,s[0]、s[1]分別對應的第 1 個字節和第 2 個字節。順序輸出s[0]、s[1]就相當于輸出了 i 的兩個字節中的內容,見圖1.
getw 函數如下:
getw(FILE * fp)
{
char * s;
int i;
s=char * &i; /*使 s 指向 i 的起始地址 */
s[0]=getc(fp);
s[1]=getc(fp);
return(i);
}
如果用 ANSI C 提供的 fread 和 fwrite函數,讀寫任何類型數據都是十分方便的。如果所用的系統不提供這兩個函數,用戶只好自己定義所需函數。例如,可以定義一個向磁盤文件寫一個實數(用二進制方式)函數 putfloat:
putfloat(float num,FILE * fp)
{
char * s;
int count;
s=(char *) #
for(count=0;count<4,count++)
putc(s[count],fp);
}
同樣可以編寫出讀寫任何類型數據的函數。
例13.1從鍵盤輸入一些字符,逐個把它們送到磁盤上,直到輸入一個"#"為止.程序如下:
#include<stdlib.h>
#include<stdio.h>
void main()
{
FILE*fp;
char ch,filename[10];
scanf("%s",filename);
if((fp=fopen(filename,"w"))==NULL)
{
printf("can not open file\n");
exit(0);/*終止程序*/
}
ch=getchar();/*接收輸入的一個字符*/
ch=getchar();/*這一句可以省略.*/
while(ch!='#')
{
fputc(ch,fp);putchar(ch);
ch=getchar();
}
putchar(10);/*向屏幕輸出一個換行符*/
fclose(fp);
}
運行后的結果:輸入:is a c# 輸出 a c
13.5 文件的定位
文件中有一個位置指針,指向當前讀寫的位置。如果順序讀寫一個文件,每次讀寫一個字符,則讀寫完一個字符后,該位置指針自動移動指向下一個字符位置。如果想改變這樣的規律,強制使位置指針指向其它指定的位置,可以用后面介紹的有關函數。
13.5.1 rewind 函數
rewind 函數的作用是使位置指針重新返回文件的開頭,此函數沒有返回值。
例13.4 有一個磁盤文件,第一次將它的內容顯示在屏幕上,第二次把它復制到另一文件上。
#include "stdio.h"
void main()
{
FILE * fp1,* fp2;
fp1=fopen("file1.txt","r");
fp2=fopen("file2.txt","w");
while(! feof(fp1))
putchar(getc(fp1));
rewind(fp1);
while(! feof(fp1))
putc(getc(fp1),fp2);
fclose(fp1);
fclose(fp2);
}
(先在某一個位置上新建立一個文本文件,命名為file1.txt然后運行本程序(在vc編譯系統運行過))如:“ file1.txt ”里面的內容是:
Nu11 pointer assignment
(無效的) ( 指示器)(分配、任務、作業)
運行后的結果是:
在第一次將內容顯示在屏幕上,file1.txt 的位置指針已指到文件末尾,feof 的值為零(真)。執行 rewind 函數 ,使文件的位置指針重新定位于文件開頭,并使 feof 函數的值恢復為0(假)。
fopen (打開文件) fclose(文件關閉) rewind(重新) putchar(寫一個字符的函數)feof=end of file (文件末尾) putc (輸出字符)