檔案操作
目錄
??????一、我們為什么需要檔案?
二、究竟什么才是檔案
程式檔案
資料檔案
三、檔案名
四、檔案型別
五、檔案緩沖區
六、檔案指標
七、打開檔案和關閉檔案
fopen
fclose
八、檔案順序讀寫表
流(stream)
fputc
fgetc
fgets
fputs
fprintf
fscanf
對比一組函式
fwrite
fread
九、檔案隨機讀寫
fseek
ftell
rewind
十、檔案讀取結束的判定
一、我們為什么需要檔案?
由于我們之前所撰寫的程式在執行程序中所產生的資料及結果都只是臨時存放在記憶體區域,一旦程式運行結束后,該程式所開辟的記憶體空間將全部回傳給作業系統,那么此時我們再想要去查看之前的資料和執行結果,顯示是不可能的,
那該如何解決這個問題呢?
我們需要用到的就是——檔案, 沒錯,檔案 的作用就是可以將程式運行程序中所產生的資料結果都存盤下來,即便程式結束運行,我們依然可以通過 檔案 來找到這些資料,甚至再次運行程式時依然能使用這些資料,
二、究竟什么才是檔案
準確的來說:計算機檔案就是存盤在計算機硬碟、磁盤上的資訊集合,例如熟悉的圖片檔案、視頻檔案、程式等等,
檔案有各種各樣的型別,但是在程式設計中,我們一般談的檔案有兩種:程式檔案、資料檔案
程式檔案
包括源程式檔案(后綴為.c ), 目標檔案(windows環境后綴為.obj), 可執行程式(windows環境后綴為.exe),
資料檔案
檔案的內容不是程式,而是程式運行時讀寫的資料,比如程式運行需要從中讀取資料的檔案,或者輸出內容的檔案,
這次我們便圍繞資料檔案進行討論說明,
三、檔案名
一個檔案要有一個唯一的檔案標識,以便用戶識別和參考,
檔案名包含3部分∶檔案路徑 + 檔案名主干 + 檔案后綴
例如︰ c : \code\text.txt
檔案路徑: c : \code
檔案名主干: test
檔案后綴: .txt當然為了方便起見,檔案標識常被成為 檔案名,檔案名主干 的命名規則遵循識別符號的命名規則,
但是我們要了解此時所稱呼的檔案名,實際上包括以上3部分內容,而不僅僅是檔案名主干,
這邊我來舉例一些常見的檔案后綴方便大家去了解檔案的性質:
txt(文本檔案)dat(資料檔案)doc(Word生成的檔案)xlsx(excel生成的檔案)exe(可執行檔案)jpg(圖片檔案)等……
四、檔案型別
那么我們如何來區分檔案型別呢?
首先根據資料的組織形式,資料檔案被稱為了 文本檔案 或 二進制檔案,
而資料在記憶體中以二進制的形式存盤時,如果不加轉換的輸出到外存中去,那么就是 二進制檔案,
反之,如果在外存上以ASCII碼的形式進行存盤,如果在存盤到外存前就轉換,那么就是 文本檔案,
一個資料如何在記憶體中存盤的?
字符在記憶體中一律是以ASCII碼值形式進行存盤的,而數值型資料既可以用ASCII碼值形式存盤,也可以使用二進制形式進行存盤,
例:
#include <stdio.h>
int main()
{
int a = 10000;
FILE *pf = fopen("eg.txt", "wb");
//fopen 打開名為test.txt的檔案
//wb 以二進制的形式寫入檔案
fwrite(&a, 4, 1, pf);//寫一個四個位元組的資料放到以pf維護的檔案中去
fclose(pf);
pf = NULL;
return 0;
}
我們以二進制編輯器打開我們所創建的 eg.txt 檔案中去:

我們可以看出這是以16進制形式顯示的,并且由于編譯器是按照小端進行存盤的,
所以應該為 00 00 27 10
轉換成二進制就是 00000000 00000000 00100111 00010000
圖解:

