c study_13
?
第十章:文件
文件
文件的基本概念
所謂"文件"是指一組相關數據的有序集合。 這個數據集有一個名稱,叫做文件名。 實際上在前面的各章中我們已經多次使用了文件,例如源程序文件、目標文件、可執(zhí)行文件、庫文件 (頭文件)等。文件通常是駐留在外部介質(如磁盤等)上的, 在使用時才調入內存中來。從不同的角度可對文件作不同的分類。從用戶的角度看,文件可分為普通文件和設備文件兩種。
普通文件是指駐留在磁盤或其它外部介質上的一個有序數據集,可以是源文件、目標文件、可執(zhí)行程序; 也可以是一組待輸入處理的原始數據,或者是一組輸出的結果。對于源文件、目標文件、 可執(zhí)行程序可以稱作程序文件,對輸入輸出數據可稱作數據文件。
設備文件是指與主機相聯(lián)的各種外部設備,如顯示器、打印機、鍵盤等。在操作系統(tǒng)中,把外部設備也看作是一個文件來進行管理,把它們的輸入、輸出等同于對磁盤文件的讀和寫。 通常把顯示器定義為標準輸出文件, 一般情況下在屏幕上顯示有關信息就是向標準輸出文件輸出。如前面經常使用的printf,putchar 函數就是這類輸出。鍵盤通常被指定標準的輸入文件, 從鍵盤上輸入就意味著從標準輸入文件上輸入數據。scanf,getchar函數就屬于這類輸入。
從文件編碼的方式來看,文件可分為ASCII碼文件和二進制碼文件兩種。
ASCII文件也稱為文本文件,這種文件在磁盤中存放時每個字符對應一個字節(jié),用于存放對應的ASCII碼。例如,數5678的存儲形式為:
ASC碼: 00110101 00110110 00110111 00111000
↓ ↓ ↓ ↓
十進制碼: 5 6 7 8 共占用4個字節(jié)。ASCII碼文件可在屏幕上按字符顯示, 例如源程序文件就是ASCII文件,用DOS命令TYPE可顯示文件的內容。 由于是按字符顯示,因此能讀懂文件內容。
二進制文件是按二進制的編碼方式來存放文件的。 例如, 數5678的存儲形式為: 00010110 00101110只占二個字節(jié)。二進制文件雖然也可在屏幕上顯示, 但其內容無法讀懂。C系統(tǒng)在處理這些文件時,并不區(qū)分類型,都看成是字符流,按字節(jié)進行處理。 輸入輸出字符流的開始和結束只由程序控制而不受物理符號(如回車符)的控制。 因此也把這種文件稱作"流式文件"。
本章討論流式文件的打開、關閉、讀、寫、 定位等各種操作。文件指針在C語言中用一個指針變量指向一個文件, 這個指針稱為文件指針。通過文件指針就可對它所指的文件進行各種操作。 定義說明文件指針的一般形式為: FILE* 指針變量標識符; 其中FILE應為大寫,它實際上是由系統(tǒng)定義的一個結構, 該結構中含有文件名、文件狀態(tài)和文件當前位置等信息。 在編寫源程序時不必關心FILE結構的細節(jié)。例如:FILE *fp; 表示fp是指向FILE結構的指針變量,通過fp 即可找存放某個文件信息的結構變量,然后按結構變量提供的信息找到該文件, 實施對文件的操作。習慣上也籠統(tǒng)地把fp稱為指向一個文件的指針。文件的打開與關閉文件在進行讀寫操作之前要先打開,使用完畢要關閉。 所謂打開文件,實際上是建立文件的各種有關信息, 并使文件指針指向該文件,以便進行其它操作。關閉文件則斷開指針與文件之間的聯(lián)系,也就禁止再對該文件進行操作。
在C語言中,文件操作都是由庫函數來完成的。 在本章內將介紹主要的文件操作函數。
文件打開函數fopen
fopen函數用來打開一個文件,其調用的一般形式為: 文件指針名=fopen(文件名,使用文件方式) 其中,"文件指針名"必須是被說明為FILE 類型的指針變量,"文件名"是被打開文件的文件名。 "使用文件方式"是指文件的類型和操作要求。"文件名"是字符串常量或字符串數組。例如:
FILE *fp;
fp=("file a","r");
其意義是在當前目錄下打開文件file a, 只允許進行"讀"操作,并使fp指向該文件。
又如:
FILE *fphzk
fphzk=("c:\\hzk16',"rb")
其意義是打開C驅動器磁盤的根目錄下的文件hzk16, 這是一個二進制文件,只允許按二進制方式進行讀操作。兩個反斜線"\\ "中的第一個表示轉義字符,第二個表示根目錄。使用文件的方式共有12種,下面給出了它們的符號和意義。
文件使用方式 意 義
"rt" 只讀打開一個文本文件,只允許讀數據
"wt" 只寫打開或建立一個文本文件,只允許寫數據
"at" 追加打開一個文本文件,并在文件末尾寫數據
"rb" 只讀打開一個二進制文件,只允許讀數據
"wb" 只寫打開或建立一個二進制文件,只允許寫數據
"ab" 追加打開一個二進制文件,并在文件末尾寫數據
"rt+" 讀寫打開一個文本文件,允許讀和寫
"wt+" 讀寫打開或建立一個文本文件,允許讀寫
"at+" 讀寫打開一個文本文件,允許讀,或在文件末追加數 據
"rb+" 讀寫打開一個二進制文件,允許讀和寫
"wb+" 讀寫打開或建立一個二進制文件,允許讀和寫
"ab+" 讀寫打開一個二進制文件,允許讀,或在文件末追加數據
對于文件使用方式有以下幾點說明:
1. 文件使用方式由r,w,a,t,b,+六個字符拼成,各字符的含義是:
r(read): 讀
w(write): 寫
a(append): 追加
t(text): 文本文件,可省略不寫
b(banary): 二進制文件
+: 讀和寫
2. 凡用"r"打開一個文件時,該文件必須已經存在, 且只能從該文件讀出。
3. 用"w"打開的文件只能向該文件寫入。 若打開的文件不存在,則以指定的文件名建立該文件,若打開的文件已經存在,則將該文件刪去,重建一個新文件。
4. 若要向一個已存在的文件追加新的信息,只能用"a "方式打開文件。但此時該文件必須是存在的,否則將會出錯。
5. 在打開一個文件時,如果出錯,fopen將返回一個空指針值NULL。在程序中可以用這一信息來判別是否完成打開文件的工作,并作相應的處理。因此常用以下程序段打開文件:
if((fp=fopen("c:\\hzk16","rb")==NULL)
{
printf("\nerror on open c:\\hzk16 file!");
getch();
exit(1);
}
這段程序的意義是,如果返回的指針為空,表示不能打開C盤根目錄下的hzk16文件,則給出提示信息"error on open c:\ hzk16file!",下一行getch()的功能是從鍵盤輸入一個字符,但不在屏幕上顯示。在這里,該行的作用是等待, 只有當用戶從鍵盤敲任一鍵時,程序才繼續(xù)執(zhí)行, 因此用戶可利用這個等待時間閱讀出錯提示。敲鍵后執(zhí)行exit(1)退出程序。
6. 把一個文本文件讀入內存時,要將ASCII碼轉換成二進制碼, 而把文件以文本方式寫入磁盤時,也要把二進制碼轉換成ASCII碼,因此文本文件的讀寫要花費較多的轉換時間。對二進制文件的讀寫不存在這種轉換。
7. 標準輸入文件(鍵盤),標準輸出文件(顯示器 ),標準出錯輸出(出錯信息)是由系統(tǒng)打開的,可直接使用。文件關閉函數fclose文件一旦使用完畢,應用關閉文件函數把文件關閉, 以避免文件的數據丟失等錯誤。
fclose函數
調用的一般形式是: fclose(文件指針); 例如:
fclose(fp); 正常完成關閉文件操作時,fclose函數返回值為0。如返回非零值則表示有錯誤發(fā)生。文件的讀寫對文件的讀和寫是最常用的文件操作。
在C語言中提供了多種文件讀寫的函數:
·字符讀寫函數 :fgetc和fputc
·字符串讀寫函數:fgets和fputs
·數據塊讀寫函數:freed和fwrite
·格式化讀寫函數:fscanf和fprinf
下面分別予以介紹。使用以上函數都要求包含頭文件stdio.h。字符讀寫函數fgetc和fputc字符讀寫函數是以字符(字節(jié))為單位的讀寫函數。 每次可從文件讀出或向文件寫入一個字符。
一、讀字符函數fgetc
fgetc函數的功能是從指定的文件中讀一個字符,函數調用的形式為: 字符變量=fgetc(文件指針); 例如:ch=fgetc(fp);其意義是從打開的文件fp中讀取一個字符并送入ch中。
對于fgetc函數的使用有以下幾點說明:
1. 在fgetc函數調用中,讀取的文件必須是以讀或讀寫方式打開的。
2. 讀取字符的結果也可以不向字符變量賦值,例如:fgetc(fp);但是讀出的字符不能保存。
3. 在文件內部有一個位置指針。用來指向文件的當前讀寫字節(jié)。在文件打開時,該指針總是指向文件的第一個字節(jié)。使用fgetc 函數后, 該位置指針將向后移動一個字節(jié)。 因此可連續(xù)多次使用fgetc函數,讀取多個字符。 應注意文件指針和文件內部的位置指針不是一回事。文件指針是指向整個文件的,須在程序中定義說明,只要不重新賦值,文件指針的值是不變的。文件內部的位置指針用以指示文件內部的當前讀寫位置,每讀寫一次,該指針均向后移動,它不需在程序中定義說明,而是由系統(tǒng)自動設置的。
[例10.1]讀入文件e10-1.c,在屏幕上輸出。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("e10_1.c","rt"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
ch=fgetc(fp);
while (ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
fclose(fp);
}
本例程序的功能是從文件中逐個讀取字符,在屏幕上顯示。 程序定義了文件指針fp,以讀文本文件方式打開文件"e10_1.c", 并使fp指向該文件。如打開文件出錯, 給出提示并退出程序。程序第12行先讀出一個字符,然后進入循環(huán), 只要讀出的字符不是文件結束標志(每個文件末有一結束標志EOF)就把該字符顯示在屏幕上,再讀入下一字符。每讀一次,文件內部的位置指針向后移動一個字符,文件結束時,該指針指向EOF。執(zhí)行本程序將顯示整個文件。
二、寫字符函數fputc
fputc函數的功能是把一個字符寫入指定的文件中,函數調用的 形式為: fputc(字符量,文件指針); 其中,待寫入的字符量可以是字符常量或變量,例如:fputc('a',fp);其意義是把字符a寫入fp所指向的文件中。
對于fputc函數的使用也要說明幾點:
1. 被寫入的文件可以用、寫、讀寫,追加方式打開,用寫或讀寫方式打開一個已存在的文件時將清除原有的文件內容,寫入字符從文件首開始。如需保留原有文件內容,希望寫入的字符以文件末開始存放,必須以追加方式打開文件。被寫入的文件若不存在,則創(chuàng)建該文件。
2. 每寫入一個字符,文件內部位置指針向后移動一個字節(jié)。
3. fputc函數有一個返回值,如寫入成功則返回寫入的字符, 否則返回一個EOF。可用此來判斷寫入是否成功。
[例10.2]從鍵盤輸入一行字符,寫入一個文件, 再把該文件內容讀出顯示在屏幕上。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("string","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);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf("\n");
fclose(fp);
}
程序中第6行以讀寫文本文件方式打開文件string。程序第13行從鍵盤讀入一個字符后進入循環(huán),當讀入字符不為回車符時, 則把該字符寫入文件之中,然后繼續(xù)從鍵盤讀入下一字符。 每輸入一個字符,文件內部位置指針向后移動一個字節(jié)。寫入完畢, 該指針已指向文件末。如要把文件從頭讀出,須把指針移向文件頭, 程序第19行rewind函數用于把fp所指文件的內部位置指針移到文件頭。 第20至25行用于讀出文件中的一行內容。
[例10.3]把命令行參數中的前一個文件名標識的文件, 復制到后一個文件名標識的文件中, 如命令行中只有一個文件名則把該文件寫到標準輸出文件(顯示器)中。
#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 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 open %s\n",argv[1]);
getch();
exit(1);
}
while((ch=fgetc(fp1))!=EOF)
fputc(ch,fp2);
fclose(fp1);
fclose(fp2);
}
本程序為帶參的main函數。程序中定義了兩個文件指針 fp1 和fp2,分別指向命令行參數中給出的文件。如命令行參數中沒有給出文件名,則給出提示信息。程序第18行表示如果只給出一個文件名,則使fp2指向標準輸出文件(即顯示器)。程序第25行至28行用循環(huán)語句逐個讀出文件1中的字符再送到文件2中。再次運行時,給出了一個文件名(由例10.2所建立的文件), 故輸出給標準輸出文件stdout,即在顯示器上顯示文件內容。第三次運行,給出了二個文件名,因此把string中的內容讀出,寫入到OK之中。可用DOS命令type顯示OK的內容:字符串讀寫函數fgets和fputs
一、讀字符串函數fgets函數的功能是從指定的文件中讀一個字符串到字符數組中,函數調用的形式為: fgets(字符數組名,n,文件指針); 其中的n是一個正整數。表示從文件中讀出的字符串不超過 n-1個字符。在讀入的最后一個字符后加上串結束標志'\0'。例如:fgets(str,n,fp);的意義是從fp所指的文件中讀出n-1個字符送入字符數組str中。
[例10.4]從e10_1.c文件中讀入一個含10個字符的字符串。
#include<stdio.h>
main()
{
FILE *fp;
char str[11];
if((fp=fopen("e10_1.c","rt"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
fgets(str,11,fp);
printf("%s",str);
fclose(fp);
}
本例定義了一個字符數組str共11個字節(jié),在以讀文本文件方式打開文件e101.c后,從中讀出10個字符送入str數組,在數組最后一個單元內將加上'\0',然后在屏幕上顯示輸出str數組。輸出的十個字符正是例10.1程序的前十個字符。
對fgets函數有兩點說明:
1. 在讀出n-1個字符之前,如遇到了換行符或EOF,則讀出結束。
2. fgets函數也有返回值,其返回值是字符數組的首地址。
二、寫字符串函數fputs
fputs函數的功能是向指定的文件寫入一個字符串,其調用形式為: fputs(字符串,文件指針) 其中字符串可以是字符串常量,也可以是字符數組名, 或指針 變量,例如:
fputs("abcd",fp);
其意義是把字符串"abcd"寫入fp所指的文件之中。[例10.5]在例10.2中建立的文件string中追加一個字符串。
#include<stdio.h>
main()
{
FILE *fp;
char ch,st[20];
if((fp=fopen("string","at+"))==NULL)
{
printf("Cannot open file strike any key 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);
}
本例要求在string文件末加寫字符串,因此,在程序第6行以追加讀寫文本文件的方式打開文件string 。 然后輸入字符串, 并用fputs函數把該串寫入文件string。在程序15行用rewind函數把文件內部位置指針移到文件首。 再進入循環(huán)逐個顯示當前文件中的全部內容。
數據塊讀寫函數fread和fwrite
C語言還提供了用于整塊數據的讀寫函數。 可用來讀寫一組數據,如一個數組元素,一個結構變量的值等。讀數據塊函數調用的一般形式為: fread(buffer,size,count,fp); 寫數據塊函數調用的一般形式為: fwrite(buffer,size,count,fp); 其中buffer是一個指針,在fread函數中,它表示存放輸入數據的首地址。在fwrite函數中,它表示存放輸出數據的首地址。 size 表示數據塊的字節(jié)數。count 表示要讀寫的數據塊塊數。fp 表示文件指針。
例如:
fread(fa,4,5,fp); 其意義是從fp所指的文件中,每次讀4個字節(jié)(一個實數)送入實數組fa中,連續(xù)讀5次,即讀5個實數到fa中。
[例10.6]從鍵盤輸入兩個學生數據,寫入一個文件中, 再讀出這兩個學生的數據顯示在屏幕上。
#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;
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);
}
本例程序定義了一個結構stu,說明了兩個結構數組boya和 boyb以及兩個結構指針變量pp和qq。pp指向boya,qq指向boyb。程序第16行以讀寫方式打開二進制文件"stu_list",輸入二個學生數據之后,寫入該文件中, 然后把文件內部位置指針移到文件首,讀出兩塊學生數據后,在屏幕上顯示。
格式化讀寫函數fscanf和fprintf
fscanf函數,fprintf函數與前面使用的scanf和printf 函數的功能相似,都是格式化讀寫函數。 兩者的區(qū)別在于 fscanf 函數和fprintf函數的讀寫對象不是鍵盤和顯示器,而是磁盤文件。這兩個函數的調用格式為: fscanf(文件指針,格式字符串,輸入表列); fprintf(文件指針,格式字符串,輸出表列); 例如:
fscanf(fp,"%d%s",&i,s);
fprintf(fp,"%d%c",j,ch);
用fscanf和fprintf函數也可以完成例10.6的問題。修改后的程序如例10.7所示。
[例10.7]
#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);
}
與例10.6相比,本程序中fscanf和fprintf函數每次只能讀寫一個結構數組元素,因此采用了循環(huán)語句來讀寫全部數組元素。 還要注意指針變量pp,qq由于循環(huán)改變了它們的值,因此在程序的25和32行分別對它們重新賦予了數組的首地址。
文件的隨機讀寫
前面介紹的對文件的讀寫方式都是順序讀寫, 即讀寫文件只能從頭開始,順序讀寫各個數據。 但在實際問題中常要求只讀寫文件中某一指定的部分。 為了解決這個問題可移動文件內部的位置指針到需要讀寫的位置,再進行讀寫,這種讀寫稱為隨機讀寫。 實現隨機讀寫的關鍵是要按要求移動位置指針,這稱為文件的定位。文件定位移動文件內部位置指針的函數主要有兩個, 即 rewind 函數和fseek函數。
rewind函數前面已多次使用過,其調用形式為: rewind(文件指針); 它的功能是把文件內部的位置指針移到文件首。 下面主要介紹
fseek函數。
fseek函數用來移動文件內部位置指針,其調用形式為: fseek(文件指針,位移量,起始點); 其中:"文件指針"指向被移動的文件。 "位移量"表示移動的字節(jié)數,要求位移量是long型數據,以便在文件長度大于64KB 時不會出錯。當用常量表示位移量時,要求加后綴"L"。"起始點"表示從何處開始計算位移量,規(guī)定的起始點有三種:文件首,當前位置和文件尾。
其表示方法如表10.2。
起始點 表示符號 數字表示
──────────────────────────
文件首 SEEK—SET 0
當前位置 SEEK—CUR 1
文件末尾 SEEK—END 2
例如:
fseek(fp,100L,0);其意義是把位置指針移到離文件首100個字節(jié)處。還要說明的是fseek函數一般用于二進制文件。在文本文件中由于要進行轉換,故往往計算的位置會出現錯誤。文件的隨機讀寫在移動位置指針之后, 即可用前面介紹的任一種讀寫函數進行讀寫。由于一般是讀寫一個數據據塊,因此常用fread和fwrite函數。下面用例題來說明文件的隨機讀寫。
[例10.8]在學生文件stu list中讀出第二個學生的數據。
#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 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);
}
文件stu_list已由例10.6的程序建立,本程序用隨機讀出的方法讀出第二個學生的數據。程序中定義boy為stu類型變量,qq為指向boy的指針。以讀二進制文件方式打開文件,程序第22行移動文件位置指針。其中的i值為1,表示從文件頭開始,移動一個stu類型的長度, 然后再讀出的數據即為第二個學生的數據。
文件檢測函數
C語言中常用的文件檢測函數有以下幾個。
一、文件結束檢測函數feof函數調用格式: feof(文件指針);
功能:判斷文件是否處于文件結束位置,如文件結束,則返回值為1,否則為0。
二、讀寫文件出錯檢測函數ferror函數調用格式: ferror(文件指針);
功能:檢查文件在用各種輸入輸出函數進行讀寫時是否出錯。 如ferror返回值為0表示未出錯,否則表示有錯。
三、文件出錯標志和文件結束標志置0函數clearerr函數調用格式: clearerr(文件指針);
功能:本函數用于清除出錯標志和文件結束標志,使它們?yōu)?值。
C庫文件
C系統(tǒng)提供了豐富的系統(tǒng)文件,稱為庫文件,C的庫文件分為兩類,一類是擴展名為".h"的文件,稱為頭文件, 在前面的包含命令中我們已多次使用過。在".h"文件中包含了常量定義、 類型定義、宏定義、函數原型以及各種編譯選擇設置等信息。另一類是函數庫,包括了各種函數的目標代碼,供用戶在程序中調用。 通常在程序中調用一個庫函數時,要在調用之前包含該函數原型所在的".h" 文件。
在附錄中給出了全部庫函數。
ALLOC.H 說明內存管理函數(分配、釋放等)。
ASSERT.H 定義 assert調試宏。
BIOS.H 說明調用IBM—PC ROM BIOS子程序的各個函數。
CONIO.H 說明調用DOS控制臺I/O子程序的各個函數。
CTYPE.H 包含有關字符分類及轉換的名類信息(如 isalpha和toascii等)。
DIR.H 包含有關目錄和路徑的結構、宏定義和函數。
DOS.H 定義和說明MSDOS和8086調用的一些常量和函數。
ERRON.H 定義錯誤代碼的助記符。
FCNTL.H 定義在與open庫子程序連接時的符號常量。
FLOAT.H 包含有關浮點運算的一些參數和函數。
GRAPHICS.H 說明有關圖形功能的各個函數,圖形錯誤代碼的常量定義,正對不同驅動程序的各種顏色值,及函數用到的一些特殊結構。
IO.H 包含低級I/O子程序的結構和說明。
LIMIT.H 包含各環(huán)境參數、編譯時間限制、數的范圍等信息。
MATH.H 說明數學運算函數,還定了 HUGE VAL 宏, 說明了matherr和matherr子程序用到的特殊結構。
MEM.H 說明一些內存操作函數(其中大多數也在STRING.H 中說明)。
PROCESS.H 說明進程管理的各個函數,spawn...和EXEC ...函數的結構說明。
SETJMP.H 定義longjmp和setjmp函數用到的jmp buf類型, 說明這兩個函數。
SHARE.H 定義文件共享函數的參數。
SIGNAL.H 定義SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量,說明rajse和signal兩個函數。
STDARG.H 定義讀函數參數表的宏。(如vprintf,vscarf函數)。
STDDEF.H 定義一些公共數據類型和宏。
STDIO.H 定義Kernighan和Ritchie在Unix System V 中定義的標準和擴展的類型和宏。還定義標準I/O 預定義流:stdin,stdout和stderr,說明 I/O流子程序。
STDLIB.H 說明一些常用的子程序:轉換子程序、搜索/ 排序子程序等。
STRING.H 說明一些串操作和內存操作函數。
SYS\STAT.H 定義在打開和創(chuàng)建文件時用到的一些符號常量。
SYS\TYPES.H 說明ftime函數和timeb結構。
SYS\TIME.H 定義時間的類型time[ZZ(Z] [ZZ)]t。
TIME.H 定義時間轉換子程序asctime、localtime和gmtime的結構,ctime、 difftime、 gmtime、 localtime和stime用到的類型,并提供這些函數的原型。
VALUE.H 定義一些重要常量, 包括依賴于機器硬件的和為與Unix System V相兼容而說明的一些常量,包括浮點和雙精度值的范圍。
本章小結
1. C系統(tǒng)把文件當作一個"流",按字節(jié)進行處理。
2. C文件按編碼方式分為二進制文件和ASCII文件。
3. C語言中,用文件指針標識文件,當一個文件被 打開時, 可取得該文件指針。
4. 文件在讀寫之前必須打開,讀寫結束必須關閉。
5. 文件可按只讀、只寫、讀寫、追加四種操作方式打開,同時還必須指定文件的類型是二進制文件還是文本文件。
6. 文件可按字節(jié),字符串,數據塊為單位讀寫,文件也可按指定的格式進行讀寫。
7. 文件內部的位置指針可指示當前的讀寫位置,移動該指針可以對文件實現隨機讀寫。
- 作者: dugujian 2004年12月17日, 星期五 08:28 回復(0) | 引用(0) 加入博采
第九章:預處理
第九章:預處理
預處理
概述
在前面各章中,已多次使用過以"#"號開頭的預處理命令。如包含命令# include,宏定義命令# define等。在源程序中這些命令都放在函數之外, 而且一般都放在源文件的前面,它們稱為預處理部分。
所謂預處理是指在進行編譯的第一遍掃描(詞法掃描和語法分析)之前所作的工作。預處理是C語言的一個重要功能, 它由預處理程序負責完成。當對一個源文件進行編譯時, 系統(tǒng)將自動引用預處理程序對源程序中的預處理部分作處理, 處理完畢自動進入對源程序的編譯。
C語言提供了多種預處理功能,如宏定義、文件包含、 條件編譯等。合理地使用預處理功能編寫的程序便于閱讀、修改、 移植和調試,也有利于模塊化程序設計。本章介紹常用的幾種預處理功能。
宏定義
在C語言源程序中允許用一個標識符來表示一個字符串, 稱為"宏"。被定義為"宏"的標識符稱為"宏名"。在編譯預處理時,對程序中所有出現的"宏名",都用宏定義中的字符串去代換, 這稱為"宏代換"或"宏展開"。
宏定義是由源程序中的宏定義命令完成的。 宏代換是由預處理程序自動完成的。在C語言中,"宏"分為有參數和無參數兩種。 下面分別討論這兩種"宏"的定義和調用。
無參宏定義
無參宏的宏名后不帶參數。其定義的一般形式為: #define 標識符 字符串 其中的"#"表示這是一條預處理命令。凡是以"#"開頭的均為預處理命令。"define"為宏定義命令。 "標識符"為所定義的宏名。"字符串"可以是常數、表達式、格式串等。在前面介紹過的符號常量的定義就是一種無參宏定義。 此外,常對程序中反復使用的表達式進行宏定義。例如: # define M (y*y+3*y) 定義M表達式(y*y+3*y)。在編寫源程序時,所有的(y*y+3*y)都可由M代替,而對源程序作編譯時,將先由預處理程序進行宏代換,即用(y*y+3*y)表達式去置換所有的宏名M,然后再進行編譯。
#define M (y*y+3*y)
main(){
int s,y;
printf("input a number: ");
scanf("%d",&y);
s=3*M+4*M+5*M;
printf("s=%d\n",s);
}
上例程序中首先進行宏定義,定義M表達式(y*y+3*y),在s= 3*M+4*M+5* M中作了宏調用。在預處理時經宏展開后該語句變?yōu)?#xff1a;s=3*(y*y+3*y)+4(y*y+3*y)+5(y*y+3*y);但要注意的是,在宏定義中表達式(y*y+3*y)兩邊的括號不能少。否則會發(fā)生錯誤。
當作以下定義后: #difine M y*y+3*y在宏展開時將得到下述語句: s=3*y*y+3*y+4*y*y+3*y+5*y*y+3*y;這相當于; 3y2+3y+4y2+3y+5y2+3y;顯然與原題意要求不符。計算結果當然是錯誤的。 因此在作宏定義時必須十分注意。應保證在宏代換之后不發(fā)生錯誤。對于宏定義還要說明以下幾點:
1. 宏定義是用宏名來表示一個字符串,在宏展開時又以該字符串取代宏名,這只是一種簡單的代換,字符串中可以含任何字符,可以是常數,也可以是表達式,預處理程序對它不作任何檢查。如有錯誤,只能在編譯已被宏展開后的源程序時發(fā)現。
2. 宏定義不是說明或語句,在行末不必加分號,如加上分號則連分號也一起置換。
3. 宏定義必須寫在函數之外,其作用域為宏定義命令起到源程序結 束。如要終止其作用域可使用# undef命令,例如: # define PI 3.14159
main()
{
......
}
# undef PIPI的作用域
f1()
....表示PI只在main函數中有效,在f1中無效。
4. 宏名在源程序中若用引號括起來,則預處理程序不對其作宏代換。
#define OK 100
main()
{
printf("OK");
printf("\n");
}
上例中定義宏名OK表示100,但在printf語句中OK被引號括起來,因此不作宏代換。程序的運行結果為:OK這表示把"OK"當字符串處理。
5. 宏定義允許嵌套,在宏定義的字符串中可以使用已經定義的宏名。在宏展開時由預處理程序層層代換。例如: #define PI 3.1415926
#define S PI*y*y /* PI是已定義的宏名*/對語句: printf("%f",s);在宏代換后變?yōu)?#xff1a; printf("%f",3.1415926*y*y);
6. 習慣上宏名用大寫字母表示,以便于與變量區(qū)別。但也允許用小寫字母。
7. 可用宏定義表示數據類型,使書寫方便。例如: #define STU struct stu在程序中可用STU作變量說明: STU body[5],*p;#define INTEGER int 在程序中即可用INTEGER作整型變量說明: INTEGER a,b; 應注意用宏定義表示數據類型和用typedef定義數據說明符的區(qū)別。宏定義只是簡單的字符串代換,是在預處理完成的,而typedef是在編譯時處理的,它不是作簡單的代換, 而是對類型說明符重新命名。被命名的標識符具有類型定義說明的功能。請看下面的例子: #define PIN1 int* typedef (int*) PIN2;從形式上看這兩者相似, 但在實際使用中卻不相同。下面用PIN1,PIN2說明變量時就可以看出它們的區(qū)別: PIN1 a,b;在宏代換后變成 int *a,b;表示a是指向整型的指針變量,而b是整型變量。然而:PIN2 a,b;表示a,b都是指向整型的指針變量。因為PIN2是一個類型說明符。由這個例子可見,宏定義雖然也可表示數據類型, 但畢竟是作字符
代換。在使用時要分外小心,以避出錯。
8. 對"輸出格式"作宏定義,可以減少書寫麻煩。例9.3 中就采用了這種方法。
#define P printf
#define D "%d\n"
#define F "%f\n"
main(){
int a=5, c=8, e=11;
float b=3.8, d=9.7, f=21.08;
P(D F,a,b);
P(D F,c,d);
P(D F,e,f);
}
帶參宏定義
C語言允許宏帶有參數。在宏定義中的參數稱為形式參數, 在宏調用中的參數稱為實際參數。對帶參數的宏,在調用中,不僅要宏展開, 而且要用實參去代換形參。
帶參宏定義的一般形式為: #define 宏名(形參表) 字符串 在字符串中含有各個形參。帶參宏調用的一般形式為: 宏名(實參表);
例如:
#define M(y) y*y+3*y /*宏定義*/
:
k=M(5); /*宏調用*/
: 在宏調用時,用實參5去代替形參y, 經預處理宏展開后的語句
為: k=5*5+3*5
#define MAX(a,b) (a>b)?a:b
main(){
int x,y,max;
printf("input two numbers: ");
scanf("%d%d",&x,&y);
max=MAX(x,y);
printf("max=%d\n",max);
}
上例程序的第一行進行帶參宏定義,用宏名MAX表示條件表達式(a>b)?a:b,形參a,b均出現在條件表達式中。程序第七行max=MAX(x,
y)為宏調用,實參x,y,將代換形參a,b。宏展開后該語句為: max=(x>y)?x:y;用于計算x,y中的大數。對于帶參的宏定義有以下問題需要說明:
1. 帶參宏定義中,宏名和形參表之間不能有空格出現。
例如把: #define MAX(a,b) (a>b)?a:b寫為: #define MAX (a,b) (a>b)?a:b 將被認為是無參宏定義,宏名MAX代表字符串 (a,b)(a>b)?a:b。
宏展開時,宏調用語句: max=MAX(x,y);將變?yōu)?#xff1a; max=(a,b)(a>b)?a:b(x,y);這顯然是錯誤的。
2. 在帶參宏定義中,形式參數不分配內存單元,因此不必作類型定義。而宏調用中的實參有具體的值。要用它們去代換形參,因此必須作類型說明。這是與函數中的情況不同的。在函數中,形參和實參是兩個不同的量,各有自己的作用域,調用時要把實參值賦予形參,進行"值傳遞"。而在帶參宏中,只是符號代換,不存在值傳遞的問題。
3. 在宏定義中的形參是標識符,而宏調用中的實參可以是表達式。
#define SQ(y) (y)*(y)
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=SQ(a+1);
printf("sq=%d\n",sq);
}
上例中第一行為宏定義,形參為y。程序第七行宏調用中實參為a+1,是一個表達式,在宏展開時,用a+1代換y,再用(y)*(y) 代換SQ,得到如下語句: sq=(a+1)*(a+1); 這與函數的調用是不同的, 函數調用時要把實參表達式的值求出來再賦予形參。 而宏代換中對實參表達式不作計算直接地照原樣代換。
4. 在宏定義中,字符串內的形參通常要用括號括起來以避免出錯。 在上例中的宏定義中(y)*(y)表達式的y都用括號括起來,因此結果是正確的。如果去掉括號,把程序改為以下形式:
#define SQ(y) y*y
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=SQ(a+1);
printf("sq=%d\n",sq);
}
運行結果為:input a number:3
sq=7 同樣輸入3,但結果卻是不一樣的。問題在哪里呢? 這是由于代換只作符號代換而不作其它處理而造成的。 宏代換后將得到以下語句: sq=a+1*a+1; 由于a為3故sq的值為7。這顯然與題意相違,因此參數兩邊的括號是不能少的。即使在參數兩邊加括號還是不夠的,請看下面程序:
#define SQ(y) (y)*(y)
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=160/SQ(a+1);
printf("sq=%d\n",sq);
}
本程序與前例相比,只把宏調用語句改為: sq=160/SQ(a+1); 運行本程序如輸入值仍為3時,希望結果為10。但實際運行的結果如下:input a number:3 sq=160為什么會得這樣的結果呢?分析宏調用語句,在宏代換之后變?yōu)?#xff1a; sq=160/(a+1)*(a+1);a為3時,由于"/"和"*"運算符優(yōu)先級和結合性相同, 則先作160/(3+1)得40,再作40*(3+1)最后得160。為了得到正確答案應在宏定義中的整個字符串外加括號, 程序修改如下
#define SQ(y) ((y)*(y))
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=160/SQ(a+1);
printf("sq=%d\n",sq);
}
以上討論說明,對于宏定義不僅應在參數兩側加括號, 也應在整個字符串外加括號。
5. 帶參的宏和帶參函數很相似,但有本質上的不同,除上面已談到的各點外,把同一表達式用函數處理與用宏處理兩者的結果有可能是不同的。main(){
int i=1;
while(i<=5)
printf("%d\n",SQ(i++));
}
SQ(int y)
{
return((y)*(y));
}#define SQ(y) ((y)*(y))
main(){
int i=1;
while(i<=5)
printf("%d\n",SQ(i++));
}
在上例中函數名為SQ,形參為Y,函數體表達式為((y)*(y))。在例9.6中宏名為SQ,形參也為y,字符串表達式為(y)*(y))。 兩例是相同的。例9.6的函數調用為SQ(i++),例9.7的宏調用為SQ(i++),實參也是相同的。從輸出結果來看,卻大不相同。分析如下:在例9.6中,函數調用是把實參i值傳給形參y后自增1。 然后輸出函數值。因而要循環(huán)5次。輸出1~5的平方值。而在例9.7中宏調用時,只作代換。SQ(i++)被代換為((i++)*(i++))。在第一次循環(huán)時,由于i等于1,其計算過程為:表達式中前一個i初值為1,然后i自增1變?yōu)?,因此表達式中第2個i初值為2,兩相乘的結果也為2,然后i值再自增1,得3。在第二次循環(huán)時,i值已有初值為3,因此表達式中前一個i為3,后一個i為4, 乘積為12,然后i再自增1變?yōu)?。進入第三次循環(huán),由于i 值已為5,所以這將是最后一次循環(huán)。計算表達式的值為5*6等于30。i值再自增1變?yōu)?,不再滿足循環(huán)條件,停止循環(huán)。從以上分析可以看出函數調用和宏調用二者在形式上相似, 在本質上是完全不同的。
6. 宏定義也可用來定義多個語句,在宏調用時,把這些語句又代換到源程序內??聪旅娴睦印?br />#define SSSV(s1,s2,s3,v) s1=l*w;s2=l*h;s3=w*h;v=w*l*h;
main(){
int l=3,w=4,h=5,sa,sb,sc,vv;
SSSV(sa,sb,sc,vv);
printf("sa=%d\nsb=%d\nsc=%d\nvv=%d\n",sa,sb,sc,vv);
}
程序第一行為宏定義,用宏名SSSV表示4個賦值語句,4 個形參分別為4個賦值符左部的變量。在宏調用時,把4 個語句展開并用實參代替形參。使計算結果送入實參之中。
文件包含
文件包含是C預處理程序的另一個重要功能。文件包含命令行的一般形式為: #include"文件名" 在前面我們已多次用此命令包含過庫函數的頭文件。例如:
#include"stdio.h"
#include"math.h"
文件包含命令的功能是把指定的文件插入該命令行位置取代該命令行, 從而把指定的文件和當前的源程序文件連成一個源文件。在程序設計中,文件包含是很有用的。 一個大的程序可以分為多個模塊,由多個程序員分別編程。 有些公用的符號常量或宏定義等可單獨組成一個文件, 在其它文件的開頭用包含命令包含該文件即可使用。這樣,可避免在每個文件開頭都去書寫那些公用量, 從而節(jié)省時間,并減少出錯。
對文件包含命令還要說明以下幾點:
1. 包含命令中的文件名可以用雙引號括起來,也可以用尖括號括起來。例如以下寫法都是允許的: #include"stdio.h" #include<math.h> 但是這兩種形式是有區(qū)別的:使用尖括號表示在包含文件目錄中去查找(包含目錄是由用戶在設置環(huán)境時設置的), 而不在源文件目錄去查找; 使用雙引號則表示首先在當前的源文件目錄中查找,若未找到才到包含目錄中去查找。 用戶編程時可根據自己文件所在的目錄來選擇某一種命令形式。
2. 一個include命令只能指定一個被包含文件, 若有多個文件要包含,則需用多個include命令。3. 文件包含允許嵌套,即在一個被包含的文件中又可以包含另一個文件。
條件編譯
預處理程序提供了條件編譯的功能。 可以按不同的條件去編譯不同的程序部分,因而產生不同的目標代碼文件。 這對于程序的移植和調試是很有用的。 條件編譯有三種形式,下面分別介紹:
1. 第一種形式:
#ifdef 標識符
程序段1
#else
程序段2
#endif
它的功能是,如果標識符已被 #define命令定義過則對程序段1進行編譯;否則對程序段2進行編譯。如果沒有程序段2(它為空),本格式中的#else可以沒有, 即可以寫為:
#ifdef 標識符
程序段 #endif
#define NUM ok
main(){
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu*)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
#ifdef NUM
printf("Number=%d\nScore=%f\n",ps->num,ps->score);
#else
printf("Name=%s\nSex=%c\n",ps->name,ps->sex);
#endif
free(ps);
}
由于在程序的第16行插入了條件編譯預處理命令, 因此要根據NUM是否被定義過來決定編譯那一個printf語句。而在程序的第一行已對NUM作過宏定義,因此應對第一個printf語句作編譯故運行結果是輸出了學號和成績。在程序的第一行宏定義中,定義NUM表示字符串OK,其實也可以為任何字符串,甚至不給出任何字符串,寫為: #define NUM 也具有同樣的意義。 只有取消程序的第一行才會去編譯第二個printf語句。讀者可上機試作。
2. 第二種形式:
#ifndef 標識符
程序段1
#else
程序段2
#endif
與第一種形式的區(qū)別是將"ifdef"改為"ifndef"。它的功能是,如果標識符未被#define命令定義過則對程序段1進行編譯, 否則對程序段2進行編譯。這與第一種形式的功能正相反。
3. 第三種形式:
#if 常量表達式
程序段1
#else
程序段2
#endif
它的功能是,如常量表達式的值為真(非0),則對程序段1 進行編譯,否則對程序段2進行編譯。因此可以使程序在不同條件下,完成不同的功能
#define R 1
main(){
float c,r,s;
printf ("input a number: ");
scanf("%f",&c);
#if R
r=3.14159*c*c;
printf("area of round is: %f\n",r);
#else
s=c*c;
printf("area of square is: %f\n",s);
#endif
}
本例中采用了第三種形式的條件編譯。在程序第一行宏定義中,定義R為1,因此在條件編譯時,常量表達式的值為真, 故計算并輸出圓面積。上面介紹的條件編譯當然也可以用條件語句來實現。 但是用條件語句將會對整個源程序進行編譯,生成的目標代碼程序很長,而采用條件編譯,則根據條件只編譯其中的程序段1或程序段2, 生成的目標程序較短。如果條件選擇的程序段很長, 采用條件編譯的方法是十分必要的。
本章小結
1. 預處理功能是C語言特有的功能,它是在對源程序正式編譯前由預處理程序完成的。程序員在程序中用預處理命令來調用這些功能。
2. 宏定義是用一個標識符來表示一個字符串,這個字符串可以是常量、變量或表達式。在宏調用中將用該字符串代換宏名。
3. 宏定義可以帶有參數,宏調用時是以實參代換形參。而不是"值傳送"。
4. 為了避免宏代換時發(fā)生錯誤,宏定義中的字符串應加括號,字符串中出現的形式參數兩邊也應加括號。
5. 文件包含是預處理的一個重要功能,它可用來把多個源文件連接成一個源文件進行編譯,結果將生成一個目標文件。
6. 條件編譯允許只編譯源程序中滿足條件的程序段,使生成的目標程序較短,從而減少了內存的開銷并提高了程序的效率。
7. 使用預處理功能便于程序的修改、閱讀、移植和調試,也便于實現模塊化程序設計。
轉載于:https://www.cnblogs.com/hcmfys/archive/2009/04/08/1432052.html
總結
以上是生活随笔為你收集整理的c study_13的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DELPHI之备忘(二)
- 下一篇: js的oop方式和this指针问题