來源:https://blog.csdn.net/jamenu/article/details/85066236
前言在譚浩強的C語言設計的第10張講了有關文件的知識,以往我們寫的C程序的數據都是由鍵盤輸入的現在我們要對文件進行操作。
文件是作業系統的基本單位。是我們專業領域的重要的一部分。就拿我們的編譯器來說,我們寫了一個程序,成功的運行了,編譯系統會主動的生成3個文件如下圖
它們分別是源程序文件.c目標文件.o可執行文件.exe
在實際的情況下,常常要把一些數據輸出到磁碟上保存起來,在需要時送入內存之中這就需要我們用到磁碟文件。
1.文件名一個文件要有一個唯一的文件標識以便識別和引用,分別為1.路徑(用來找到文件)2.名稱(識別是什麼)3.後綴(文件的格式或屬性)
2.文件的分類書上將文件分成兩類,分別是ASCII文件和二進位文件1.ASCII文件我們在剛剛接觸c語言時就了解了ASCII標,每個特定的數代表一個字符,那麼將字符形式的文件就是ASCII文件,也稱為文本文件,每個字節存放一個字符的ASCII值。2.二進位文件數據在內存中是以二進位形式存儲的(因為CPU只認識0和1),如果不加轉換的輸出到外存,就是二進位文件。也稱之為映像文件(因為從二進位轉換到ASCII需要一定的時間,佔的空間也更多,加之二進位文件的實用優點,我們生活中常用二進位的文件。)
3.緩衝區概念我們都知道,在計算機的存儲設備中,CPU的緩存速度是最快的,其次是顯卡的顯存,DDR5的顯存早就已經普及,並有被淘汰之勢,再次是內存,就在這幾年DDR4的內存才開始廣泛的使用,最慢的就是我們的硬碟(磁碟),機械硬碟的轉速很慢,最快的速度也只有200多MB一秒,儘管我們現在普遍用上了固態硬碟,還是M.2協議的,但是和內存的差距依舊很大。因為兩者的差距太大,就必須在數據寫入磁碟時就有必要在內存中開闢一片緩衝區(所以緩衝區不是什麼高大上的東西)
4.文件類型指針回顧前面的知識,指針必須要有基類型(指向),如int p,floatp…。那麼顧名思義文件指針就是指向文件的,其實文件在C語言中是,一種結構體也就是和結構體指針是一樣的。以下是文件結構體的定義,結構體的名稱為FILE。
tpyedef struct
{ short level; //緩衝區滿或空的程度
unsigned flags; //文件狀態標誌
char fd; //文件描述符
unsigned char hold; //如緩衝區無內容不讀取字符
short bsize; //緩衝區的大小
unsigned char*buffer; //數據緩衝區的位置
unsigned char*curp; //文件位置標記指針當前的指向
unsigned istemp; //臨時文件指示器
short token; //用於有效性檢查
}FILE
//這是TC2.0中的定義
1234567891011121314151617181920
#ifndef _FILE_DEFINED
struct _iobuf {
char *_ptr; //文件輸入的下一個位置
int _cnt; //當前緩衝區的相對位置
char *_base; //指基礎位置(即是文件的其始位置)
int _flag; //文件標誌
int _file; //文件的有效性驗證
int _charbuf; //檢查緩衝區狀況,如果無緩衝區則不讀取
int _bufsiz; //緩衝區大小
char *_tmpfname; //臨時文件名
};
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif //這是VC6。0裡定義當然我們不必去深究其中的作用(會寫標準庫定義的人不知道比我們高到哪裡去了)但要稍微了解一下
下面介紹關於文件的函數1.打開與關閉文件函數fopen 打開文件函數 (成功打開後指向該流的文件指針就會被返回,失敗返回NULL)fclose 關閉文件函數引用方法
在公眾號【C語言中文社區】後臺回復「C語言」,免費獲取200G學習禮包。
fopen(文件名,使用文件方式); //文件名是一個字符串,使用方式需要加雙引號,
1為了使文件指針與文件建立聯繫我們要將函數返回的指針給我們的文件指針像這樣
FILE*fp;
fp = fopen("test.txt","r");//以讀的方式打開默認路徑下一個叫test的文本文件可以說這樣就將fp指向了test這個文件。既然文件的使用方式,我們就要全面的了解
文件使用方式含義如果指定的文件不存在r(只讀)輸入數據,打開一個已存在文本文件出錯w(只寫)輸出數據,打開一個文本文件建立一個新文件a(追加)向文本文件尾添加數據出錯rb(只讀)輸入數據,打開一個2進位文件出錯wb(只寫)輸出數據,打開一個2進位文件建立新文件r+(讀寫)讀寫,文本文件出錯w+(讀寫)讀寫,文本文件建立新文件a+(讀寫)讀寫,文本文件出錯rb+(讀寫)讀寫,二進位文件出錯wb+(讀寫)讀寫,二進位文件建立新文件ab+(讀寫)讀寫二進位文件出錯這裡我們來用一個小程序演示一下我現在編譯器默認路徑下建立一個空的文本文件test
#include <stdio.h>
int main()
{
FILE*p; //設置一個文件指針
char ch; //定義一個字符變量
if((p=fopen("test.txt","w"))==NULL) //打開一個叫test的文本文件,以只寫的方式,當 出錯時,即返回值為NULL時,輸出錯誤信息
{
printf("ERROR");
exit(0); //關閉所有文件,終止正在執行的程序
}
for(;ch!='\n';) //輸入ch知道輸入空格。
{
scanf("%c",&ch);
fputc(ch,p); //將字符ch寫入p所指向的文件
}
fclose(p);//關閉文件
}我們再到默認路徑打開test.txt。
可以看到我們在鍵盤裡輸出的Merry Christmas已經被寫入了test之中
下面我們來讀取這個文件裡的信息我們只需要將程序稍微修改一下就可以
#include <stdio.h>
int main()
{
FILE*p;
char ch;
if((p=fopen("test.txt","r"))==NULL) //以只讀的方式打開test。
{
printf("ERROR");
exit(0);
}
for(;ch!='\n';)
{
ch=fgetc(p); //ch得到p所指文件中的每一個字符
putchar(ch); //將得到的字符輸出到屏幕
}
fclose(p); //關閉文件
}下面是截圖
值得注意的是,在我們向文件寫入的時候會將原有的內容清空再進行寫入請看我們再對test進行寫入這次我輸入了happy再到默認路徑下打開text文件
可以看到我們的Merry Christmas被清除了並用我們的happy寫入了文件如果要對文本文件 進行寫入並保留原有內容,就要用追加的方式進行
if((p=fopen("test.txt","a"))==NULL) //只需將w改為a即可下面我們再進行寫入再打開test文件
5.順序讀寫數據文件
1.讀寫字符函數讀字符 fgetc
fegrtc(文件指針) //從指針所指向的文件中讀入一個字符寫字符fputc
fputc(ch,fp) //把字符變量ch寫到fp所指向的文件中去我們在上一個例子中就使用了這倆個函數,為了有更深刻的印象。我以一個文件複製的程序來中舉例下面是源碼
#include <stdio.h>
int main()
{
FILE*p1,*p2; //設置2個文件指針
char filename[30],filename1[30],ch; //設置2個字符數組用來輸入文件名用
printf("請輸入要複製的文件名\n");
gets(filename); //輸入文件名
printf("請輸入複製後的文件名:\n");
gets(filename1); //輸入文件名
if((p1=fopen(filename,"rb"))==NULL) //打開被複製的文件
{
printf("ERROR");
exit(0);
}
if((p2=fopen(filename1,"wb"))==NULL) //寫入要複製的文件名
{
printf("ERROR");
exit(0);
}
while(!feof(p1)) //用一個檢查文件是否結束的函數來判斷
{
ch=fgetc(p1); //讀出每一個p1指向的文件中的字節,把ch寫入到p2指向的文件中去,如果沒有p2文件,則會建立一個以filename1字符數組命名的文件
fputc(ch,p2);
}
printf("複製成功");
fclose(p1); //用完之後,為了避免不必要的操作幹擾讀寫,要關閉文件,即斷掉文件指針與文件的聯繫
fclose(p2);
}下面是運行效果,我的默認路徑中有一張名為23.jpg的圖片文件運行程序寫入如下再去查看默認路徑就多了一張叫jaychou的jpg圖片下面就要介紹檢測文件是否介紹的函數feof(end of file)
feof(文件指針) //當文件結束時返回非0值,當文件未結束時返回0再看剛才的循環語句
while(!feof(fp))//當返回值為0的時候執行循環,返回值非0的時候就結束循環當然我們也可以用這個程序來複製文本文件
當然對於文本文件文件可以不用二進位的方式處理我們也可以把剛才的循環語句換成如下
while(!(ch=fgetc(fp))==EOF) //我們的EOF和NULL一樣都是標準庫裡的宏定義EOF就是-1,NULL代表0
1那麼這個-1又代表了什麼文件的所有有效字符後有一個文件尾標誌,當讀完全部的字符後,文件讀寫位置標記就會指向最後一個字符的後面的結束字節(裡面存放了-1),如果再讀取就會讀出-1在文本文件中,數據都是以字符的ASCII代碼值的形式存放。我們知道,ASCII代碼值的範圍是0~127,不可能出現-1,因此可以用EOF作為文件結束標誌。
WARNING二進位文件因為是以二進位形式保存的所以不能以字符的方式來存取的,所以不應用剛才的方式對二進位文件進行寫入。
同樣我們也有字符串的讀寫文件
函數名調用形式功能返回值fgetsfgets(str,n,fp)從fp指向的文件讀入一個長度為n-1的字符串,存放到字符數組str中去成功返回地址str,失敗返回NULLfputsfputs(str,fp))把str所指向的字符串寫入fp所指向的文件中成功返回0,失敗返回非零下面來舉個例子現在test中寫如下內容我們用fgets函數讀取出來並輸出上面的文字
#include <stdio.h>
#define LEN 40
int main()
{
FILE*fp;
char filename[LEN],string[30]; //定義一個字符數組來存儲文件裡的信息
printf("請輸入要打開的文件名");
gets(filename);
if((fp=fopen(filename,"r"))==NULL) //以只讀的方式打開文件打開文件
{
printf("ERROR");
exit(0);
}
fgets(string,20,fp); //將fp所指的文件中的20字符讀取給字符串string
fclose(fp); //關閉文件
puts(string); //以字符串形式輸出string
}6.用格式化的方式讀寫文本文件在我們以前的輸出輸入之中,常用scanf和printf函數,我們的格式化讀寫函數和他們類似
名稱引用方式fprintffprintf(文件指針,格式字符串,輸出列表)fscanffscanf(文件指針,格式字符串,輸入列表)#include <stdio.h>
int main()
{
int i=3;float j=4.567;char string[20];
FILE*fp;
if((fp=fopen("test.txt","r+"))==NULL) //以讀寫的方式打開test
{
printf("ERROR");
exit(0);
}
fscanf(fp,"%s",string); 把其中的字符串寫入字符數組string中
puts(string); //輸出由文件中寫入的字符串
fprintf(fp,"%3d,%6.4f",i,j); //以%3d,%6.4的格式輸入整形變量i和浮點型變量j
fclose(fp); //關閉文件
}再去默認路徑中打開test文件7.用二進位方式向文件讀寫一組數據
其實我們上面的複製圖片的例子也是對二進位文件的讀寫,在這裡有一組專門的二進位文件讀寫函數分別是freadfwrite
名稱引用方式freadfread(buffer,size,count,fp)fwritefwrite(buffer,size,count,fp)(也被稱為數據塊讀寫)
函數原型size_t fread ( void *buffer, size_t size, size_t count, FILE *stream)
參 數buffer用於接收數據的內存地址(是一個指針,無論輸出輸入都是首地址)size要讀的每個數據項的字節數,單位是字節count要讀count個數據項,每個數據項size個字節.stream文件指針
數據塊讀寫多用於結構體變量的讀寫(因為結構體所佔的字符數是不規則的)下面舉一個例子
#include <stdio.h>
#define LEN 15 //結構體中字符串的長度
#define NUM 8 //要輸入數據學生的個數
struct Student //定義學生結構體
{
char name[LEN];
int num;
int age;
char add[LEN];
}Stud[NUM]; //將數據先存放在結構體數組之中
int main()
{
FILE*fp;
int i;
if((fp=fopen("stduent","wb"))==NULL) //建立一個叫student的文件以二進位形式進行打開進行寫入操作,
{
printf("ERROR");
exit(0);
}
printf("請輸入學生數據\n");
for(i=0;i<NUM;i++) //
{
scanf("%s%d%d%s",&Stud[i].name,&Stud[i].num,&Stud[i].age,&Stud[i].add);//輸入學生數據信息
}
for(i=0;i<NUM;i++)
{
if(fwrite(&Stud[i],sizeof(struct Student),1,fp)!=1)//每次寫一個結構體變量所佔的字節,將輸入的數據寫入文件
{
printf("write error");
}
}
fclose(fp); //關閉文件
}完成輸入如圖當我們去默認路徑找到student文件,發現並不是文本文件,用文本文本的格式打開會看見二進位的源碼轉化出的亂碼我們再改變一下以上的代碼,將student的文件中的學生數據讀取出來
按照上面的輸入稍微修改一下即可下面是代碼
#include <stdio.h>
#define LEN 15
#define NUM 8
struct Student
{
char name[LEN];
int num;
int age;
char add[LEN];
}Stud[NUM];
int main()
{
FILE*fp;
int i;
if((fp=fopen("student","rb"))==NULL) //以二進位的方式讀取
{
printf("ERROR");
exit(0);
}
printf("姓名 學號 年齡 地址\n");
for(i=0;i<NUM;i++) //將數據每次一個結構體變量所佔字節的個數的數據賦給結構體變量
{
if(fread(&Stud[i],sizeof(struct Student),1,fp)!=1)
{
printf("read error");
}
}
for(i=0;i<NUM;i++) //輸出結構體變量的各個成員,輸出數據
{
printf("%-10s%4d%4d%-15s",Stud[i].name,Stud[i].num,Stud[i].age,Stud[i].add);
printf("\n");
}
fclose(fp); //關閉文件
}下面是運行截圖不用從鍵盤輸入數據,將文件中的數據讀取出來
PS(當文件不在編譯器默認的路徑時,要在文件名初加上文件的路徑,若一個文件為D:\data\student,我們要輸入它的路徑如下D:\data\student因為只是一個反斜槓的話會被和後面的字符被認為是轉義字符處理,這也是我們的老師在我們剛學C語言時就教過的)
8.隨機讀寫數據文件
1.文件位置標記即其定位
文件位置標記其實是一個文件中的一個指針在我們的順序讀寫當中,都是一個字符一個字符的讀出有寫入沒有出現跳躍的情況,但在實際使用的過程中,我們不許要每次都要把文件全部都讀一遍只需要稍微的改動某些數據,下面由本人這個靈魂畫師來畫一個圖演示一下文件位置標記的定位
rewind 功能是將文件內部的指針重新指向一個文件的開頭
rewind(文件指針);現在把上面的代碼改一下
#include <stdio.h>
#define LEN 15
#define NUM 8
struct Student
{
char name[LEN];
int num;
int age;
char add[LEN];
}Stud[NUM];
int main()
{
FILE*fp,*fp1;
int i;
if((fp=fopen("student","rb"))==NULL)
{
printf("ERROR");
exit(0);
}
if((fp1=fopen("CSDN","wb"))==NULL) //以寫入的方式打開一個文件
{
printf("ERROR");
exit(0);
}
printf("姓名 學號 年齡 地址\n");
for(i=0;i<NUM;i++) //把文件中的數據讀入結構體數組的元素
{
if(fread(&Stud[i],sizeof(struct Student),1,fp)!=1)
{
printf("read error");
}
}
rewind(fp); //重置文件指針
for(i=0;i<NUM;i++) //寫入新的文件中
{
if(fwrite(&Stud[i],sizeof(struct Student),1,fp1)!=1)
{
printf("read error");
}
}
for(i=0;i<NUM;i++) //輸出文件數據
{
printf("%-10s%4d%4d%15s",Stud[i].name,Stud[i].num,Stud[i].age,Stud[i].add);
printf("\n");
}
fclose(fp);
fclose(fp1);
}運行如前面的圖一樣,默認路徑中出現了一個名為CSDN的文件,裡面存放著學生的數據
fseek函數,文件指針位置函數引用方法為
fseek(文件指針,位移量,起始點);起始點有3種情況,分別是0,1,2如表
起始點名字用數字代表文件開始位置SEEK_SET0文件當前位置SEEK_CUR1文件末尾位置SEEK_END2位移量是long形數據要在結尾加上L如
fseek(fp,100L,0);//將文件位置標記向前移動到離文件開頭100個字節處
fseek(fp,50L,1);//將文件位置標記前移到離當前位置50個字節處
fseek(fp,-10L,2);//將文件位置標記從文件末尾後退10個字節在上面的代碼出修改一下
for(i=0;i<NUM;i+=2)
{
fseek(fp,i*sizeof(struct Student),0);//跳過每次移動2個結構體所佔的字節
printf("%-10s%4d%4d%15s",Stud[i].name,Stud[i].num,Stud[i].age,Stud[i].add);
printf("\n");
}我們就實現了只輸出部分的數據ferror函數在調用各種輸入輸出函數(如 putc.getc.fread.fwrite等)時,如果出現錯誤,除了函數返回值有所反映外,還可以用ferror函數檢查。它的一般調用形式為 ferror(fp);如果ferror返回值為0(假),表示未出錯。如果返回一個非零值,表示出錯。應該注意,對同一個文件 每一次調用輸入輸出函數,均產生一個新的ferror函 數值,因此,應當在調用一個輸入輸出函數後立即檢 查ferror函數的值,否則信息會丟失。在執行fopen函數時,ferror函數的初始值自動置為0。clearerrclearerr的作用是使文件錯誤標誌和文件結束標誌置為0.假設在調用一個輸入輸出函數時出現了錯誤,ferror函數值為一個非零值。在調用clearerr(fp)後,ferror(fp)的值變為0。只要出現錯誤標誌,就一直保留,直到對同一文件調用clearerr函數或rewind函數,或任何一個輸入輸出函數。
偷偷告訴你,關注後回復「C語言」有驚喜哦