五、檔案緩沖區
我們來了解一下什么是檔案緩沖區:
按照ANSIC 標準采用“緩沖檔案系統”處理的資料檔案的,所謂緩沖檔案系統是指系統自動地在記憶體中微程式中每一個正在使用的檔案開辟一塊“檔案緩沖區”,從記憶體向磁盤輸出資料會先送到記憶體中的緩沖區,裝滿緩沖區后才一起送到磁盤上,如果磁盤向計算機讀入資料,則從磁盤檔案中讀取資料輸入到記憶體緩沖區(充滿緩沖區),然后再從緩沖區逐個地將資料送到程式資料區(程式變數等),而緩沖區的大小根據C編譯系統決定的,
圖解:

六、檔案指標
緩沖檔案系統中, 關鍵的概念是 “檔案型別指標” , 簡稱 “檔案指標” ,每個被使用的檔案都在記憶體中開辟一個相應的檔案資訊區,用來存放檔案的有關資訊(如檔案的名字、檔案狀態及檔案當前位置等),這些資訊是保存在一個結構體變數中的,
該結構體型別是由系統宣告的,取名為 FILE ,
例:
//vs2013編譯環境提供的stdio.h頭檔案的檔案型別申明
struct _iobuf
{
char* _ptr;
int _cnt;
char* _base;
int _f1ag;
int _file;
int _charbuf;
int _bufsiz;
char* _tmpfname;
};
typedef struct _iobuf FILE;
- 當然不同的C編輯器它所對應的FILE型別包含的內容也不完全相同,但從根本上大同小異,
- 每當打開一個檔案時,系統會根據檔案的情況自動創建一個FILE結構的變數,并填充其中的資訊,使用時我們不必在意這些細節,
- 一般情況下通過FILE指標來維護這個FILE結構的變數,這樣使用起來會更加便捷,
例:(創建FILE*的指標變數)
FILE *pf;//檔案指標變數
定義 pf 是一個指向 FILE 型別資料的指標變數,可以使 pf 指向某個檔案的檔案資訊區(是一個結構體變數),通過該檔案資訊區中的資訊就能夠訪問該檔案,也就是說,通過檔案指標變數能夠找到與它關聯的檔案
圖解:

