1. 程式人生 > >C語言檔案的輸入/輸出

C語言檔案的輸入/輸出

    對檔案的輸入/輸出學習是C語言中的一塊重要內容,因為當你的程式變得複雜時,難免要處理一些檔案,涉及到檔案的讀取和寫入。C語言提供了強大的檔案輸入/輸出功能,其標準I/O包中包含了很多專用的函式,可以很方便地讀取和寫入檔案,我將在下面介紹幾種常用的檔案I/O函式,並最後給出一個模擬壓縮檔案的例子。

    在介紹函式之前,首先要說明C語言處理輸入和輸出,是採用“流“的形式,而且常常會有緩衝區,緩衝區的存在可以提高輸入/輸出處理的高效性,舉個簡答的例子,如果沒有緩衝,下面這個程式碼的執行效果會很尷尬!

while((ch=getchar())!='\n')
	putchar(ch);
putchar('\n');


    當你輸入“abc[回車的時候]”,螢幕顯示的是"aabbcc",因為沒有緩衝的存在,一輸入馬上得到輸出。那麼如何理解檔案流的形式,可以這麼理解,字元的輸入就像注入一段水流,這些呼叫函式就在岸邊去抽取自己想要的輸入,然後顯示,我以一個常見的小錯誤程式碼來說明
char ch;
int num1, num2;

while((ch=getchar())!='\n'){
	putchar(ch);
	scanf("%d%d", &num1, &num2);
	printf("%d %d", num1, num2);
}
putchar('\n');

    當你輸入“a 42 24[回車]”的時候,這段字元序列“a【空格】42【空格】24【回車】”就向水流一樣流進緩衝區,其中a被ch=getchar()捕獲,然後進入迴圈,由於scanf()自動跳過空白字元,所以捕獲兩個整型量42和24,遇到【回車】會重新整理緩衝區,所以螢幕輸出“a42 24”,然後就結束迴圈了,因為[回車]被ch=getchar()捕獲,不滿足迴圈條件,跳出迴圈。

    所以大家在分析輸入輸出的時候也可以照著這種方法去分析,其實良好的輸入輸出功能是一個是需要程式設計師很細心的去處理,涉及到很多細節。

    接下來,介紹三個標準檔案:stdin,stdout,stderr。可以將這3個檔案理解成C語言自帶的用於處理鍵盤輸入,螢幕顯示,標準錯誤的立即顯示。stdin表示標準輸入,通常指鍵盤輸入,stdout表示標準輸出,通常指螢幕顯示,stderr表示標準錯誤,不經過緩衝區,直接輸出到螢幕,舉幾個簡單例子:

fprintf(stdout, "Hello World!\n");
printf("Hello World!\n");

fgets(char_array, MAX_SIZE, stdin);

    上述fprintf()語句和printf()功能相同,都是向螢幕輸出"Hello World!", fgets()函式則是將鍵盤輸入的字串放到容量為MAX_SIZE大小的字元陣列中。

    通過上述描述,初學者大概對輸入輸出流有了一個基本的概念,也瞭解了C語言的3個標準檔案。其實大家可以發現C語言本質是把鍵盤輸入,螢幕輸出當成檔案來處理的,這和UNIX和Linux的理念很像,一切裝置均被視為檔案。因為C語言的兩位創始人Dennis M. Ritchie和Brain W. Kernighan也是UNIX的創始人啊,真的是佩服的五體投地,大家感興趣的話可以查一查UNIX和C語言的歷史,Linux又和UNIX很有淵源呢!

    標準I/O包中含有很多個函式,這裡介紹常用的幾個:

    fopen()函式,第一個引數是要開啟的檔名(包含該檔名的字串的地址),第二個引數是用於指定檔案開啟模式的一個字串。模式字串包括:“r”,“w”,“a”,“r+”,“w+”,“a+”,其中r表示只讀,w表示寫入,a表示追加,若帶有+則表示即可讀取也可寫入,尤其要注意w引數,是將指定檔案的原有內容清空,重新寫入,若是希望對原有檔案進行修改(保留原有檔案內容),需要使用“a”引數;該函式的返回值為FILE型別的指標。

    fclose()函式,引數只有一個,即FILE型別的指標指定的檔案,其功能為關閉檔案,並重新整理緩衝區,檔案成功關閉返回0,否則返回EOF。

    fseek()定位檔案當前的位置,引數為3個,第1個為表示檔案的FILE型別指標,第2個為偏移量(long型別,正數表示向前偏移,負數表示向後偏移),第3個為起始點模式(可以稱之為基準,共有3個,SEEK_SET表示檔案的開始,SEEK_CUR表示檔案當前位置,SEEK_END表示檔案的結尾)

    fwrite()函式將二進位制資料寫入檔案,其函式原型為size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp); 舉例來說:要寫入10個double型別的資料,可以這樣做:

    double number[10];

    fwrite(number, sizeof(double), 10, fp);

    與fwrite()函式對應,fread()函式從檔案中讀取相應的資料到制定目標中,其函式原型為: size_t fread(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);用法類似與fread(),只不過是從檔案中讀取相應的資料。

    接下來,我以一個模擬壓縮的程式來說明檔案的I/O讀取。壓縮是一個很高深的學問,我這裡只是提一個最最簡單的壓縮思路:如果有一串重複性很高的序列,比如0000000001111110000001111,那麼我將其表示成09160614,其中9表示0的個數,6表示1的個數,6表示0的個數,4表示1的個數,這樣是不是就可以實現壓縮了呢。我給出這個模擬壓縮程式的程式碼如下:

