C語言文件讀寫(4)-判斷文件是否結束

C語言文件讀寫-判斷文件是否結束

在讀文件的時候,很重要的一個條件是:文件是否結束,因爲文件已經結束了,就不用繼續讀文件了。

判斷文件結束,有許多方式,比如讀取文本文件和二進制文件就可以使用不同的方式,下面分別進行詳細介紹和舉例。

使用EOF判斷文件是否結束

EOF的值爲-1,所以往往使用EOF也可以判斷文件是否結束,一般用在非格式化文本文件讀取中,如果在格式化文本讀取時使用EOF來判斷,在某些情況下是會出錯的。

函數fgetc返回的值爲一個字符,當文件結束時,返回EOF,因爲文本文件中可打印字符沒有字符的值是-1,所以,可以用EOF來判斷文件是否結束了。

這也是唯一的一個可以使用EOF來判斷文件是否結束,而且永遠正確的函數,前提是必須是讀文本文件(格式化或者非格式化都可以支持)。

我們來看一下使用fgetc和EOF來檢測文件是否結束的例子,代碼如下:

void EOF_test_getc(const char* file_name)
{
	int ch = 0;
	int count = 0;
	FILE *file = fopen(file_name,"r");
	if(!file)
		return;
	while(1)
	{
		ch = fgetc(file);
		if(ch == EOF)
		{
			printf("reach the end of file,the char number is %d\n",count);
			break;
		}
		else
		{
			count++;
			putchar(ch);
		}
	}
	fclose(file);
}

在代碼中,我們使用fgetc來讀取文件中的每一個字符,如果讀取到的字符是EOF,則結束讀取,每讀取一個字符,就對count++,以統計文件中字符的個數。運行效果如圖所示。

但是出現了一個奇怪的問題,程序中統計出來的字符是98個,但是文件大小卻是102個字節,相差了4個字符,那4個字符到哪裏去了?

《C語言文件讀寫(1)-文本文件讀操作》

中提到,在Windows上,如果寫入行結束符'\n',系統會自動替換爲'\r\n',在讀取的時候,會自動把'\r\n'轉換爲'\n',因爲我們這個文件有4行,所以文件中就多了4個'\r',這就是爲什麼文件的實際大小會比讀取出來的字符多了4個的原因。

注意,這種判斷文件結束的方式只能針對文本文件,不能用在二進制文件上面,因爲二進制文件的內容什麼值都可以存儲,-1也會是其中的一個值。

fscanf和fscanf_s有時候也可以用EOF來判斷文件是否結束,而且在前面的幾篇文章中也確實使用了這種方式來判斷,但是,如果文本文件中的內容一旦有一點錯,scanf中的format字符串不能匹配,則永遠都不會返回EOF這個值,就會造成死循環,所以在使用fscanf或者fscanf_s的時候,不推薦使用返回值EOF來判斷文件是否結尾,而是使用feof函數來檢測,後面會介紹。

 

使用是否爲NULL來判斷

函數fgets用來獲取一行的文本文件數據,如果返回爲NULL,則表示文件結束了或者讀取錯誤,所以有時候也可以使用返回值是否爲NULL來判斷文件是否結束,示例代碼如下:

void read_text_by_gets(const char* file_name)
{
	char buffer[128]={0};
	FILE *file = fopen(file_name,"rt");
	if(!file)
		return;
	while(NULL != (fgets(buffer,sizeof(buffer),file)))
	{
		//顯示每一次讀取到的內容
		printf("%s",buffer);
	}
	fclose(file);
}

這在絕大多數的時候都可以判斷文件是否真的結束了,但是,如果讀取文件出錯了,也會返回NULL,這就不能區分是文件真的結束了,還是由於別的原因讀取錯誤,所以,並不推薦用這種方式來檢測文件是否結束。

通過返回值的大小來判斷

我們在使用fread來讀取二進制文件的時候,往往可以通過返回值是否等於count的值來判斷文件是否結束,大多數情況下是沒有問題的,示例代碼如下:


void read_binary_file(const char* file_name)
{
	struct Student stu={0};
	FILE *file = fopen(file_name,"rb");
	if(!file)
		return;
	while(fread(&stu,sizeof(stu),1,file) == 1)
	{
		printf("學號:%d 姓名:%s 學院:%s 分數:%.2f\n",stu.ID,stu.Name,stu.College,stu.Score);		
	}
	fclose(file);
}

在示例代碼中,fread因爲我們每次只讀一個Student的大小,所以如果返回的值小於1的話,則說明文件結束了。