七、打開檔案和關閉檔案
我們都知道,檔案在讀寫之前應該想先打開檔案,在使用結束后應該關閉檔案,
在撰寫程式的時候,在打開檔案的同時,都會回傳一個 FILE* 的指標變數指向該檔案,也相當于建立了指標和檔案的關系,
ANSIC規定使用 fopen 函式來打開檔案,fclose 來關閉檔案,
fopen
//函式原型
FILE* fopen(const char* filename, const char* mode);
函式功能: Open a file.(打開檔案)
回傳型別: Each of these functions returns a pointer to the open file. A null pointer value indicates an error.(如果打開成功,回傳指向檔案資訊區的指標,如果回傳失敗,回傳空指標NULL)
函式引數1:filename(檔案名,實際上包括3部分內容,而不僅僅是檔案名主干,如果檔案路徑未寫,則默認本路徑)
函式引數2:Type of access permitted(檔案打開方式)
檔案打開方式表:
| 檔案使用方式 | 含義 | 如果指定檔案不存在 |
| "r"(只讀) | 為了輸入資料,打開一個已經存在的文本檔案 | 出錯 |
| "w"(只寫) | 為了輸出資料,打開一個文本檔案 | 建立一個新檔案 |
| "a"(追加) | 向文本檔案尾添加資料 | 出錯 |
| "rb"(只讀) | 為了輸入資料,打開一個二進制檔案 | 出錯 |
| "wb"(只寫) | 為了輸出資料,打開一個文進制檔案 | 建立一個新檔案 |
| "ab"(追加) | 向一個二進制檔案尾添加資料 | 出錯 |
| "r+"(讀寫) | 為了讀和寫,打開一個文本檔案 | 出錯 |
| "w+"(讀寫) | 問了讀和寫,建立一個新的檔案 | 建立一個新檔案 |
| "a+"(讀寫) | 打卡一個檔案,在檔案尾進行讀寫 | 建立一個新檔案 |
| "rb+"(讀寫) | 為了讀和寫打開一個二進制檔案 | 出錯 |
| "wb+"(讀寫) | 為了讀和寫,新建一個新的二進制檔案 | 建立一個新檔案 |
| "ab+"(讀寫) | 打開一個二進制檔案,在檔案尾進行讀和寫 | 建立一個新檔案 |
| 注: ‘w’ 打開的時候如果有同名檔案,該檔案中的內容會被銷毀 | ||
fclose
//函式原型
int fclose(FILE* stream);
函式功能:Closes a stream(fclose) or closes all open streams(_fcloseall).(關閉檔案)
回傳型別:fclose returns 0 if the stream is successfully closed._fcloseall returns the total number of streams closed.Both functions return EOF to indicate an error(關閉檔案成功回傳0,關閉檔案失敗回傳EOF(值為 - 1)來報錯)
函式引數:Pointer to FILE structure(檔案指標)
例:
#include <stdio.h>
int main()
{
//打開檔案mysize.txt
//相對路徑
//..表示上一級路徑
//fopen("../../mysize.txt", "r");
//.表示當前路徑
FILE* pf = fopen("mysize.txt", "r");//相對路徑
if (pf == NULL)
{
perror("fopen");
return 1;
}
//打開成功
//讀檔案
fclose(pf);//關閉檔案
return 0;
}
我們首先新建一個檔案:

執行結果:

我們此時再換成絕對路徑:

剛剛我們是以 “r”(只讀)的方式來打開檔案,現在我們來嘗試用 “w”(只寫)的方式來打開檔案
#include <stdio.h>
int main()
{
FILE* pf = fopen("eg.dat", "w");//相對路徑
if (pf == NULL)
{
perror("fopen");
return 1;
}
//打開成功
//讀檔案
printf("file open success.\n");
fclose(pf);//關閉檔案
return 0;
}
觀察檔案夾,發現新建了 eg.dat 這么一個檔案 :

八、檔案順序讀寫表
| 名稱 | 函式名 | 功能(回傳值) | |
| 字符輸入函式 | fgetc(fp) | 從檔案指標 fp 所指向的檔案的當前讀指標位置讀取一個字符,讀取完成后指標自動后移指向下一個字符 成功時回傳該字符,否則回傳EOF | |
| 字符輸出函式 | fputc(ch,fp) | 將字符 ch 寫入到檔案指標 fp 所指向的檔案的當前寫指標位置 成功是回傳字符本身,否則回傳EOF | |
| 文本行輸入函式 | fgets(str, n, fp) | 從 fp 所指向的檔案的當前讀指標位置讀出 n 個字符放入字串 str 中成功時回傳該字串地址,否則回傳NULL | |
| 文本行輸出函式 | fputs(str, fp) | 將字串 str 寫入到檔案指標 fp 所指向的檔案的當前寫指標位置 成功時回傳非0值,否則回傳EOF | |
| 格式化輸入函式 | fscanf(fp, 格式控制字串,地址串列) | 按格式控制串所描述的格式,從 fp 所指向的檔案中讀取資料,送到指定的變數中, 若輸出操作成功,回傳實際寫入的字符 若輸出操作失敗,則回傳EOF | |
| 格式化輸出函式 | fprintf(fp, 格式控制字串,輸出串列) | 將輸出項按照制定的格式寫入 fp 所指向的檔案中, 若輸入操作成功,回傳實際讀出的資料 若沒有讀到資料項,則回傳0 若檔案結束或呼叫失敗,則回傳EOF | |
| 二進制輸入 | size_t fread(buffer, size, count, stream) | 讀取 [count_num] 個物件(每個物件大小為 size(大小) 指定的位元組數),并把它們替換到由 buffer (緩沖區) 指定的陣列,資料來自給出的輸入流 函式的回傳值是讀取的內容數量 | |
| 二進制輸出 | size_t fwrite(buffer, size, count, stream) | 把 buffer 所指向的陣列中的資料寫入到給定流 stream 中 若成功,該函式回傳一個 size_t 物件,表示元素的總數,該物件是一個整型資料型別, 若該數字與 count 引數不同,則會顯示一個錯誤, | |
單看這個表可能沒有什么感覺,接下去就讓我們來詳細的進行每個函式的功能描述,在此之前需要先介紹一個函式,它是什么呢?
流(stream)
概念:輸入輸出是資料傳送的程序, 資料如流水一樣從一處流向另一處, 因此常將輸入輸出形象地稱為流(stream), 即資料流,流表示了資訊從源到目的端的流動,在輸入操作時, 資料從檔案流向計算機記憶體, 在輸出操作時, 資料從計算機流向檔案(如列印機、磁盤檔案),檔案是由作業系統進行統一管理的, 無論是用Word打開或保存檔案, 還是C程式中的輸入輸出都是通過作業系統進行的,“流”是一個傳輸通道, 資料可以從運行環境(有關設備)流入程式中, 或從程式流至運行環境,
圖解:

