熬夜整理的C/C++萬字總結(五),文件操作
一、文件操作

文件在今天的計算機系統中作用是很重要的。文件用來存放程序、文檔、數據、表格、圖片和其他很多種類的信息。作為一名程序員,您必須編程來創建、寫入和讀取文件。編寫程序從文件讀取信息或者將結果寫入文件是一種經常性的需求。C提供了強大的和文件進行通信的方法。使用這種方法我們可以在程序中打開文件,然后使用專門的 I/O 函數讀取文件或者寫入文件。
程序員書籍資源,值得!
1.1 文件相關概念
1.1.1 文件的概念
一個文件通常就是磁盤上一段命名的存儲區。但是對于操作系統來說,文件就會更復雜一些。例如,一個大文件可以存儲在一些分散的區段中,或者還會包含一些操作系統可以確定其文件類型的附加數據,但是這些是操作系統,而不是我們程序員所要關心的事情。我們應該考慮如何在 C 程序中處理文件。
1.1.2 流的概念
流是一個動態的概念,可以將一個字節形象地比喻成一滴水,字節在設備、文件和程序之間的傳輸就是流,類似于水在管道中的傳輸,可以看出,流是對輸入輸出源的一種抽象,也是對傳輸信息的一種抽象。
C語言中,I/O 操作可以簡單地看作是從程序移進或移出字節,這種搬運的過程便稱為流(stream)。程序只需要關心是否正確地輸出了字節數據,以及是否正確地輸入了要讀取字節數據,特定 I/O 設備的細節對程序員是隱藏的。
1.1.2.1 文本流
文本流,也就是我們常說的以文本模式讀取文件。文本流的有些特性在不同的系統中可能不同。其中之一就是文本行的最大長度。標準規定至少允許 254 個字符。另一個可能不同的特性是文本行的結束方式。例如在 Windows 系統中,文本文件約定以一個回車符和一個換行符結尾。但是在 Linux 下只使用一個換行符結尾。
標準 C 把文本定義為零個或者多個字符,后面跟一個表示結束的換行符(\n).對于那些文本行的外在表現形式與這個定義不同的系統上,庫函數負責外部形式和內部形式之間的翻譯。例如,在 Windows 系統中,在輸出時,文本的換行符被寫成一對回車/換行符。在輸入時,文本中的回車符被丟棄。這種不必考慮文本的外部形勢而操縱文本的能力簡化了可移植程序的創建。
1.1.2.1 二進制流
二進制流中的字節將完全根據程序編寫它們的形式寫入到文件中,而且完全根據它們從文件或設備讀取的形式讀入到程序中。它們并未做任何改變。這種類型的流適用于非文本數據,但是如果你不希望I/O函數修改文本文件的行末字符,也可以把它們用于文本文件。
c語言在處理這兩種文件的時候并不區分,都看成是字符流,按字節進行處理。
我們程序中,經常看到的文本方式打開文件和二進制方式打開文件僅僅體現在換行符的處理上。
比如說,在 widows 下,文件的換行符是 \r\n,而在 Linux 下換行符則是 \n。
當對文件使用文本方式打開的時候,讀寫的 windows 文件中的換行符 \r\n 會被替換成 \n 讀到內存中,當在 windows 下寫入文件的時候,\n 被替換成 \r\n 再寫入文件。如果使用二進制方式打開文件,則不進行 \r\n 和 \n 之間的轉換。 那么由于 Linux 下的換行符就是 \n,所以文本文件方式和二進制方式無區別。
10W+字C語言硬核總結(一),值得閱讀!
10W+字C語言硬核總結(二),值得閱讀!
10W+字C語言硬核總結(三),值得閱讀!
熬夜整理的C語言/C++萬字總結(四)
1.2 文件的操作
1.2.1 文件流總覽
標準庫函數是的我們在 C 程序中執行與文件相關的 I/O 任務非常方便。下面是關于文件 I/O 的一般概況。
程序為同時處于活動狀態的每個文件聲明一個指針變量,其類型為 FILE*。這個指針指向這個 FILE 結構,當它處于活動狀態時由流使用。
流通過 fopen 函數打開。為了打開一個流,我們必須指定需要訪問的文件或設備以及他們的訪問方式(讀、寫、或者讀寫)。Fopen 和操作系統驗證文件或者設備是否存在并初始化 FILE。
根據需要對文件進行讀寫操作。
最后調用 fclose 函數關閉流。關閉一個流可以防止與它相關的文件被再次訪問,保證任何存儲于緩沖區中的數據被正確寫入到文件中,并且釋放 FILE 結構。
標準 I/O 更為簡單,因為它們并不需要打開或者關閉。
I/O 函數以三種基本的形式處理數據:單個字符、文本行和二進制數據。對于每種形式都有一組特定的函數對它們進行處理。
輸入/輸出函數家族:
1.2.2 文件指針
我們知道,文件是由操作系統管理的單元。當我們想操作一個文件的時候,讓操作系統幫我們打開文件,操作系統把我們指定要打開文件的信息保存起來,并且返回給我們一個指針指向文件的信息。文件指針也可以理解為代指打開的文件。這個指針的類型為 FILE 類型。該類型定義在 stdio.h 頭文件中。通過文件指針,我們就可以對文件進行各種操作。
對于每一個 ANSI C 程序,運行時系統必須提供至少三個流-標準輸入(stdin)、標準輸出(stdout)、標準錯誤(stderr),它們都是一個指向 FILE 結構的指針。標準輸入是缺省情況下的輸入來源,標準輸出時缺省情況下的輸出設置。具體缺省值因編譯器而異,通常標準輸入為鍵盤設備、標準輸出為終端或者屏幕。
ANSI C 并未規定 FILE 的成員,不同編譯器可能有不同的定義。VS 下 FILE 信息如下:
struct?_iobuf?{
char??*_ptr;?????????//文件輸入的下一個位置
int???_cnt;??????????//剩余多少字符未被讀取
char??*_base;????????//指基礎位置(應該是文件的其始位置)
int???_flag;?????????//文件標志
int???_file;?????????//文件的有效性驗證
int???_charbuf;??????//檢查緩沖區狀況,如果無緩沖區則不讀取
int???_bufsiz;???????//文件的大小
char??*_tmpfname;????//臨時文件名
};
typedef?struct?_iobuf?FILE;
1.2.3 文件緩沖區
文件緩沖區
ANSI C 標準采用“緩沖文件系統”處理數據文件 所謂緩沖文件系統是指系統自動地在內存區為程序中每一個正在使用的文件開辟一個文件緩沖區從內存向磁盤輸出數據必須先送到內存中的緩沖區,裝滿緩沖區后才一起送到磁盤去 如果從磁盤向計算機讀入數據,則一次從磁盤文件將一批數據輸入到內存緩沖區(充滿緩沖 區),然后再從緩沖區逐個地將數據送到程序數據區(給程序變量) 。
那么文件緩沖區有什么作用呢?
如我們從磁盤里取信息,我們先把讀出的數據放在緩沖區,計算機再直接從緩沖區中取數據,等緩沖區的數據取完后再去磁盤中讀取,這樣就可以減少磁盤的讀寫次數,再加上計算機對緩沖區的操作大大快于對磁盤的操作,故應用緩沖區可大大提高計算機的運行速度。
1.2.4 文件打開關閉
1.2.4.1 文件打開(fopen)
文件的打開操作表示將給用戶指定的文件在內存分配一個 FILE 結構區,并將該結構的指針返回給用戶程序,以后用戶程序就可用此 FILE 指針來實現對指定文件的存取操作了。當使用打開函數時,必須給出文件名、文件操作方式(讀、寫或讀寫)。
FILE?*?fopen(const?char?*?filename,?const?char?*?mode);
功能:打開文件
參數:
filename:需要打開的文件名,根據需要加上路徑 mode:打開文件的權限設置
返回值:
成功:文件指針 失敗:NULL
示例代碼:
void?test(){
FILE?*fp?=?NULL;
//?"\"這樣的路徑形式,只能在windows使用
//?"/"這樣的路徑形式,windows和linux平臺下都可用,建議使用這種
//?路徑可以是相對路徑,也可是絕對路徑
fp?=?fopen("../test",?"w");
//fp?=?fopen("..\test",?"w");
if?(fp?==?NULL)?//返回空,說明打開失敗
{
//perror()是標準出錯打印函數,能打印調用庫函數出錯原因
perror("open");
return?-1;
}
}
應該檢查fopen的返回值!如何函數失敗,它會返回一個NULL值。如果程序不檢查錯誤,這個NULL指針就會傳給后續的I/O函數。它們將對這個指針執行間接訪問,并將失敗。
1.2.4.2 文件關閉(fclose)
文件操作完成后,如果程序沒有結束,必須要用 fclose() 函數進行關閉,這是因為對打開的文件進行寫入時,若文件緩沖區的空間未被寫入的內容填滿,這些內容不會寫到打開的文件中。只有對打開的文件進行關閉操作時,停留在文件緩沖區的內容才能寫到該文件中去,從而使文件完整。再者一旦關閉了文件,該文件對應的FILE結構將被釋放,從而使關閉的文件得到保護,因為這時對該文件的存取操作將不會進行。文件的關閉也意味著釋放了該文件的緩沖區。
int?fclose(FILE?*?stream);
功能:關閉先前 fopen() 打開的文件。此動作讓緩沖區的數據寫入文件中,并釋放系統所提供的文件資源。
參數:stream:文件指針
返回值:
成功:0 失敗:-1
它表示該函數將關閉FILE指針對應的文件,并返回一個整數值。若成功地關閉了文件,則返回一個 0 值,否則返回一個非 0 值。
1.2.4 文件讀寫函數回顧
按照字符讀寫文件:fgetc(), fputc()
按照行讀寫文件:fputs(), fgets()
按照塊讀寫文件:fread(), fwirte()
按照格式化讀寫文件:fprintf(), fscanf()
按照隨機位置讀寫文件:fseek(), ftell(), rewind()
1.2.4.1 字符讀寫函數
int?fputc(int?ch,?FILE?*?stream);
功能:將 ch 轉換為 unsigned char 后寫入 stream 指定的文件中。
參數: ch:需要寫入文件的字符。 stream:文件指針
返回值: 成功:成功寫入文件的字符。 失敗:返回-1
int?fgetc(FILE?*?stream);
功能:從 stream 指定的文件中讀取一個字符。
參數: stream:文件指針
返回值: 成功:返回讀取到的字符。 失敗:-1
int?feof(FILE?*?stream);
功能:檢測是否讀取到了文件結尾。
參數: stream:文件指針
返回值: 非0值:已經到文件結尾。 0:沒有到文件結尾
void?test(){
//寫文件
FILE*?fp_write=?NULL;
//寫方式打開文件
fp_write?=?fopen("./mydata.txt",?"w+");
if?(fp_write?==?NULL){
return;
}
char?buf[]?=?"this?is?a?test?for?pfutc!";
for?(int?i?=?0;?i?
fputc(buf[i],?fp_write);
}
fclose(fp_write);
//讀文件
FILE*?fp_read?=?NULL;
fp_read?=?fopen("./mydata.txt",?"r");
if?(fp_read?==?NULL){
return;
}
#if?0
//判斷文件結尾?注意:多輸出一個空格
while?(!feof(fp_read)){
printf("%c",fgetc(fp_read));
}
#else
char?ch;
while?((ch?=?fgetc(fp_read))?!=?EOF){
printf("%c",?ch);
}
#endif
}
將把流指針 fp 指向的文件中的一個字符讀出,并賦給 ch,當執行 fgetc() 函數時,若當時文件指針指到文件尾,即遇到文件結束標志 EOF (其對應值為 -1 ),該函數返回一個 -1 給 ch,在程序中常用檢查該函數返回值是否為 -1 來判斷是否已讀到文件尾,從而決定是否繼續。
1.2.4.2 行讀寫函數
int?fputs(const?char?*?str,?FILE?*?stream);
功能:將 str 所指定的字符串寫入到 stream 指定的文件中, 字符串結束符 '
功能:將 str 所指定的字符串寫入到 stream 指定的文件中, 字符串結束符 '\0' 不寫入文件。
' 不寫入文件。參數:
str:字符串。 stream:文件指針
返回值:
成功:0。 失敗:-1
char?*?fgets(char?*?str,?int?size,?FILE?*?stream);
功能:從 stream 指定的文件內讀入字符,保存到 str 所指定的內存空間,直到出現換行字符、讀到文件結尾或是已讀了 size - 1 個字符為止,最后會自動加上字符 '
功能:從 stream 指定的文件內讀入字符,保存到 str 所指定的內存空間,直到出現換行字符、讀到文件結尾或是已讀了 size - 1 個字符為止,最后會自動加上字符 '\0' 作為字符串結束。
' 作為字符串結束。參數:
str:字符串。
size:指定最大讀取字符串的長度(size - 1)。
stream:文件指針
void?test(){
//寫文件
FILE*?fp_write=?NULL;
//寫方式打開文件
fp_write?=?fopen("./mydata.txt",?"w+");
if?(fp_write?==?NULL){
perror("fopen:");
return;
}
char*?buf[]?=?{
"01?this?is?a?test?for?pfutc!\n",
"02?this?is?a?test?for?pfutc!\n",
"03?this?is?a?test?for?pfutc!\n",
"04?this?is?a?test?for?pfutc!\n",
};
for?(int?i?=?0;?i?4;?i?++){
fputs(buf[i],?fp_write);
}
fclose(fp_write);
//讀文件
FILE*?fp_read?=?NULL;
fp_read?=?fopen("./mydata.txt",?"r");
if?(fp_read?==?NULL){
perror("fopen:");
return;
}
//判斷文件結尾
while?(!feof(fp_read)){
char?temp[1024]?=?{?0?};
fgets(temp,?1024,?fp_read);
printf("%s",temp);
}
fclose(fp_read);
}
1.2.4.3 塊讀寫函數
size_t?fwrite(const?void?*ptr,?size_t?size,?size_t?nmemb,?FILE?*stream);
功能:以數據塊的方式給文件寫入內容。
參數:
ptr:準備寫入文件數據的地址
size: size_t 為 unsigned int類型,此參數指定寫入文件內容的塊數據大小
nmemb:寫入文件的塊數,寫入文件數據總大小為:size * nmemb
stream:已經打開的文件指針
返回值:
成功:實際成功寫入文件數據的塊數,此值和nmemb相等
失敗:0
size_t?fread(void?*ptr,?size_t?size,?size_t?nmemb,?FILE?*stream);
功能:以數據塊的方式從文件中讀取內容
參數:
ptr:存放讀取出來數據的內存空間
size: size_t 為 unsigned int類型,此參數指定讀取文件內容的塊數據大小
nmemb:讀取文件的塊數,讀取文件數據總大小為:size * nmemb
stream:已經打開的文件指針
返回值:
成功:實際成功讀取到內容的塊數,如果此值比nmemb小,但大于0,說明讀到文件的結尾。
失敗:0
typedef?struct?_TEACHER{
char?name[64];
int?age;
}Teacher;
void?test(){
//寫文件
FILE*?fp_write=?NULL;
//寫方式打開文件
fp_write?=?fopen("./mydata.txt",?"wb");
if?(fp_write?==?NULL){
perror("fopen:");
return;
}
Teacher?teachers[4]?=?{
{?"Obama",?33?},
{?"John",?28?},
{?"Edward",?45},
{?"Smith",?35}
};
for?(int?i?=?0;?i?4;?i?++){
fwrite(&teachers[i],sizeof(Teacher),1,?fp_write);
}
//關閉文件
fclose(fp_write);
//讀文件
FILE*?fp_read?=?NULL;
fp_read?=?fopen("./mydata.txt",?"rb");
if?(fp_read?==?NULL){
perror("fopen:");
return;
}
Teacher?temps[4];
fread(&temps,?sizeof(Teacher),?4,?fp_read);
for?(int?i?=?0;?i?4;i++){
printf("Name:%s?Age:%d\n",temps[i].name,temps[i].age);
}
fclose(fp_read);
}
1.2.4.4 格式化讀寫函數
int?fprintf(FILE?*?stream,?const?char?*?format,?...);
功能:根據參數format字符串來轉換并格式化數據,然后將結果輸出到stream指定的文件中,指定出現字符串結束符 '\0' 為止。 參數:
stream:已經打開的文件
format:字符串格式,用法和printf()一樣
返回值:
成功:實際寫入文件的字符個數
失敗:-1
int?fscanf(FILE?*?stream,?const?char?*?format,?...);
功能:從stream指定的文件讀取字符串,并根據參數format字符串來轉換并格式化數據。
參數:
stream:已經打開的文件
format:字符串格式,用法和scanf()一樣
返回值:
成功:實際從文件中讀取的字符個數
失敗: - 1
注意:fscanf遇到空格和換行時結束。
void?test(){
//寫文件
FILE*?fp_write=?NULL;
//寫方式打開文件
fp_write?=?fopen("./mydata.txt",?"w");
if?(fp_write?==?NULL){
perror("fopen:");
return;
}
fprintf(fp_write,"hello?world:%d!",10);
//關閉文件
fclose(fp_write);
//讀文件
FILE*?fp_read?=?NULL;
fp_read?=?fopen("./mydata.txt",?"rb");
if?(fp_read?==?NULL){
perror("fopen:");
return;
}
char?temps[1024]?=?{?0?};
while?(!feof(fp_read)){
fscanf(fp_read,?"%s",?temps);
printf("%s",?temps);
}
fclose(fp_read);
}
1.2.5.5 隨機讀寫函數
int?fseek(FILE?*stream,?long?offset,?int?whence);
功能:移動文件流(文件光標)的讀寫位置。
參數:
stream:已經打開的文件指針
offset:根據 whence 來移動的位移數(偏移量),可以是正數,也可以負數,如果正數,則相對于 whence 往右移動,如果是負數,則相對于 whence 往左移動。如果向前移動的字節數超過了文件開頭則出錯返回,如果向后移動的字節數超過了 文件末尾,再次寫入時將增大文件尺寸。
whence:其取值如下:
SEEK_SET:從文件開頭移動offset個字節
SEEK_CUR:從當前位置移動offset個字節
SEEK_END:從文件末尾移動offset個字節
返回值:
成功:0
失敗:-1
long?ftell(FILE?*stream);
功能:獲取文件流(文件光標)的讀寫位置。
參數:stream:已經打開的文件指針
返回值:
成功:當前文件流(文件光標)的讀寫位置
失敗:-1
void?rewind(FILE?*stream);
功能:把文件流(文件光標)的讀寫位置移動到文件開頭。
參數:stream:已經打開的文件指針
返回值:無返回值
typedef?struct?_TEACHER{
char?name[64];
int?age;
}Teacher;
void?test(){
//寫文件
FILE*?fp_write?=?NULL;
//寫方式打開文件
fp_write?=?fopen("./mydata.txt",?"wb");
if?(fp_write?==?NULL){
perror("fopen:");
return;
}
Teacher?teachers[4]?=?{
{?"Obama",?33?},
{?"John",?28?},
{?"Edward",?45?},
{?"Smith",?35?}
};
for?(int?i?=?0;?i?4;?i++){
fwrite(&teachers[i],?sizeof(Teacher),?1,?fp_write);
}
//關閉文件
fclose(fp_write);
//讀文件
FILE*?fp_read?=?NULL;
fp_read?=?fopen("./mydata.txt",?"rb");
if?(fp_read?==?NULL){
perror("fopen:");
return;
}
Teacher?temp;
//讀取第三個數組
fseek(fp_read?,?sizeof(Teacher)?*?2?,?SEEK_SET);
fread(&temp,?sizeof(Teacher),?1,?fp_read);
printf("Name:%s?Age:%d\n",temp.name,temp.age);
memset(&temp,0,sizeof(Teacher));
fseek(fp_read,?-(int)sizeof(Teacher),?SEEK_END);
fread(&temp,?sizeof(Teacher),?1,?fp_read);
printf("Name:%s?Age:%d\n",?temp.name,?temp.age);
rewind(fp_read);
fread(&temp,?sizeof(Teacher),?1,?fp_read);
printf("Name:%s?Age:%d\n",?temp.name,?temp.age);
fclose(fp_read);
}
1.4 文件讀寫案例
讀寫配置文件
配置文件格式如下:
正式的數據以 ‘:’ 冒號進行分割,冒號前為 key 起到索引作用,冒號后為 value 是實值。# 開頭的為注釋,而不是正式數據
#英雄的Id heroId:1 #英雄的姓名 heroName:德瑪西亞 #英雄的攻擊力 heroAtk:1000 #英雄的防御力 heroDef:500 #英雄的簡介 heroInfo:前排坦克
struct?ConfigInfo
{
char?key[64];
char?value[64];
};
//獲取文件有效行數
int?getFileLine(const?char??*?filePath)
{
FILE?*?file?=?fopen(filePath,?"r");
char?buf[1024]?=?{0};
int?lines?=?0;
while?(fgets(buf,1024,file)?!=?NULL)
{
if?(isValidLine(buf))
{
lines++;
}
memset(buf,?0,?1024);
}
fclose(file);
return?lines;
}
//解析文件
void?parseFile(const?char??*?filePath,?int?lines,?struct?ConfigInfo**?configInfo)
{
struct?ConfigInfo?*?pConfig?=??malloc(sizeof(struct?ConfigInfo)?*?lines);
if?(pConfig?==?NULL)
{
return;
}
FILE?*?file?=?fopen(filePath,?"r");
char?buf[1024]?=?{?0?};
int?index?=?0;
while?(fgets(buf,?1024,?file)?!=?NULL)
{
if?(isValidLine(buf))
{
//解析數據到struct?ConfigInfo中
memset(pConfig[index].key,?0,?64);
memset(pConfig[index].value,?0,?64);
char?*?pos?=?strchr(buf,?':');
strncpy(pConfig[index].key,?buf,?pos?-?buf);
strncpy(pConfig[index].value,?pos?+?1,?strlen(pos?+?1)?-?1);?//?從第二個單詞開始截取字符串,并且不截取換行符
//printf("key?=?%s\n",?pConfig[index].key);
//printf("value?=?%s\n",?pConfig[index].value);
index++;
}
memset(buf,?0,?1024);
}
*configInfo?=?pConfig;
}
//獲取指定的配置信息
char?*?getInfoByKey(char?*?key,?struct?ConfigInfo*configInfo?,int?lines)
{
for?(int?i?=?0;?i?
{
if?(strcmp(key,?configInfo[i].key)?==?0)
{
return?configInfo[i].value;
}
}
return?NULL;
}
//釋放配置文件信息
void?freeConfigInfo(struct?ConfigInfo*configInfo)
{
free(configInfo);
configInfo?=?NULL;
}
//判斷當前行是否為有效行
int?isValidLine(char?*?buf)
{
if?(buf[0]?==?'0'?||?buf[0]?==?'\0'?||?strchr(buf,':')?==?NULL)
{
return?0;//?如果行無限?返回假
}
return?1;
}
int?main(){
char?*?filePath?=?"./config.txt";
int?lines?=?getFileLine(filePath);
printf("文件有效行數為:%d\n",?lines);
struct?ConfigInfo?*?config?=?NULL;
parseFile(filePath,?lines,?&config);
printf("heroId?=?%s\n",?getInfoByKey("heroId",?config,?lines));
printf("heroName:?=?%s\n",?getInfoByKey("heroName",?config,?lines));
printf("heroAtk?=?%s\n",?getInfoByKey("heroAtk",?config,?lines));
printf("heroDef:?=?%s\n",?getInfoByKey("heroDef",?config,?lines));
printf("heroInfo:?=?%s\n",?getInfoByKey("heroInfo",?config,?lines));
freeConfigInfo(config);
config?=?NULL;
system("pause");
return?EXIT_SUCCESS;
}
C++ Windows
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。