/*================================================

 # Author:      [email protected]    [email protected]
 # Filetype:    C source code
 # Environment: Linux & Ubuntu 14.04
 # Tool:        Vim & Gcc
 # Date:        Wed Aug 24 2016 
 # Descprition: condense the image!

================================================*/

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>

#define COMMAND_SIZE 80

FILE * create_image(char *);
void get_command(char [], char *, char *);
void condense_image(FILE *, FILE *);

int main(int argc, char *argv[])
{
	if(argc!=3){
		printf("Input error! The usage: %s source_file condensed_file\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	char command[COMMAND_SIZE] = "ls -l $(pwd) | egrep ";
	get_command(command, argv[1], argv[2]);

	FILE *source_file, *condensed_file;
	
	source_file = create_image(argv[1]);
	condensed_file = fopen(argv[2], "w");

	condense_image(source_file, condensed_file);

	fclose(source_file);
	fclose(condensed_file);

	system(command);
	printf("Bye!\n");

	return 0;
}

FILE * create_image(char *filename)
{
	FILE *fp;
	int lsize, wsize, i, j;

	printf("Enter the image size (length * width): ");
	scanf("%d%d", &lsize, &wsize);

	char image[lsize][wsize];

	srand((unsigned int)time(NULL));
	for(i=0;i<lsize;i++)
		for(j=0;j<wsize;j++)
			image[i][j] = rand() % 2 + '0';
	
	fp = fopen(filename, "w+");
	if(fp==NULL){
		printf("Create %s error!\n", filename);
		exit(EXIT_FAILURE);
	}

	for(i=0;i<lsize;i++){
		fwrite(image[i], sizeof(char), wsize, fp);
		putc('\n', fp);
	}

	return fp;
}

void get_command(char command[COMMAND_SIZE], char *str1, char *str2)
{
	strcat(command, "\"");
	strcat(command, str1);
	strcat(command, "|");
	strcat(command, str2);
	strcat(command, "\"");
	
	return;
}

void condense_image(FILE *source, FILE *target)
{
	int ch, prev, count;

	count = 1;
	fseek(source, 0L, SEEK_SET);
	
	prev = getc(source);
	while((ch=getc(source))!=EOF){
		if(ch=='\n'){
			putc('\n',target);
			continue;
		}

		if(ch!=prev){
			putc(prev, target);
			fprintf(target, "%d", count);
			count = 1;
		}
		else
			count++;
		prev = ch;
	}
	fseek(target, -1L, SEEK_END);
	putc(prev, target);
	fprintf(target, "%d", count);

	return;
}

    上述程式的功能是使用者通過命令列引數輸入兩個檔名,執行程式時,先建立模擬數字影象的檔案,全為01序列,影象大小由使用者輸入,然後將該檔案通過上述思路壓縮,另存為壓縮檔案,最後程式在螢幕上列出新建兩個檔案的資訊。

    注意上述程式中使用到了C語言的命令列引數,常規main(void),若要使用命令列引數,則為main(int argc, char *argv[]),其中argc表示引數的個數,argv為指向字元的指標陣列,另外為了確保已經產生了兩個新建檔案,呼叫了system()函式,可以執行Linux的系統命令,列出兩個新建的檔案。

    執行結果如下所示:

    用Vim同時開啟這兩個檔案,顯示如下:


    如果讀者對程式碼有什麼意見或建議,歡迎郵箱或者留言探討!