簡介
儲存在變數的資料只是暫時的,想要在程式結束時儲存資料就必須靠檔案
這個章節要介紹的是如何操作檔案
fopen,fclose
如果要處理檔案要用fopen來開啟檔案,fopen函數的原型宣告如下:
1
| FILE* fopen (const char*, const char*);
|
fopen的第一個參數是檔案名稱(路徑),第二個參數是打開檔案的方式
以下表格是檔案打開的方式
模式 |
說明 |
r |
開啟檔案進行唯讀,若檔案不存在,則傳回NULL |
w |
開啟檔案進行唯寫,若檔案不存在,則建立新檔,若檔案存在則將之刪除,再建立新檔 |
a |
開啟檔案進行附加,若檔案存在,則資料從檔案尾端寫入,若檔案不存在則建立新檔 |
rb |
以二進位模式開啟檔案進行唯讀 |
wb |
以二進位模式開啟檔案進行唯寫 |
ab |
以二進位模式開啟檔案進行附加 |
r+ |
開啟檔案進行讀寫,若檔案不存在,則建立新檔,若檔案存在,資料將從檔案開頭進行覆寫 |
w+ |
開啟檔案進行讀寫,若檔案不存在,則建立新檔,若檔案存在則覆寫原有的資料 |
a+ |
開啟檔案進行附加、讀取,若檔案不存在則建立新檔,若檔案存在,則資料從檔案尾端寫入 |
r+b |
以二進位方式開啟檔案進行讀寫 |
w+b |
以二進位方式開啟檔案進行讀寫 |
a+b |
以二進位方式開啟檔案進行附加、讀取 |
fopen會使用緩衝區來減少I/O,以提高效率,所以在讀寫的過程中其實是對緩衝區做讀寫
使用檔案的好習慣就是不使用時就要使用fclose關閉,參數為檔案指標,以下為原型宣告
如果檔案正常關閉則回傳0,不正常關閉則回傳非0的數字
fgetc,fputc,fgets,fputs
開啟檔案後可以用fgetc讀取檔案的一個字元,用fputc輸出一個字元
函數宣告原型如下
1 2
| int fgetc(FILE* fp); int fputc(int ch, FILE *fp);
|
fgetc傳檔案指標進去就可以取得該檔案的一個字元,直到檔案結尾EOF(End Of File)
可以像下面這樣判斷檔案結尾
1 2 3 4 5 6 7 8
| char ch; ch = fgetc(file); while( ch != EOF ) { ... ... ... ch = fgetc(file); }
|
fputc傳一個字元跟檔案指標就能寫一個字元進去
而如果想一次處理一行字串就要使用fgets、fputs,函數宣告原型如下
1 2
| char* fgets(char *str, int length, FILE *fp); int fputs(char *str, FILE *fp);
|
fgets第一個參數是要儲存的字串位址,第二個是要讀幾個字,由於最後一個字要是’\0’
所以真正的長度為length-1,第三個參數是檔案指標
fputs第一個參數是要輸出的字串,第二個參數是要輸出到哪個檔案
以下程式是示範用fgetc,fputc複製檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <stdio.h> int main() { FILE *file1 = fopen("source.txt", "r"); if(!file1) { puts("來源檔案開啟失敗"); return 1; } FILE *file2 = fopen("target.txt", "w"); if(!file2) { puts("目的檔案開啟失敗"); return 1; }
char ch; ch = fgetc(file1); while(ch != EOF) { fputc(ch, file2); ch = fgetc(file1); } fclose(file1); fclose(file2); return 0; }
|
以下程式是示範用fgets,fputs複製檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include <stdio.h> int main() { FILE *file1 = fopen("source.txt", "r"); if(!file1) { puts("來源檔案開啟失敗"); return 1; } FILE *file2 = fopen("target.txt", "w"); if(!file2) { puts("目的檔案開啟失敗"); return 1; }
char str[50]; while(fgets(str, 50, file1) != NULL) { fputs(str, file2); } fclose(file1); fclose(file2); return 0; }
|
fscanf,fprintf
檔案操作也可以做格式化,以下是原型宣告
1 2
| int fprintf(FILE *fp, char *formatstr, arg1, arg2, ...); int fscanf(FILE *fp, char *formatstr, arg1, arg2, ...);
|
除了第一個參數要給檔案指標以外,其餘跟scanf,printf一樣
以下是範例程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <stdio.h> int main() { char ch;
FILE *file = fopen("test.txt", "w"); if(!file) { puts("無法寫入檔案"); return 1; }
fprintf(file, "%s\t%d\r\n", "AAA", 100); fprintf(file, "%s\t%d\r\n", "BBB", 90); fprintf(file, "%s\t%d\r\n", "CCC", 80);
fclose(file);
file = fopen("test.txt", "r");; if(!file) { puts("無法讀入檔案"); return 1; } char name[10]; int score; puts("名字\t分數"); while(fscanf(file, "%s\t%d", name, &score) != EOF) { printf("%s\t%d\n", name, score); }
fclose(file);
return 0; }
|
fread,fwrite
使用二進位模式讀寫檔案就需要用fread,fwrite
讀寫二進位檔案都是使用位元組(Byte)為單位的區塊(Block),以下為函數原型宣告
1 2
| int fread(char *buffer, int size, int count, FILE *fp); int fwrite(char *buffer, int size, int count, FILE *fp);
|
fread的意思是將count個元素從fp檔案中讀出到buffer,每個元素大小為size位元組
fwrite的意思是從buffer寫入count個元素到fp檔案中,每個元素大小為size位元組
以下為複製檔案的範例程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <stdio.h> int main() {
FILE *file1 = fopen("source.txt", "rb"); FILE *file2 = fopen("target.txt", "wb");
if(!file1) { puts("檔案讀入失敗"); return 1; }
if(!file2) { puts("檔案輸出失敗"); return 1; } char ch;
while(!feof(file1)) { fread(&ch, sizeof(char), 1, file1); if(!feof(file1)) { fwrite(&ch, sizeof(char), 1, file2); } }
fclose(file1); fclose(file2); return 0; }
|
fseek
開啟檔案的時候,會有一個指標指向檔案開頭,每次讀取幾個字元,就會往後位移
C語言提供fseek來位移這個指標,方便我們要從任一地方讀取檔案
以下為fseek的函數原型宣告
1
| int fseek(FILE *fp, long offset, int mode);
|
第一個參數是檔案,第二個參數為偏移量,第三個參數是從哪裡位移
以下是位移模式的說明
位移模式 |
說明 |
SEEK_SET |
檔案開頭 |
SEEK_CUR |
目前游標所在位置 |
SEEK_END |
檔案結尾 |
這種檔案操作稱為非循序的檔案存取,一般會使用二進位模式搭配自定的資料結構
以下是範例程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include <stdio.h> struct Employee{ char name[30]; int age; char gender; double salary; }; void printPrompt(); int main() {
FILE *file = fopen("data.bin", "wb"); int count; printf("要建立幾筆資料? "); scanf("%d", &count); struct Employee employee = {"",0,'\0',0.0}; int i; for(i = 0; i < count; i++) fwrite( &employee, sizeof(employee), 1, file); fclose(file); count = 0; file = fopen("data.bin", "r+b"); if(!file) { puts("無法讀取檔案"); return 1; } while(1) { fread(&employee, sizeof(employee), 1, file); if(!feof(file)) { count++; } else { break; } }
rewind(file); printf("輸入序號(1-%d)\n", count); puts("輸入0離開"); int id; while(1) { printf("\n序號? "); scanf("%d", &id); if(id == 0) { break; } printf("輸入名字 年齡 性別 薪水\n=> "); scanf("%s %d %c %lf", employee.name, &(employee.age), &(employee.gender), &(employee.salary));
fseek(file, (id - 1) * sizeof(employee), SEEK_SET); fwrite(&employee, sizeof(employee), 1, file); printf("輸入序號(1-%d)\n", count); puts("輸入0離開"); }
fclose(file); return 0; }
|
執行結果
要建立幾筆資料? 5
現在開始輸入資料
輸入序號(1-5)
輸入0離開
序號? 1
輸入名字 年齡 性別 薪水
=> Gundam 20 M 81000
輸入序號(1-5)
輸入0離開
序號? 5
輸入名字 年齡 性別 薪水
=> Box 20 M 22000
輸入序號(1-5)
輸入0離開
序號? 0
現在輸出所有資料
名字 年齡 性別 薪水
Gundam 20 M 81000.00
0 0 0.00
0 0 0.00
0 0 0.00
Box 20 M 22000.00
freopen
freopen跟fopen不一樣的地方是fopen是打開檔案
freopen是重新導向串流(stream)直到程式結束為止,以下是函數原型宣告
1
| FILE * freopen ( const char * filename, const char * mode, FILE * stream );
|
第一個參數是檔案名稱,第二個是檔案模式,第三個則是要重新導向的串流
串流分為stdin(標準輸入)、stdout(標準輸出)、stderr(標準錯誤)
以下是範例程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <stdio.h> int main() { freopen("source.txt","r",stdin); freopen("target.txt","w",stdout); char str[50]; while(gets(str) != NULL) { puts(str); } return 0; }
|
參考
- C語言
- 未格式化檔案 I/O
- 格式化檔案 I/O
- 二進位檔案 I/O
- 資料流游標
- 隨機存取檔案