第十三章 文件
一,C文件概述
從用戶(hù)的角度看,文件可分為 普通文件 和 設(shè)備文件 ;
設(shè)備文件 為 顯示器、打印機(jī)、鍵盤(pán)等。
在操作系統(tǒng)中,把 外部設(shè)備 也看作是一個(gè)文件來(lái)進(jìn)行管理,把他們的輸入、輸出等同于對(duì)磁盤(pán)文件的讀和寫(xiě)。
從文件編碼的方式看,文件可分為 ASCII碼文件 和 二進(jìn)制碼文件 兩種。
ASCII文件 也稱(chēng)為 文本文件 ,這種文件在磁盤(pán)中存放時(shí)每個(gè)字符對(duì)應(yīng)一個(gè)字節(jié),用于存放對(duì)于的ASCII碼。
C系統(tǒng)在處理這些文件時(shí),并不區(qū)分類(lèi)型,都看成是字符流,按字節(jié)進(jìn)行處理。
輸入輸出字符流的開(kāi)始和結(jié)束只由程序控制而不受物理符號(hào)(如回車(chē)符)的控制。
因此,把這種文件稱(chēng)作“流式文件”。
本章討論流式文件的打開(kāi)、關(guān)閉、讀、寫(xiě)、定義等各種操作。
二,文件指針
在C語(yǔ)言中,用一個(gè)指針變量指向一個(gè)文件,這個(gè)指針?lè)Q為 文件指針。
定義說(shuō)明文件指針的一般形式為: FILE *指針變量標(biāo)識(shí)符;
其中 FILE 應(yīng)為大寫(xiě),他實(shí)際上是由系統(tǒng)定義的一個(gè)結(jié)構(gòu),該結(jié)構(gòu)中含有 文件名 、文件狀態(tài) 和 文件當(dāng)前位置 等信息。
在編寫(xiě)源程序時(shí)不必關(guān)心FILE結(jié)構(gòu)的細(xì)節(jié)。
例如: FILE *fp;
表示fp是指向FILE結(jié)構(gòu)的指針變量,通過(guò)fp即可找存放某個(gè)文件信息的結(jié)構(gòu)變量,然后按結(jié)構(gòu)變量提供的信息找到該文件,實(shí)施對(duì)文件的操作。
習(xí)慣上也籠統(tǒng)地把fp稱(chēng)為指向一個(gè)文件的指針。
三,文件的打開(kāi)與關(guān)閉
文件在進(jìn)行讀寫(xiě)操作之前要先打開(kāi),使用完畢要關(guān)閉。
所謂打開(kāi)文件,實(shí)際上是建立文件的各種有關(guān)信息,并使文件指針指向該文件,以便進(jìn)行其他操作。
關(guān)閉文件則斷開(kāi)指針與文件之間的聯(lián)系,也就禁止再對(duì)該文件進(jìn)行操作。
在C語(yǔ)言中,文件操作都是由庫(kù)函數(shù)來(lái)完成的。
1. 文件的打開(kāi)
文件指針名=fopen(文件名,使用文件方式);
例如:
FILE *fp;
fp=fopen("file a","r");
含義是在當(dāng)前目錄下打開(kāi) file a ,只允許進(jìn)行“讀”操作,并使fp指向該文件。
又如:
FILE *fphzk;
fphzk=fopen("c:\\hzk16","rb");
含義是打開(kāi) c:\\hzk16,這是一個(gè)二進(jìn)制文件,只允許按二進(jìn)制方式進(jìn)行讀操作。
兩個(gè)反斜線 \\ 中的第一個(gè)表示轉(zhuǎn)義字符,第二個(gè)表示根目錄。
使用文件的方式共有12種,下面給出了他們的符號(hào)和意義:
- "rt"=只讀打開(kāi)一個(gè)文本文件 "wt"=只寫(xiě) 打開(kāi)或建立一個(gè)文本文件 "at"=追加打開(kāi)一個(gè)文本文件
- "rb"=只讀打開(kāi)一個(gè)二進(jìn)制文件 "wb" "ab"
- "rt+"=讀寫(xiě)打開(kāi)一個(gè)文件文件 ,允許讀和寫(xiě) "wt+"=讀寫(xiě)打開(kāi)或建立一個(gè)文本文件,允許讀寫(xiě) "at+" "rb+" "wb+" "ab+"
對(duì)于文件使用方式有以下幾點(diǎn)說(shuō)明:
- 1)文件使用方式由r,w,a,t,b,+六個(gè)字符拼成,各字符的含義是:
r=read w=write a=append t=text b=binary +=read&write - 2)凡用“r”打開(kāi)一個(gè)文件時(shí),該文件必須已經(jīng)存在,且只能從該文件讀出。
- 3)用“w”打開(kāi)的文件只能向該文件寫(xiě)入。若打開(kāi)的文件不存在,則以指定的文件名建立該文件,若打開(kāi)的文件已經(jīng)存在,則將該文件刪除,重建一個(gè)新文件。
- 4)若要向一個(gè)已存在的文件追加新的信息,只能用“a”方式打開(kāi)文件。但此時(shí)文件必須是存在的,否則將會(huì)出錯(cuò)。
- 5)在打開(kāi)一個(gè)文件時(shí),如果出錯(cuò),fopen將返回一個(gè)空指針NULL。在程序中可以用這一信息來(lái)判斷釋放完成打開(kāi)文件的工作,并做相應(yīng)的處理。因此,常用以下程序段打開(kāi)文件:
if((fp=fopen("c:\\hzk16","rb")==NULL) {
printf("\nerror on open C:\\hzk16 file!");
getch();
exit(1);
}
- 6)把一個(gè)文本文件讀入內(nèi)存時(shí),要將ASCII碼轉(zhuǎn)換成二進(jìn)制碼,而把文件以文本方式寫(xiě)入磁盤(pán)時(shí),也要把二進(jìn)制碼轉(zhuǎn)換成ASCII碼,因此文本文件的讀寫(xiě)要花費(fèi)較多的轉(zhuǎn)換時(shí)間。對(duì)二進(jìn)制文件的讀寫(xiě)不存在這種轉(zhuǎn)換。
- 7)標(biāo)準(zhǔn)輸入文件(鍵盤(pán))、標(biāo)準(zhǔn)輸出文件(顯示器)、標(biāo)準(zhǔn)出錯(cuò)輸出(出錯(cuò)信息)是由系統(tǒng)打開(kāi)的,可直接使用。
2. 文件關(guān)閉函數(shù):
fclose(文件指針);
正常完成關(guān)閉文件操作時(shí),fclose 的返回值為0,如返回非零值則表示有錯(cuò)誤發(fā)生
四、文件的讀寫(xiě)
字符讀寫(xiě)函數(shù)
fgetc fputc
字符串讀寫(xiě)函數(shù)
fgets fputs
數(shù)據(jù)塊讀寫(xiě)函數(shù)
freed fwrite
格式化讀寫(xiě)函數(shù)
fscanf fprintf
需要 #include <stdio.h>
1. 字符讀寫(xiě)函數(shù)fgetc和fputc
字符讀寫(xiě)函數(shù)每次可從文件讀出或?qū)懭胍粋€(gè)字符。
(1)讀字符函數(shù)fgetc
字符變量=fgetc(文件指針);
說(shuō)明:
a)讀取字符的結(jié)果也可以不保存。即 fgetc(fp);
b)在文件內(nèi)部有一個(gè) 位置指針 ,用來(lái)指向文件的 當(dāng)前讀寫(xiě)字節(jié) 。
在文件打開(kāi)時(shí),該指針總是指向文件的第一個(gè)字節(jié)。
使用 fgetc函數(shù) 后,該位置的指針將 向后移動(dòng)一個(gè)字節(jié)。
因此可以連續(xù)多次使用 fgetc函數(shù) ,讀取多個(gè)字符。
應(yīng)注意文件指針和文件內(nèi)部的位置指針不是一回事:文件指針是指向整個(gè)文件的,須在程序中定義說(shuō)明,只要不重新賦值,文件指針的值是不變的。文件內(nèi)部的位置指針用以指示文件內(nèi)部的當(dāng)前讀寫(xiě)位置,每讀寫(xiě)一次,該指針均向后移動(dòng),他不需要在程序中定義說(shuō)明,而是由系統(tǒng)自動(dòng)設(shè)置的。
例子:讀入文件c1.doc,在屏幕上輸出:
#include<stdio.h>
main() {
FILE *fp;
char ch;
if ((fp=fopen("d:\\jrzh\\example\\c1.txt","rt"))==NULL) {
printf("\nCannot open file strike any key exit!");
getch();
exit(1);
}
ch=fgetc(fp);
while(ch!=EOF) {
putchar(ch);
ch=fgetc(fp);
}
fclose(fp);
}
(2)寫(xiě)字符函數(shù)fputc
fputc(字符量,文件指針);
說(shuō)明:
a)每寫(xiě)入一個(gè)字符,文件內(nèi)部位置指針向后移動(dòng)一個(gè)字節(jié)。
b)fputc函數(shù) 有一個(gè)返回值,如寫(xiě)入成功則返回寫(xiě)入的字符,否則返回一個(gè)EOF。
可用此來(lái)判斷寫(xiě)入是否成功。
例子:從鍵盤(pán)輸入一行字符,寫(xiě)入一個(gè)文件,再把該文件內(nèi)容讀出顯示在屏幕上。
#include<stdio.h>
main() {
FILE *fp;
char ch;
if ((fp=fopen("aaa","wt+"))==NULL) {
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("input a string:\n");
ch=getchar();
while(ch!='\n') {
fputc(ch,fp);
ch=getchar();
}
rewind(fp);//把內(nèi)部位置指針移動(dòng)文件頭
ch=fgetc(fp);
while(ch!=EOF) {
putchar(ch);
ch=fgetc(fp);
}
printf("\n");
fclose(fp);
}
例子:把命令行參數(shù)中的前一個(gè)文件名標(biāo)識(shí)的文件,復(fù)制到后一個(gè)文件名標(biāo)識(shí)的文件中。
如命令行只有一個(gè)文件名則把該文件寫(xiě)到標(biāo)準(zhǔn)輸出文件(顯示器)中。
#include<stdio.h>
main(int argc,char *argv[]) {
FILE *fp1,*fp2;
char ch;
if (argc==1) {
printf("have not enter file name strike any key to exit!");
getch();
exit(0);
}
if((fp1=fopen(argv[1],"rt"))==NULL) {
printf("Cannot open %s\n",argv[1]);
getch();
exit(1);
}
if (argc==2)
fp2=stdout;
else if ((fp2=fopen(argv[2],"wt+"))==NULL) {
printf("Cannot oepn %s\n",argv[2]);
getch();
exit(1);
}
while ((ch=fgetc(fp1))!=EOF)
fputc(ch,fp2);
fclose(fp1);
fclose(fp2);
}
2. 字符串讀寫(xiě)函數(shù)fgets和fputs
(1)讀字符串函數(shù)fgets
fgets(字符數(shù)組名,n,文件指針);
n表示從文件中讀出的字符串不超過(guò) n-1 個(gè)字符。
在讀入的最后一個(gè)字符后加上串結(jié)束標(biāo)志 \0
例如:fgets(str,n,fp);
說(shuō)明:
a)在讀出 n-1個(gè)字符之前,如遇到了換行符或EOF,則讀出結(jié)束。
b)fgets函數(shù)也有返回值,其返回值是字符數(shù)組的首地址。
(2)寫(xiě)字符串函數(shù)fputs:
fputs(字符串,文件指針);
例如: fputs("abcd",fp);
例子:在文件string中追加一個(gè)字符串。
#include<stdio.h>
main() {
FILE *fp;
char ch,st[20];
if ((fp=fopen("string","at+"))==NULL) {
printf("Cannot open file strike any key to exit!");
getch();
exit(1);
}
printf("input a string:\n");
scanf("%s",st);
fputs(st,fp);
rewind(fp);
ch=fgetc(fp);
while (ch!=EOF) {
putchar(ch);
ch=fgetc(fp);
}
printf("\n");
fclose(fp);
}
3. 數(shù)據(jù)塊讀寫(xiě)函數(shù)fread和fwrite
可用來(lái)讀寫(xiě)一組數(shù)據(jù),如一個(gè)數(shù)組元素、一個(gè)結(jié)構(gòu)變量的值等。
讀數(shù)據(jù)塊函數(shù)調(diào)用的一般形式為:
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
其中,buffer是一個(gè)指針,在 fread函數(shù) 中,他表示存放輸入數(shù)據(jù)的首地址。在 fwrite函數(shù) 中,他表示存放輸出數(shù)據(jù)的首地址。
size 表示數(shù)據(jù)庫(kù)的字節(jié)數(shù)
count 表示要讀寫(xiě)的數(shù)據(jù)塊塊數(shù)
例如: fread(fa,4,5,fp); 含義是從fp所指的文件中,每次讀4個(gè)字節(jié)(一個(gè)實(shí)數(shù))送入實(shí)數(shù)組fa中,連續(xù)讀5次,即讀5個(gè)實(shí)數(shù)到fa中。
例子:從鍵盤(pán)輸入兩個(gè)學(xué)生數(shù)據(jù),寫(xiě)入一個(gè)文件中,再讀出這兩個(gè)學(xué)生的數(shù)據(jù)顯示在屏幕上。
#include<stdio.h>
struct stu {
char name[10];
int num;
int age;
char addr[15];
} bota[2],boyb[2],*pp,*qq;
main() {
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if ((fp=fopen("d:\\stu_list","wb+"))==NULL) {
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("\ninput data\n");
for(i=0;i<2;i++,pp++)
scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
pp=boya;
fwrite(pp,sizeof(struct stu),2,fp);
rewind(fp);
fread(qq,sizeof(struct stu),2,fp);
printf("\n\nname\tnumber age addr\n");
for(i=0;i<2;i++,qq++)
printf("%s\t%5d%7d %s\n",qq->name,qq->num,qq->age,qq->addr);
fclose(fp);
}
四、格式化讀寫(xiě)函數(shù)fscanf和fprintf:
fscanf(文件指針,格式字符串,輸入表列);
fprintf(文件指針,格式字符串,輸出表列);
// 例如:
fscanf(fp,"%d%s",&i,s);
fprint(fp,"%d%c",j,ch);
例子:上面的例子的改寫(xiě):
#include<stdio.h>
struct stu {
char name[10];
int num;
int age;
char addr[15];
} boya[2],boyb[2],*pp,*qq;
main() {
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen("stu_list","wb+"))==NULL) {
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("\ninput data\n");
for(i=0;i<2;i++,pp++)
scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
pp=boya;
for(i=0;i<2;i++,pp++)
fprintf(fp,"%s %d %d %s\n",pp->name,pp->num,pp->age,pp->addr);
rewind(fp);
for(i=0;i<2;i++,qq++)
fscanf(fp,"%s %d %d %s\n",qq->name,&qq->num,&qq->age,qq->addr);
printf("\n\nname\tnumber age addr\n");
qq=boyb;
for (i=0;i<2;i++,qq++)
printf("%s\t%5d %7d %s\n",qq->name,qq->num,qq->age,qq->addr);
fclose(fp);
}
五,文件的隨機(jī)讀寫(xiě)
1. 文件定位
rewind函數(shù) 和 fseek函數(shù)
rewind前面介紹過(guò),是把內(nèi)部位置指針移動(dòng)到文件首。
fseek(文件指針,位移量,起始點(diǎn));
"位移量"是long類(lèi)型數(shù)據(jù),以便在文件長(zhǎng)度大于64KB時(shí)不會(huì)出錯(cuò)。
當(dāng)用常量表示位移量時(shí),要求加后綴L
“起始點(diǎn)”表示從何處開(kāi)始計(jì)算位移量,規(guī)定的起始點(diǎn)有三種: 文件首,當(dāng)前位置 和 文件尾 。
其表示方法如下:
SEEK_SET=0=文件首
SEEK_CUR=1=當(dāng)前位置
SEEK_END=2=文件末尾
例如:fseek(fp,100L,0);
還要說(shuō)明的是 fseek函數(shù) 一般用于二進(jìn)制文件。
在文本文件中由于要進(jìn)行轉(zhuǎn)換,故往往計(jì)算的位置會(huì)出現(xiàn)錯(cuò)誤。
2. 文件的隨機(jī)讀寫(xiě)
在移動(dòng)位置指針之后,即可用前面介紹的任一種讀寫(xiě)函數(shù)進(jìn)行讀寫(xiě)。
由于一般是讀寫(xiě)一個(gè)數(shù)據(jù)塊,因此常用 fread 和 fwrite 函數(shù)。
例子:在學(xué)生文件stu_list中讀出第二個(gè)學(xué)生的數(shù)據(jù)
#include<stdio.h>
struct stu {
char name[10];
int num;
int age;
char addr[15];
} boy,*qq;
main() {
FILE *fp;
char ch;
int i=1;
qq=&boy;
if((fp=fopen("stu_list","rb"))==NULL) {
printf("Cannot open file strike any key to exit!");
getch();
exit(1);
}
rewind(fp);
fseek(fp,i*sizeof(struct stu),0);
fread(qq,sizeof(struct stu),1,fp);
printf("\n\nname\tnumber age addr\n");
printf("%s\t%5d %7d %s\n",qq->name,qq->num,qq->age,qq->addr);
}
六,文件檢測(cè)函數(shù)
1. 文件結(jié)束檢測(cè)函數(shù)feof函數(shù)
feof(文件指針)
功能:判斷文件是否處于文件結(jié)束位置,如文件結(jié)束,返回值為1,否則為0.
2,讀寫(xiě)文件出錯(cuò)檢測(cè)函數(shù)
ferror(文件指針);
功能:檢查文件在用各種輸入輸出函數(shù)進(jìn)行讀寫(xiě)時(shí)是否出錯(cuò),如ferror返回值為0表示未出錯(cuò),否則表示出錯(cuò)。
3,文件出錯(cuò)標(biāo)志和文件結(jié)束標(biāo)志置0函數(shù)
cleanerr(文件指針);
功能:本函數(shù)用于清除出錯(cuò)標(biāo)志和文件結(jié)束標(biāo)志,使他們?yōu)?值。
七、C庫(kù)文件
分為兩類(lèi):
一類(lèi)是擴(kuò)展名為 .h 的文件,稱(chēng)為頭文件,在前面的包含命令中我們已多次使用過(guò)。
在 .h 文件中包含了 常量定義、類(lèi)型定義、宏定義、函數(shù)原型以及各種編譯選擇設(shè)置等信息。
另一類(lèi)是函數(shù)庫(kù),包括了 各種函數(shù)的目標(biāo)代碼,供用戶(hù)在程序中調(diào)用。通常在程序中調(diào)用一個(gè)庫(kù)函數(shù)時(shí),要在調(diào)用之前包含該函數(shù)原型所在的 .h 文件。
下面給出Turbo C的全部".h"文件。
ALLOC.H 說(shuō)明內(nèi)存管理函數(shù)(分配、釋放等)
ASSERT.H 定義assert調(diào)試宏
BIOS.H 說(shuō)明調(diào)用IBM-PC ROM BIOS子程序的各個(gè)函數(shù)
CONIO.H 說(shuō)明調(diào)用DOS控制臺(tái)I/O子程序的各個(gè)函數(shù)
CTYPE.H 包含有關(guān)字符分類(lèi)及轉(zhuǎn)換的各類(lèi)信息(如isalpha和toascii等)
DIR.H 包含有關(guān)目錄和路徑的結(jié)構(gòu)、宏定義和函數(shù)、
DOS.H 定義和說(shuō)明MSDOS和8086調(diào)用的一些常量和函數(shù)
ERRON.H 定義錯(cuò)誤代碼的助記符
FCNTL.H 定義在與open庫(kù)子程序連接時(shí)的符號(hào)常量
FLOAT.H 包含有關(guān)浮點(diǎn)運(yùn)算的一些參數(shù)和函數(shù)
GRAPHICS.H 說(shuō)明有關(guān)圖形功能的各個(gè)函數(shù),圖形錯(cuò)誤代碼的常量定義,正對(duì)不同驅(qū)動(dòng)程序的各種顏色值,及函數(shù)用到的一些特殊結(jié)構(gòu)
IO.H 包含低級(jí)I/0子程序的結(jié)構(gòu)和說(shuō)明
LIMIT.H 包含各環(huán)境參數(shù)、編譯時(shí)間限制、數(shù)的范圍等信息
MATH.H 說(shuō)明數(shù)學(xué)運(yùn)算函數(shù),還定義了HUGE VAL 宏,說(shuō)明了matherr和matherr子程序用到的特殊結(jié)構(gòu)
MEM.H 說(shuō)明一些內(nèi)存操作函數(shù)(其中大多數(shù)也在STRING.H中說(shuō)明)
PROCESS.H 說(shuō)明進(jìn)程管理的各個(gè)函數(shù),spawn...和EXEC...函數(shù)的結(jié)構(gòu)說(shuō)明
SETJMP.H 定義longjmp和setjmp函數(shù)用到的jmp buf類(lèi)型,說(shuō)明這兩個(gè)函數(shù)
SHARE.H 定義文件共享函數(shù)的參數(shù)
SIGNAL.H 定義SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量,說(shuō)明rajse和signal兩個(gè)函數(shù)
STDARG.H 定義讀函數(shù)參數(shù)表的宏。(如vprintf,vscanf函數(shù))
STDDEF.H 定義一些公共數(shù)據(jù)類(lèi)型和宏
STDIO.H 定義Kernighan和Ritchie在Unix System V 中定義的標(biāo)準(zhǔn)和擴(kuò)展的類(lèi)型和宏。還定義標(biāo)準(zhǔn)I/O預(yù)定義流:stdin、stdout和stderr,說(shuō)明I/0流子程序。
STDLIB.H 說(shuō)明一些常用的子程序:轉(zhuǎn)換子程序、搜索/排序子程序等。
STRING.H 說(shuō)明一些字符串操作和內(nèi)存操作函數(shù)
SYS\STAT.H 定義在打開(kāi)和創(chuàng)建文件時(shí)用到的一些符號(hào)常量
SYS\TYPES.H 說(shuō)明ftime函數(shù)和timeb結(jié)構(gòu)
SYS\TIME.H 定義時(shí)間的類(lèi)型time[ZZ(Z] [ZZ)]T。
TIME.H 定義時(shí)間轉(zhuǎn)換子程序asctime、localtime和gmtime的結(jié)構(gòu),ctime、difftime、gmtime、localtime和stime用到的類(lèi)型,并提供這些函數(shù)的原型
VALUE.H 定義一些重要常量,包括依賴(lài)于機(jī)器硬件的和為與Unix System V相兼容而說(shuō)明的一些常量,包括浮點(diǎn)和雙精度的范圍。