注:C語言程式,只要運行起來,就默認打開了三個流,型別均為 FILE*
stdin --- 標準輸入流 --- 鍵盤
stdout --- 標準輸出流 --- 螢屏
stderr --- 標準錯誤流 --- 螢屏
fputc
例:
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE* pf = fopen("mysize.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//寫檔案
//關閉檔案
fputc('H', stdout);//stdout 標準輸出流
fputc('y', stdout);
fputc('D', stdout);
fputc('r', stdout);
fputc('a', stdout);
fclose(pf);
return 0;
}
執行結果:

fgetc
例:
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE *pf = fopen("mysize.txt","r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//讀檔案
int ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
ret = fgetc(pf);
printf("%c", ret);
//關閉檔案
fclose(pf);
pf = NULL;
return 0;
}
執行結果:

fgets
在舉例前我們先在 mysize.txt 文本內部加上一行字符,這樣能夠更好的展現效果:

例:
#include <stdio.h>
int main()
{
char buf[1024] = { 0 };
FILE *pf = fopen("mysize.txt", "r");
if (pf == NULL)
{
return 0;
}
//讀檔案
fgets(buf, 1024, pf);
printf("%s", buf);//buffer自身擁有換行功能
fclose(pf);
pf = NULL;
return 0;
}
執行結果:
fputs
例1:
#include <stdio.h>
int main()
{
char buf[1024] = { 0 };
FILE *pf = fopen("mysize.txt", "r");
if (pf == NULL)
{
return 0;
}
//讀檔案
fgets(buf, 1024, pf);
puts(buf);
fgets(buf, 1024, pf);
puts(buf);
fclose(pf);
pf = NULL;
return 0;
}
執行結果:

例2:
#include <stdio.h>
int main()
{
//從鍵盤讀取一行文本資訊
char buf[1024] = { 0 };
fgets(buf, 1024, stdin);//從標準輸入流讀取
fputs(buf, stdout);
//上面那種寫法等價于下面這種寫法
gets(buf);
puts(buf);
return 0;
}
fprintf
例:
#include <stdio.h>
struct S
{
int n;
float f;
char arr[10];
};
int main()
{
struct S s = { 520, 13.14f, "HSS" };
FILE *pf = fopen("mysize.txt", "w");
if (pf == NULL)
{
return 0;
}
//格式化的形式寫檔案
fprintf(pf, "%d %f %s", s.n, s.f, s.arr);
fclose(pf);//關閉檔案
pf = NULL;
return 0;
}
執行結果:

