C 讀取檔案 範例代碼
如果是可以使用C++的環境,建議是用C++比較好寫也比較不用擔心指標的問題,C++的範例代碼可以參考這一篇。
https://charlottehong.blogspot.com/2021/04/c.html
寫了兩個版本,一個是單純函式讀取要自己管控字串的記憶體。另一個是類似於封裝成物件的方式自動管理,新手用第二個可能比較好,省去字串管理記憶體的問題,這問題通常是新手出bug的點。
不囉嗦直接上代碼
1. 讀檔的純C函式
使用方式直接輸入檔名就好,會自動讀出來,檔案記得自己創建好。
#include <stdio.h>
#include <stdlib.h>
void ReadFile_Example(const char* name) {
FILE* pFile;
long lSize;
char* buffer;
long result;
// 開檔
if (fopen_s(&pFile, name, "rb") || pFile == NULL) {
fputs ("File error",stderr); exit (1);
}
// 獲取檔案長度
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
// 創建所需長度
buffer = (char*)malloc(sizeof(char)*lSize+1);
if (buffer == NULL) {fputs ("Memory error",stderr); exit (2);}
buffer[0] = 0;
// 讀取檔案
result = fread(buffer, sizeof(char), lSize, pFile);
if (result != lSize) {fputs ("Reading error",stderr); exit (3);}
buffer[lSize] = 0;
printf("%s", buffer);
// 釋放資源
fclose (pFile), pFile=NULL;
free (buffer), buffer=NULL;
}
int main(int argc, char const* argv[]) {
ReadFile_Example("a.txt");
return 0;
}
好啦~拿了就用了,沒看過這麼貼心的教學文吧,貼上直接能執行不囉嗦。
下面開始正經解說了。
開檔案 fopen()
FILE* pFile;
// 方法1 (推薦)
fopen_s(&pFile, fileName, "rb");
// 方法2
pFile = fopen(fileName, "rb");
就這樣簡單一行就可以打開了,這邊我用比較新的標準 fopen_s() 而不是 fopen() 差別在於多一個 s 是更安全的意思,詳細在自己google查一下吧,總而言之是一樣的東西寫法有一點點不同而已。
打開之後需要檢查檔案有沒有開成功,失敗的原因有很多,最常見的是檔名打錯。失敗並不會像C++直接噴例外大可無腦不管。
檢測方式直接看他返回的指標是NULL還是有東西就可以了。他可以直接寫在if裡面節省行數,像底下這個樣子。
// 開檔
void openFile(FILE** p, const char* name, const char* mode) {
if (fopen_s(p, name, mode)) {
fputs ("Open file error",stderr); exit (1);
}
}
這邊簡單處理直接噴一個 exit (1) 就不管了,如果開檔失敗還有相對應的例外處理就是從返回值那邊下手,返回布林值1或0表示成功與失敗。
直接 exit(1) 就是一旦失敗就直接結束程式了。返回值是失敗可以有對應方式,重開或是跳過之類的選項。
函式的使用方式像這個樣子
FILE* pFile;
openFile(&pFile, "a.txt", "rb");
獲取檔案長度
讀取的方式也是有依照特定長度讀取 fread( )與內建的行讀取 getline(),如果想用前者一口氣把全部讀完就需要知道檔案有多長了。
計算長度的方式是利用讀取指標計算的,他就像你打開記事本的時候出現的游標一樣意思,一格一格慢慢前進,讀到最後面遇到結束符號為止。
想知道檔案長度就把游標移動到最後面,然後算一下多少格就是了,最後再把檔案移動回原本的位置即可。
long getFileSize(FILE* p) {
long temp = ftell (p), lSize = 0;
fseek (p , 0 , SEEK_END);
(lSize) = ftell (p);
fseek (p , temp , SEEK_SET);
return lSize;
}
fseek() 中的是表示偏移距離與相對位置。定位參數總共有3個。
// 從結束點開始計算偏移0格
fseek (p , 0 , SEEK_END);
// 從起點開始計算偏移 temp 格
fseek (p , temp , SEEK_SET);
// 從當前位置開始計算偏移 -1 格
fseek (p , -1 , SEEK_CUR);
創建動態陣列
也可以用非動態的陣列存,只是檔案大小不可預測,管控動態陣列是不可少的。這邊直接提供一個函式給大家用。
檢測兩次NULL的意思是因為,第一檢測要知道進來的東西有沒有連接著別的動態陣列,要是不檢查直接把原本指的的東西給丟了,直接就 memory leak 了。第二次檢測是要知道系統有沒有真的配發下來,記憶體要是滿了系統是沒辦法給的。
最後再給個結束符,字串的操作千萬記得給一下,沒有給沒辦法正常讀。有所有的字串相關函式都是依靠結束符運作的。
// 創建動態陣列
void createString(char** str, int len) {
if (*str != NULL) {fputs ("str is not NULL",stderr); exit (2);}
*str = (char*)malloc(sizeof(char) * len);
if (*str == NULL) {fputs ("Memory error",stderr); exit (2);}
*str[0] = 0;
}
要新增就這樣用,這樣就會自動建立了。只是用完後要記得自己釋放。
char* str=NULL;
createString(&str, 10);
printf("%s\n", str);
free(str), str=NULL;
不是太大重點,如果不明白什麼意思,複製貼上就是了。
讀取檔案
讀取的參數 read()
// 讀取檔案
fread(buffer, sizeof(char), lSize, pFile);
buffer[lSize] = 0;
printf("%s", buffer);
buffer 是依據檔案長度建立的字串 char*
lSize 是檔案的總長度
其中間的這一行特別重要
buffer[lSize] = 0;
因為讀檔並不包含字串的結束字符,必須自己設置。估計很多人卡關會卡在這裡,這也引出一個問題是字串的創建長度必須是,檔案長度的+1才可以,還要裝最後一個結束符。
以上就完成拉,想練習可以把三個函式奏起來,組合成一個可以讀檔的程式,當作練習。
打開的參數
簡單整理一下懶人包
模式參數 | 說明 |
---|---|
r | [讀檔], 有+號時 檔案必須存在不會自動建立 |
w | [寫檔], 有+號時 檔案可以不存在會自動建立新的 |
b | 在 Windwos 下必須追加 |
+ | 可讀可寫 |
a | 從尾部追加 |
其中 b 的意思直接把他當作windwos上必須寫上就好了。不然讀取的時候微軟的換行標記 \r\n 會被讀成只有 \n 導致長度錯了,進而導致可能越界讀到垃圾值。
可以參考這一篇:https://blog.csdn.net/cattylll/article/details/7107089
詳細說明看下面的表格,最常用的就
參數詳細說明
模式參數 | 說明 |
---|---|
r | 開啟檔案進行唯讀,若檔案不存在,則傳回NULL |
w | 開啟檔案進行唯寫,若檔案不存在,則建立新檔,若檔案存在則將之刪除,再建立新檔 |
a | 開啟檔案進行附加,若檔案存在,則資料從檔案尾端寫入,若檔案不存在則建立新檔 |
rb | 以二進位模式開啟檔案進行唯讀 |
wb | 以二進位模式開啟檔案進行唯寫 |
ab | 以二進位模式開啟檔案進行附加 |
r+ | 開啟檔案進行讀寫,若檔案不存在,則建立新檔,若檔案存在,資料將從檔案開頭進行覆寫 |
w+ | 開啟檔案進行讀寫,若檔案不存在,則建立新檔,若檔案存在則覆寫原有的資料 |
a+ | 開啟檔案進行附加、讀取,若檔案不存在則建立新檔,若檔案存在,則資料從檔案尾端寫入 |
r+b | 以二進位方式開啟檔案進行讀寫 |
w+b | 以二進位方式開啟檔案進行讀寫 |
a+b | 以二進位方式開啟檔案進行附加、讀取 |
2. 封裝好的物件
對於新手來說,讀檔最大的難關應該是在字串的操作吧,必須自己手動管理。C是沒有物件的,指標的操作只能自己小心點知道在幹嘛。
我就就大概遵從物件的規則仿製了一個,類物件的操作方式,想練習可以看看我怎麼寫的,照著這個方式去寫程式,寫檔案的函式我留著沒寫,當練習把它寫出來吧。
代碼放最後面,先簡單說明一下怎麼用。
使用方式
宣告變數用這樣的方式
FileData* file = newFileData();
讀取檔案內建會自動管理記憶體直接取就好
FileData_getFileString(file, "a.txt");
printf("%s\n\n", file->data);
最後結束的時候記得釋放資源,在釋放之前是可以連續讀取不同檔案的。
FileData_dtor(file);
代碼
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct FileData{
FILE* pFile;
char* data;
long size;
long capa;
bool init;
} FileData;
// 檢查是否由建構子初始化
void FileData_check(FileData* f) {
if (f->init != true) {
fputs ("FileData not init.",stderr); exit (1);
}
}
// 初始化
FileData* newFileData() {
FileData* p = (FileData*)malloc(sizeof(FileData));
if (p == NULL) {
fputs ("Memory error",stderr); exit (2);
} else {
p->pFile = NULL;
p->data = NULL;
p->size = 0;
p->capa = 0;
p->init = true;
}
return p;
}
// 解構
void FileData_dtor(FileData* f){
FileData_check(f);
if (f->pFile != NULL) {
fclose (f->pFile);
f->pFile = NULL;
}
if (f->data != NULL) {
free (f->data);
f->data = NULL;
f->size = 0;
f->capa = 0;
}
}
// 讀取檔案到字串
void FileData_getFileString(FileData* self, const char* name) {
// 檢查
FileData_check(self);
if (self->pFile != NULL) {
fclose (self->pFile);
self->pFile = NULL;
}
// 宣告
FILE** pFile = &(self->pFile);
char** buffer = NULL;
// 開檔
if (fopen_s(pFile, name, "rb") || !(*pFile))
fputs ("Open file error",stderr), exit (1);
// 獲取檔案大小
fseek (*pFile , 0 , SEEK_END);
self->size = ftell (*pFile);
fseek (*pFile , 0 , SEEK_SET);
// 如果容量不足,重建所需長度
buffer = &(self->data);
if (self->capa <= self->size) {
char* pStr= NULL;
free(*buffer);
pStr = (char*)malloc(sizeof(char) * self->size+1);
self->capa = self->size+1;
if (pStr == NULL) {fputs ("Memory error",stderr); exit (2);}
*buffer = pStr;
}
fread(*buffer, sizeof(char), self->size, *pFile);
(*buffer)[self->size] = 0;
//printf("%s\n", *buffer);
// 釋放資源
fclose (*pFile);
}
// 讀取一行到字串
bool FileData_getFileLine(FileData* self, const char* name, long len) {
// 檢查
FileData_check(self);
// 宣告
FILE** pFile = &(self->pFile);
char** buffer = NULL;
// 開檔
if(self->pFile == NULL){
if (fopen_s(pFile, name, "rb") || !(*pFile))
fputs ("Open file error",stderr), exit (1);
}
// 獲取檔案大小
self->size = len;
// 如果容量不足,重建所需長度
buffer = &(self->data);
if (self->capa <= self->size) {
char* pStr= NULL;
free(*buffer);
pStr = (char*)malloc(sizeof(char) * self->size+1);
self->capa = self->size+1;
if (pStr == NULL) {fputs ("Memory error",stderr); exit (2);}
*buffer = pStr;
}
if (fgets(*buffer, self->size, *pFile) != NULL) {
return true;
}
// 沒有輸出清空
*buffer[0] = 0;
return false;
}
// 覆蓋寫檔案
void FileData_overWrite(FileData* self, const char* name, const char* inStr) {
}
// 測試
void test_getFileString() {
FileData* file = newFileData();
FileData_getFileString(file, "b.txt");
printf("%s\n\n", file->data);
FileData_getFileString(file, "a.txt");
printf("%s\n\n", file->data);
FileData_getFileString(file, "c.txt");
printf("%s\n\n", file->data);
FileData_dtor(file);
}
void test_getFileLine() {
FileData* file = newFileData();
while(FileData_getFileLine(file, "a.txt", 100)) {
printf("%s", file->data);
} printf("\n");
fseek (file->pFile , 0 , SEEK_SET);
while(FileData_getFileLine(file, "a.txt", 101)) {
printf("%s", file->data);
} printf("\n");
FileData_dtor(file);
}
//==============================================================
int main(int argc, char const* argv[]) {
test_getFileString();
test_getFileLine();
return 0;
}
//==============================================================
完整的封裝物件代碼可以看這裡:
回覆刪除https://ideone.com/y7EfL3