同樣的原因,fread如果出錯了的話,返回值也是可能小於1的,因此這個判斷也不是100%準確的。

 

使用feof來判斷

feof的原型爲:

int feof( FILE *stream );

   
   

返回值是一個整數,如果爲0,表示文件沒有結束,如果非0,表示文件結束。

但是要特別注意一點的是,這個函數的實現機制是這樣的:

是在下一次讀取的時候判斷是否到了文件末尾,如果是,則設置文件結束標誌,它並不是在調用feof這個函數的時候去檢查數據來判斷是否到了文件的末尾。所以這個函數的使用,很多朋友都會用錯,以爲這是一個真正檢查文件是否結束的函數,當然,它確實是,但是它不是即時的。

我們先看一下文本文件的例子,仍然以剛纔的student.txt爲例,代碼如下:

void feof_test_getc(const char* file_name)
{
	int ch = 0;
	int count = 0;
	FILE *file = fopen(file_name,"r");
	if(!file)
		return;
	while(!feof(file))
	{
		count++;
		ch = fgetc(file);
		putchar(ch);
	}
	printf("reach the end of file,the count is %d\n",count);
	fclose(file);
}
int main(int argc, char* argv[])
{
	feof_test_getc("student.txt");
        return 0;
}

這個代碼幾乎是我遇到的所有初學C語言甚至是學了C語言很長時間的朋友的寫法,確實,看起來沒有任何問題,我們來看一下運行結果,如圖所示。

 

請注意,count的值是99,而且最後一行還多輸出一個字符,這就是爲什麼在論壇上會經常看到爲什麼會多出一行,多出一個之類的討論。

這確實就是缺乏對feof的理解造成的,我們以爲feof就是一個實時監測文件是否結尾的函數,其實並不是,當你使用fgetc或者任何別的函數讀取文件內容的時候,如果讀取的內容剛好是文件的最後一個字符,這個時候調用feof的話,返回仍然是0,因爲它並不會去真的檢查文件是否結尾,它要依賴下一次的讀取操作,下一次再次讀取的時候,發現文件已經到了結尾,則設置文件結束標誌,feof調用的時候纔會返回非0值。

 

現在來演示一下一個代碼,可以更好地理解feof的工作機制,代碼如下:

void feof_test()
{
	int test = 0;
	char ch = 't';
	char str[20]="test";
	FILE *file1,*file2,*file3;
	//第一種情況
	file1=fopen("test1.dat","w+");
	fprintf(file1,"%c",ch);
	rewind(file1);
	fscanf(file1,"%c",&ch);
	if(feof(file1))
	{
		printf("文件1結束\n");
	}
	else
	{
		printf("文件1沒有結束\n");
	}
	fclose(file1);
	//第二種情況
	file2=fopen("test2.dat","w+");
	fprintf(file2,"%d",10);
	rewind(file2);
	fscanf(file2,"%d",&test);
	if(feof(file2))
	{
		printf("文件2結束\n");
	}
	else
	{
		printf("文件2沒有結束\n");
	}
	fclose(file2);
	//第三種情況
	file3=fopen("test3.dat","w+");
	fprintf(file3,"%s",str);
	rewind(file3);
	fscanf(file3,"%s",str);
	if(feof(file3))
	{
		printf("文件3結束\n");
	}
	else
	{
		printf("文件3沒有結束\n");
	}

	fclose(file3);

}

 在這個測試中,寫入三個文件,然後分別打開對應的文件從頭開始讀取,然後判斷feof是否返回非0值,其中第一種情況,返回0,第二種和第三種返回非0。

先看一下運行結果,再來解釋爲什麼。結果如圖所示。

爲什麼會產生這個差別呢?先說第一種情況,寫入了一個字符,然後讀取一個字符,雖然文件中只有一個字符,但是讀取一個字符就夠了,所以並不知道文件是否結束。

第二種情況,寫入數字10到文件,然後讀取一個整數,爲什麼就檢查到文件結束了呢?因爲讀取一個整數的時候,會一直讀,直到讀取到空格或者別的結束符或者超過整數範圍的時候纔會結束,因爲文件中存的是10,所以會一直讀到文件結束,10是沒有超過整數範圍的。

第三種情況和第二種情況一樣,讀取一個字符串的時候也要一直讀,直到遇到空格或者tab之類的,由於文件中只有test這四個字符,自然就會讀到文件尾了。

所以第二和第三種情況都會返回非0值,表示文件結束了。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章