此時我們再去查看 mysize.txt 記事本就會發現里面的資料已經被格式化并且替換成了剛寫的資料:

fscanf
例:
#include <stdio.h>
struct S
{
int n;
float f;
char arr[10];
};
int main()
{
struct S s = { 0 };
FILE* pf = fopen("mysize.txt", "r");
if (pf == NULL)
{
return 0;
}
//格式化的輸入資料
fscanf(pf, "%d %f %s", &(s.n), &(s.f), s.arr);
printf("%d %f %s\n", s.n, s.f, s.arr);
fclose(pf);//關閉檔案
pf = NULL;
return 0;
}
執行結果:

對比一組函式
scanf / fscanf / sscanf
printf / fprintf / sprintf
此時我們還未了解過 sscanf 和 sprintf,所以我們先進行舉例說明:
例:
#include <stdio.h>
struct S
{
int n;
float f;
char arr[10];
};
int main()
{
struct S s = { 520, 13.14f, "HSS" };
struct S tmp = { 0 };
char buf[1024] = { 0 };
//把格式化的資料轉換成字串存盤到buffer
sprintf(buf, "%d %f %s", s.n, s.f, s.arr);
//從buffer中讀取格式化的資料到tmp中
sscanf(buf, "%d %f %s", &(tmp.n), &(tmp.f), tmp.arr);
printf("%d %f %s\n", tmp.n, tmp.f, tmp.arr);
return 0;
}
執行結果:

總結:
- scanf/printf --- 是針對標準輸入流/標準輸出流的格式化輸入/輸出陳述句
- fscanf/fprintf --- 是針對所有輸入流/所有輸出流的格式化輸入/輸出陳述句
- sscanf/sprintf --- sscanf是從字串中讀取格式化的資料,sprintf是把格式化資料輸出成(存盤到)字串
fwrite
例:
#include <stdio.h>
struct S
{
char name[20];
int age;
int height;
};
int main()
{
struct S s = {"HyDra", 18, 185};
FILE *pf = fopen("mysize.txt", "wb");
if (pf == NULL)
{
return 0;
}
//二進制的形式寫檔案
fwrite(&s, sizeof(struct S), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
執行結果:
為什么執行完里面是空白的呢?此時我們再看向 mysize.txt 檔案:

為什么后面的內容就變成亂碼了呢?
原因很簡單,由于 fwrite 的功能的就是進行二進制的輸出來進行存盤,所以當我們打開記事本時,由于HyDra是一個字串,所以我們是可以看到的,但是記事本沒有辦法轉換字符成為二進制,于是后面就成了亂碼,
fread
例:
struct S
{
char name[20];
int age;
int height;
};
int main()
{
struct S tmp = { 0 };
FILE *pf = fopen("mysize.txt", "rb");
if (pf == NULL)
{
return 0;
}
//二進制的形式寫檔案
fread(&tmp, sizeof(struct S), 1, pf);
printf("%s %d %d\n", tmp.name, tmp.age, tmp.height);
fclose(pf);
pf = NULL;
return 0;
}
執行結果:
九、檔案隨機讀寫
一般情況下, 在對字符檔案進行順序讀寫時, 檔案位置標記指向檔案開頭, 這時如果對檔案進行讀的操作, 就讀第1個字符, 然后檔案位置標記向后移一個位置, 在下一次執行讀的操作時, 就將位置標記指向的第⒉個字符讀入,依此類推, 遇到檔案尾結束,
圖解:
當然也可以根據讀寫的需要, 人為地移動檔案位置標記的位置,
檔案位置標記可以向前移、向后移, 移到檔案頭或檔案尾, 然后對該位置進行讀寫,
顯然這就不是順序讀寫了, 而是隨機讀寫,
fseek
-
根據檔案指標的位置和偏移量來定位檔案指標
//函式原型
int fseek ( FILE * stream, long int offset, int origin );
//fseek(檔案型別指標, 偏移量, 起始點)
起始點的選項有三個:SEEK_CUR 、SEEK_END 、SEEK_SET
| 起始點 | 名字 | 用數字代表 |
| 檔案開始位置 | SEEK_CUR | 0 |
| 檔案當前位置 | SEEK_END | 1 |
| 檔案末尾位置 | SEEK_SET | 2 |
例:
先打開mysize.txt編輯一下里面的的資料內容:

代碼如下:
#include <stdio.h>
int main()
{
FILE *pf = fopen("mysize.txt", "r");
if (pf == NULL)
{
return 0;
}
//1.定位檔案指標
fseek(pf, 2, SEEK_CUR);
//2,讀取檔案
int tmp = fgetc(pf);
printf("%c\n", tmp);
fclose(pf);
pf = NULL;
return 0;
}
執行結果:

偏移量2,從開頭H向右偏移兩個到D(原HyDra)
ftell
- 回傳檔案指標相對于起始位置的偏移量(定位)
//函式原型
long int ftell ( FILE * stream );
例:
#include <stdio.h>
int main()
{
FILE *pf = fopen("mysize.txt", "r");
if (pf == NULL)
{
return 0;
}
//1.定位檔案指標
fseek(pf, -2, SEEK_END);
//2,讀取檔案
int eg = ftell(pf);
printf("%d\n", eg);
fclose(pf);
pf = NULL;
return 0;
}
執行結果:

由于是-2,從檔案結尾向左偏移2個,指向 r 前面的位置,所以結果是3(原HyDra)
rewind
- 讓檔案指標的位置回到檔案的起始位置
//函式原型
void rewind ( FILE * stream );
例:
#include <stdio.h>
int main()
{
FILE *pf = fopen("mysize.txt", "r");
if (pf == NULL)
{
return 0;
}
int as = fgetc(pf);
printf("%c\n", as);
rewind(pf);
as = fgetc(pf);
printf("%c\n", as);
fclose(pf);
pf = NULL;
return 0;
}
執行結果:

由于恢復到了起始位置,所以列印出來仍為 H (原HyDra)
十、檔案讀取結束的判定
有一個函式特別容易被錯誤使用:feof
牢記 : 在檔案讀取程序中,不能用feof函式的回傳值直接用來判斷檔案的是否結束,而是應用于當檔案讀取結束的時候,判斷是讀取失敗結束,還是遇到檔案尾結束,
1. 文本檔案讀取是否結束,判斷回傳值是否為EOF (fgetc),或者NULL(fgets)
- fgetc判斷是否為EOF
- fgets判斷回傳值是否為NULL
2. 二進制檔案的讀取結束判斷,判斷回傳值是否小于實際要讀的個數
- fread判斷回傳值是否小于實際要讀的個數
例1(文本檔案):
這里我們參考的是之前創建的 eg.txt 檔案,我們將里面的內容全部清空,

代碼如下:
#include <stdio.h>
int main()
{
//feof(); EOF - end of file - 檔案結束標志
FILE *pf = fopen("eg.txt", "r");
if (pf == NULL)
{
return 0;
}
int size = fgetc(pf);
printf("%d\n", size);//-1
fclose(pf);
pf = NULL;
return 0;
}
執行結果:

由此可見,檔案結束的位置確實存在一個 EOF
例2(二進制檔案):
#include <stdio.h>
int main()
{
FILE *pf = fopen("mysize.txt", "r");
if (pf == NULL)
{
perror("open file eg.txt");
return 0;
}
//讀檔案
int ag = 0;
while ((ag = fgetc(pf)) != EOF)
{
putchar(ag);
}
if (ferror(pf))
{
printf("error\n");
}
else if (feof(pf))
{
printf(" end of file\n");
}
fclose(pf);
pf = NULL;
return 0;
}
執行結果:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299188.html
標籤:其他




