主頁 > 軟體設計 > C語言篇 +檔案操作(營養雞湯期末不掛科)

C語言篇 +檔案操作(營養雞湯期末不掛科)

2021-09-30 08:50:38 軟體設計

在這里插入圖片描述

目錄

  • 什么是檔案
  • 檔案名
  • 檔案型別
  • 檔案緩沖區
  • 檔案指標
    • 總結:
    • 補充:
  • 檔案的打開和關閉
    • 那么如何打開桌面上的檔案呢?
  • 檔案的順序讀寫
    • 檔案讀寫的函式
    • 字符輸出函式fputc
    • 流的概念
    • 字符輸入函式fgetc
    • 總結:
    • 文本行輸出函式 fputs
    • 文本行輸入函式 fgets
    • 格式化輸出函式 fprintf
    • 格式化輸入函式 fscanf
    • 二進制輸出 fwrite
    • 二進制輸入 fread
    • sprintf
    • sscanf
  • 檔案的隨機讀寫
    • fseek
    • ftell
    • rewind
  • 文本檔案和二進制檔案
    • 總結:
    • 關于二進制檔案和文本檔案的區別
  • 檔案結束的判定
    • 被錯誤使用的 feof
    • 總結:
  • 檔案緩沖區
    • 驗證緩沖區的存在

什么是檔案

  • 什么是檔案 磁盤上的檔案是檔案,

  • 但是在程式設計中,我們一般談的檔案有兩種:程式檔案、資料檔案

  • 程式檔案
    包括源程式檔案(后綴為.c),目標檔案(windows環境后綴為.obj),可執行程式(windows環境后綴 為.exe),

  • 資料檔案 檔案的內容不一定是程式,而是程式運行時讀寫的資料,比如程式運行需要從中讀取資料的檔案,或者輸出內 容的檔案,
    在這里插入圖片描述

本章只討論資料檔案,資料檔案總結一下:就是你可以用程式去向檔案中(輸出資訊 / 寫)又或者從檔案中(輸入資訊 /讀)到你的程式中去,而在這里需要站在記憶體的角度想清楚檔案的讀和寫

  • 在以前各章所處理資料的輸入輸出都是以終端為物件的,即從終端的鍵盤輸入資料,運行結果顯示到顯示幕上,
  • 其實有時候我們會把資訊輸出到磁盤上,當需要的時候再從磁盤上把資料讀取到記憶體中使用,這里處理的就是磁盤上的檔案,所以我們今天面對的輸入輸出物件就是檔案

檔案名

  • 檔案名包含3部分:檔案路徑+檔案名主干+檔案后綴
  • 例如: c:\code\test.txt
  • 為了方便起見,檔案標識常被稱為檔案名

檔案型別

檔案緩沖區

檔案指標

  • 緩沖檔案系統中,關鍵的概念是“檔案型別指標”,簡稱“檔案指標”,

  • 每個被使用的檔案都在記憶體中開辟了一個相應的檔案資訊區,用來存放檔案的相關資訊(如檔案的名字,檔案狀態及
    檔案當前的位置等),

  • 這些資訊是保存在一個結構體變數中的,

  • 該結構體型別是有系統宣告的,取名FILE. 例如,VS2008編譯環境提供的stdio.h 頭檔案中有以下的檔案型別申明:
    在這里插入圖片描述
    在這里插入圖片描述

總結:

當檔案被打開的時候會自動創建一個檔案資訊區,檔案資訊區是強關聯該檔案的,他會記錄檔案資訊實際上他就是一個結構體變數,struct _iobuf 是檔案的本質型別,FILE是由typedef定義出來的檔案型別,他也是一個檔案型別,
而檔案資訊區的型別是一個struct FILE型別

補充:

  • 不同的C編譯器的FILE型別包含的內容不完全相同,但是大同小異,
    每當打開一個檔案的時候,系統會根據檔案的情況自動創建一個FILE結構的變數,并填充其中的資訊,使用者不必關 心細節,
    一般都是通過一個FILE的指標來維護這個FILE結構的變數,這樣使用起來更加方便

  • 定義pf是一個指向FILE型別資料的指標變數,可以使pf指向某個檔案的檔案資訊區(是一個結構體變數),

  • 通過該檔案資訊區中的資訊就能夠訪問該檔案,也就是說,通過檔案指標變數能夠找到與它關聯的檔案

//以只讀的方式打開data檔案,并回傳該檔案資訊區的起始地址
FILE *pf1 = fopen("data1.txt,"r"");
FILE *pf2 = fopen("data2.txt,"r"");
FILE *pf3 = fopen("data3.txt,"r"");

如圖所示是檔案指標與檔案資訊區對應的關系
在這里插入圖片描述

總結:檔案資訊區實際上是一個FILE型別的結構體變數,關于他是怎么創建的,其實是在檔案被打開的時候就會自動生成,并且是關聯這個檔案的,最后再回傳檔案資訊區的起始地址,有了檔案資訊區的起始地址就能找到檔案

檔案的打開和關閉

注意事項

  • 1、檔案在讀寫之前應該先打開檔案,
  • 2、在使用結束之后應該關閉檔案,
  • 3、檔案在撰寫程式的時候,在打開檔案的同時,都會回傳一個FILE*的指標變數指向該檔案,也相當于建立了指標和檔案的關系
  • 4、ANSIC 規定使用fopen函式來打開檔案,fclose來關閉檔案

檔案打開函式的原型

FILE * fopen ( const char * filename, const char * mode );

filename:是打開檔案名
mode 是以什么模式打開

檔案關閉函式的原型

int fclose ( FILE * stream );
stream :傳入一個檔案指標

打開方式如下:
在這里插入圖片描述

//函式功能打開一個檔案
void filetest() 
{
	//以只讀的方式“r”打開檔案回傳檔案資訊區的起始地址
	FILE *pf = fopen("data.txt","r");
	//如果打開失敗回傳NULL,就列印錯誤資訊并退出程式
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//讀檔案
	//關閉檔案,有點類似于free函式的寫法
	flcose(pf);
	//指標置空防止成為野指標
	pf = NULL
}

int main() 
{
	filetest();
	return 0;
}

以只讀的方式“r”打開 由于此時在我的工程中并沒有data這個資料檔案,所以會列印錯誤資訊提示我沒有此檔案,說明我們的檔案打開失敗了
在這里插入圖片描述
既然沒有這個檔案那么就創建一個檔案
在這里插入圖片描述
當我們的工程目錄下多出了一個data.txt會是什么樣呢
在這里插入圖片描述
這個時候程式正常,并沒有出錯,
總結一下:
當以不指定路徑的方式打開檔案,默認先訪問工程目錄下,如果工程下找不到這個檔案又以只讀的方式 “r” 打開檔案那么就會出錯,

那么如何打開桌面上的檔案呢?

其實桌面上的檔案和從磁盤上打開檔案并沒有多大的區別,因為fopen的第一個引數只需要接受一個檔案名(檔案路徑+檔案名主干+檔案后綴),和使用打開方式就行
在這里插入圖片描述
這時在我的桌面上新建一個文本檔案(資料檔案),我們在程式中將他打開,先來觀察這個檔案所處的路徑
在這里插入圖片描述
有了檔案的路徑就只需要將檔案路徑傳給fopen函式就行,而在這需要注意的是 \ 遇到字符會變成轉義字符,而為了防止他成為轉義字符就需要用雙斜杠將 / 轉義成普通的斜杠,這樣才是一個完整的路徑

在這里插入圖片描述

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	fclose(pf);
	pf = NULL;
}
int main() 
{
	filetest();
	return 0;
}

那么關于以只讀 “r” 的方式打開檔案的舉例已經夠了,再來看看以“w"(只寫)的方式打開檔案會是怎么樣的呢,這里需要注意細節
請添加圖片描述
可以看到如果桌面上沒有這個檔案,那么以 “ w ” 只寫的方式打開檔案會創建一個檔案,我們再來看如果檔案已經存在了再以“ w ”只讀的方式打開會怎么樣
請添加圖片描述
很明顯可以看出之前的資訊明顯是保存了,可是再次打開這個檔案中卻什么都沒有,看到這里其實我們也應該直到這個fopen函式的脾氣了,當檔案不存在就會創建一個檔案,而當檔案存在就會銷毀該檔案的內容

檔案的順序讀寫

當我們的程式需要寫入一個資料到檔案當中去的時候對應的動作是(輸出/寫)這里用fputc表示,當我們從檔案當中讀取一個資料到記憶體中去的時候對應的動作是(輸入/讀),這里用fgetc表示,
在這里插入圖片描述

檔案讀寫的函式

在這里插入圖片描述

字符輸出函式fputc

int fputc( int c, FILE *stream );
c:要寫入到檔案的字符
stream :FILE結構的指標

int fputc( int c, FILE *stream )函式的使用,會將記憶體中的資料寫入到檔案中去,

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//順序寫入檔案,將字符寫到pf關聯的檔案中
	fputc('a',pf);
	fputc('b', pf);
	fputc('c', pf);
	fputc('d', pf);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

當程式成功執行起來的時候,可以看到文本檔案已經存放了abcd四個字符
在這里插入圖片描述
那么有沒有讀者會比較好奇fputc是怎么將字符寫入到檔案中的?
其實pf這個資訊區里面存放了一個指標用來記錄存取字符的位置,當存入字符后,指標會往后偏移,這樣子一點一點地就將資料存放完了,

流的概念

fputc他是適用于所有輸出流的(檔案 / 標準輸出),在我們c語言中有一個流的概念,流相當于水流,他是一種高度抽象的概念,而一些外部設備比如:檔案、螢屏、網路、光碟、軟盤,我可以從一些外部設備中讀寫一些資料,而不同外部設備的讀寫方式肯定不一樣,而作為程式員不需要關心怎么向外部設備讀寫資料,就在這些外部設備該需要采用如何讀寫的方式這個問題交給誰的時候,c語言封裝了流,程式員只需要關心如何往流里面輸入資料,并不需要關心資料給誰,資料給誰都是流來考慮
在這里插入圖片描述

  • c語言的程式只要一運行起來就會默認打開3個流:
  • 標準輸出流 - stdout
  • 標準輸入流 - stdin
  • 標準錯誤流 - stderr
  • 這3個流的型別都是FILE型別

fputc適用于所有輸出流,這里再舉例標準輸出流(螢屏)

void fpunc() 
{	//將字符順序輸出到螢屏
	fputc('a',stdout);
	fputc('b', stdout);
	fputc('c', stdout);
	fputc('d', stdout);
}

列印結果
在這里插入圖片描述

字符輸入函式fgetc

// 適用于所有輸入流
int fgetc( FILE *stream );
// stream:FILE結構的指標

fgetc適用于所有輸入流,這個函式功能是可以從一個流里面讀取資料然后回傳,當然這里是從檔案流里面讀取資料

void filetest() 
{
	
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//從檔案流中讀取資料回傳存放到變數ch中
	int ch = fgetc(pf);
	printf("%c",ch);

	ch = fgetc(pf);
	printf("%c", ch);

	ch = fgetc(pf);
	printf("%c", ch);

	ch = fgetc(pf);
	printf("%c", ch);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

列印結果
在這里插入圖片描述
其實pf這個資訊區里面存放了一個指標用來記錄存取字符的位置,當取出一個字符后,指標會往后偏移,這樣子一點一點地就將資料依次取出來了

fgetc函式讀取標準輸入流(鍵盤)

void pfunc() 
{
	//從標準輸入流讀取字符存放到變數ch中
	int ch = fgetc(stdin);
	printf("%c\n",ch);

	 ch = fgetc(stdin);
	printf("%c\n", ch);

	 ch = fgetc(stdin);
	printf("%c\n", ch);

	 ch = fgetc(stdin);
	printf("%c\n", ch);
}

列印結果
在這里插入圖片描述

總結:

  • 1、fgetc()是適用于所有的輸入流 ,它可以用于讀入所有輸入流
  • 2、fputc()函式是適用于所有的輸入流,它可以用于輸出所有輸出流

文本行輸出函式 fputs

功能:將一個字串輸出到檔案中
函式原型:

int fputs( const char *string, FILE *stream );
string : 想要輸出的字串
stream : FILE*型別的結構體指標 
void filetest() 
{
	
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//寫檔案,將字串寫到pf關聯的檔案中
	fputs("男人喜歡吃米飯\n",pf);
	fputs("女人喜歡吃面食\n", pf);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

程式運行后的結果:
可以看到字串成功地寫入進了檔案中去,這個函式注意的地方是如果你想讓他換行就必須再字串中加入‘\n’
在這里插入圖片描述

文本行輸入函式 fgets

這個函式的功能是從流中讀取一個字符存放到一個字符指標中去,前提是字符指標指向的空間夠大

char *fgets( char *string, int n, FILE *stream );
string : 資料存盤位置
n :可讀的最大字符數
stream :FILE結構的指標

文本行輸入函式 fgets的使用以及注意事項

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//寫檔案,將字串寫到pf關聯的檔案中
	char arr[20] = { 0 };
	fgets(arr,5,pf);
	printf("%s", arr);
	fgets( arr,5,pf);

	printf("%s",arr);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

從程式運行的結果不妨猜測一下為什么,結果會是這樣子
在這里插入圖片描述
其實fgets這個函式的引數n可讀的最大字符數雖然是5,以為可以讀5個字符,實際上只能讀到n - 1個字符,也就是4個字符,并且末尾會補‘\0’保證有5個字符

那么如果是讀取n = 20個他會全部讀完嗎?

void filetest() 
{
	
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//已將讀取的長度改為20
	char arr[20] = { 0 };
	fgets(arr,20,pf);
	printf("%s\n", arr);
	fgets( arr,20,pf);

	printf("%s\n",arr);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

從列印的結果可以看出不管n指定有多大,fgets只會讀取一行資料,并不會為了填充這20個大小的目標而去讀下一行字串,
在這里插入圖片描述

格式化輸出函式 fprintf

函式功能:以指定的格式將資料輸出到流里面去
fprintf函式的原型:

int fprintf( FILE *stream, const char *format [, argument ]...);
stream:FILE結構的指標

printf函式的原型和fprintf函式的原型對比:
在這里插入圖片描述
我們可以看出fprintf和printf的函式原型好像基本是一樣的,那么它們的使用是不是也基本一樣呢?下面來試試看

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","w");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//寫檔案,將資料寫到pf關聯的檔案中
	int arr[10] = { 0 };
	for (int i = 0; i < 10; i++) 
	{
		fprintf(pf, "%d\n", arr[i]++);
	}
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

程式執行完的結果:
在這里插入圖片描述

我們可以看出好像fprintf和printf的使用上沒太大的區別,只是一個是輸出到螢屏上去的,而另外一個是輸出到檔案上去的

格式化輸入函式 fscanf

函式功能:以指定的格式讀取出流里面的資料放到記憶體中去
fscanf函式的原型:

int fscanf( FILE *stream, const char *format [, argument ]... );

可以看出fscanf和scanf函式原型上其實也差不多,在這里就直接對標了哈哈哈
在這里插入圖片描述

代碼:

void filetest() 
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//讀檔案,從檔案流中拿去資料存放到a和b變數中
	int a = 0, b = 0;
	fscanf(pf,"%d %d", &a, &b);
	printf("%d %d", a, b);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

原本就存放在檔案中的資料:
在這里插入圖片描述

列印的結果:
在這里插入圖片描述

二進制輸出 fwrite

功能:以二進制的方式將程式中的資料輸出到檔案中

size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );

buffer:指向要寫入資料的指標	
size:元素大小(以位元組為單位)
count:要寫的元素的最大個數
stream :FILE結構的指標

代碼

struct s 
{
	int a;
	int b;
	char name[20];
};
void filetest() 
{
	//"wb":為了輸出資料,打開一個二進制檔案
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt", "wb");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//寫檔案,將資料寫到pf關聯的檔案中
	struct s s = { 0,40 ,"zhangsan"};
	fwrite(&s,sizeof(struct s),1,pf);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

程式運行的實際效果,這里我們可以看到這樣zhangsan的拼音是我們看的懂得,因為字串本身就是以文本形式存放,而剩下得資料我們就看不懂了,因為是以二進制的形式寫進檔案里去的,二進制的形式寫進去的跟以文本寫進去的會有很大區別
在這里插入圖片描述

既然以看不懂二進制的表示方式,那我們就用程式將這個文本檔案的資訊讀取出來,“rb”(只讀) 為了輸入資料,打開一個二進制檔案,二進制輸入,fread登場

二進制輸入 fread

功能:從一個流里面讀取count個大小為size(單位位元組)的資料存放到buffer里去

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

buffer:資料存盤位置
size:元素大小(以位元組為單位)
count:要讀取的最大的元素個數
stream :FILE結構的指標

在這里插入圖片描述

Fread回傳實際讀取的完整項的數量,如果發生錯誤或在達到count之前遇到檔案結束,則可能小于count,使用feof或ferror函式來區分讀錯誤和檔案結束條件,如果size或count為0,fread回傳0并且緩沖區內容不變,

在這里插入圖片描述

對標一下我們可以發現,其實它們之間的差別就是一個加了const和沒加const的區別,可實際上卻并不是這點差別

代碼:

void filetest() 
{
	struct s s = { 0 };
	//"rb" 為了輸入資料,打開一個二進制檔案
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt", "rb");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//讀檔案,打開一個二進制的檔案
	fread(&s,sizeof(struct s),1,pf);
	printf("%d %d %s",s.a,s.b,s.name);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

我們最先開始存放進去的是這個資料,顯示在文本中后看到的效果又是這樣
在這里插入圖片描述
程式運行:
在這里插入圖片描述

從程式運行的結果來看,確確實實地是將檔案中的資料以二進制的方式輸入到記憶體中去了,就像是給結構體重新賦值了一樣

對比一組函式:

scanf/fscanf/sscanf
printf/fprintf/sprintf

結論:
在這里插入圖片描述

sprintf

在這里插入圖片描述
函式功能:是將一個格式化的資料轉換成一個字串
代碼:

void pfunc() 
{
	char arr[20] = { 0 };
	struct s s = {10,100,"zhangsan"};

	//將一個格式化的資料轉換成字串,在存放到陣列中去
	sprintf(arr, "%d %d %s", s.a, s.b, s.name);
	//輸出陣列中的內容到螢屏上去
	printf("%s\n",arr);
}

程式運行結果:
在這里插入圖片描述

sscanf

在這里插入圖片描述
函式功能:是從arr的字串中提取出一個格式化的資料

代碼:

void pfunc() 
{
	char arr[20] = { 0 };
	struct s s = {10,100,"zhangsan"};
	struct s tmp = { 0 };
	//將一個格式化的資料轉換成字串,存放到arr中
	sprintf(arr, "%d %d %s", s.a, s.b, s.name);

	//將arr字串的中提取出一個格式化的資料,存放到tmp結構體中
	sscanf(arr,"%d %d %s",&tmp.a, &tmp.b, tmp.name );
	//列印結構體的資料
	printf("%d %d %s\n",tmp.a,tmp.b,tmp.name);
}

運行結果:
在這里插入圖片描述

檔案的隨機讀寫

在這里插入圖片描述

fseek

  • 將檔案指標移動到指定位置,
  • 根據檔案指標的位置和偏移量來定位檔案指標
int fseek( FILE *stream, long offset, int origin );

stream:FILE結構的指標
offset:從原點開始的位元組數
origin:初始位置

SEEK_CUR

Current position of file pointer – 檔案指標的當前位置

SEEK_END

End of file — 檔案終點

SEEK_SET

Beginning of file – 檔案起始位置

如果檔案指標指向的是中間位置c的話,那么從c的位置都是從0開始遞增d字符是偏移量為1,e是檔案指標的偏移量為2,e是檔案指標的偏移量3,(右邊為正數),而他的左邊b是檔案指標的偏移量為-1的位置,a是檔案指標偏移量為-2的位置
在這里插入圖片描述
如果檔案指標指向的是起始位置a的地方,那么從a的位置處開始往后偏移都是正數
在這里插入圖片描述
fseek 的使用代碼

void filetest() 
{
	
	//"rb" 為了輸入資料,打開一個二進制檔案
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt", "r");
	if(pf == NULL)
	{
		perror("file");
		exit(-1);
	}
	//檔案打開成功,從檔案指標的起始位置往后偏移2個位元組數,讀完一個字符檔案指標還會向后偏移一位元組
	fseek(pf,2,SEEK_SET);
	int ch = fgetc(pf);
	printf("%c\n", ch);

	//如果想往回再讀b,可以從當前位置向前偏移-2個位元組
	fseek(pf,-2,SEEK_CUR);
	ch = fgetc(pf);
	printf("%c\n", ch);
	//關閉檔案
	fclose(pf);
	pf = NULL;
}

從運行結果結果可以,fseek 確實是具有移動檔案指標的能力的,配合著fgetc函式使用,基本就是指哪打哪,即使是操作檔案也搓搓有余
在這里插入圖片描述

ftell

計算檔案指標相對于起始位置的偏移量函式

long ftell( FILE *stream );
函式功能:獲取檔案指標的當前位置,

在這里插入圖片描述

rewind

讓檔案指標的位置回到檔案的起始位置

void rewind ( FILE * stream );

上個例子我們已經看到了ftell他就像一個追蹤器一樣跟著檔案指標,檔案指標走哪他也走哪,那么接下來再介紹一個函式,比較霸氣的函式rewind,媽媽讓你回家吃飯你就回家吃飯檔案指標別亂跑
在這里插入圖片描述
從程式的運行結果可以看出檔案指標還是一個好孩子,已經回到了偏移量為0的起始位置,哈哈

文本檔案和二進制檔案

  • 根據資料的組織形式,資料檔案被稱為文本檔案或者二進制檔案,

  • 資料在記憶體中以二進制的形式存盤,如果不加轉換的輸出到外存,就是二進制檔案, .

  • 如果要求在外存上以ASCII碼的形式存盤,則需要在存盤前轉換,以ASCII字符的形式存盤的檔案就是文本檔案,

  • 一些二進制檔案比如exe,一些資料檔案比如txt

總結:

  • 1、二進制檔案就是在記憶體中以二進制形式存盤的資料不加轉換地輸出到外存(檔案)
  • 2、文本檔案要求在外存上以ASCII碼的形式存盤,則需要在存盤前轉換,轉換之后的資料輸出到外存上(檔案),

關于二進制檔案和文本檔案的區別

  • 一個資料在記憶體中是怎么存盤的呢? 字符一律以ASCII形式存盤,數值型資料既可以用ASCII形式存盤,也可以使用二進制形式存盤,
  • 如有整數10000,如果以ASCII碼的形式輸出到磁盤,則磁盤中占用5個位元組(每個字符一個位元組),而二進制形式輸出,則在磁盤上只占4個位元組(VS2013測驗)

測驗代碼:

#include <stdio.h>
int main()
{
 int a = 10000;
 FILE* pf = fopen("test.txt", "wb");
 fwrite(&a, 4, 1, pf);//二進制的形式將資料寫入檔案中
 fclose(pf);
 pf = NULL;
 return 0; }

那么10000是怎么以二進制的形式存放到記憶體中去的呢
10000的二進制序列:00000000 00000000 00100111 00010000
在這里插入圖片描述
10000的二進制序列轉化為16進制是 00 00 27 10
在這里插入圖片描述

當我們將程式運行后把這個二進制檔案一打開確實看見了10000對應的16進制,由于在vs2017是小端存盤所以低地址處存放低地址處資料,高地址處存放高地址處資料,所以看到的是反過來的
在這里插入圖片描述

檔案結束的判定

在這里插入圖片描述

  • 文本檔案讀取是否結束,判斷回傳值是否為EOF (fgetc),或者NULL(fgets)
  • 例如: fgetc判斷是否為EOF.
  • fgets判斷回傳值是否為NULL.

2.二進制檔案的讀取結束判斷,判斷回傳值是否小于實際要讀的個數, 例如: fread / fscanf 判斷回傳值是否小于實際要讀的個數

被錯誤使用的 feof

int feof( FILE *stream );
  • 牢記:在檔案讀取程序中,不能用feof函式的回傳值直接用來判斷檔案的是否結束,
  • 而是應用于當檔案讀取結束的時候,判斷是讀取失敗結束,還是遇到檔案尾結束

比如fgets是讀到檔案末尾了才結束,那么如果中途就停止讀檔案了,就可以使用feof 函式來判斷他是遇到檔案末尾了,還是中途讀取失敗

來看feof的使用

 int c; // 注意:int,非char,要求處理EOF
    FILE* fp = fopen("test.txt", "r");
    if(!fp) {
        perror("File opening failed");
        return EXIT_FAILURE;
   }
 //fgetc 當讀取失敗的時候或者遇到檔案結束的時候,都會回傳EOF
    while ((c = fgetc(fp)) != EOF) // 標準C I/O讀取檔案回圈
   { 
       putchar(c);
   }
 //判斷是什么原因結束的
 //如果檔案讀取失敗就會回傳錯誤資訊
    if (ferror(fp))
        puts("I/O error when reading");
 //feof判斷檔案是否成功讀到末尾
    else if (feof(fp))
        puts("End of file reached successfully");
    fclose(fp);

總結:

feof的用途:是檔案讀取結束了,判斷是不是遇到檔案末尾而結束的
ferror的用途:檔案讀取結束了,判斷是不是遇到錯誤后讀取結束

檔案緩沖區

  • ANSIC 標準采用“緩沖檔案系統”處理的資料檔案的,所謂緩沖檔案系統是指系統自動地在記憶體中為程式中每一個正在使用的檔案開辟一塊“檔案緩沖區”,

  • 從記憶體向磁盤輸出資料會先送到記憶體中的緩沖區,裝滿緩沖區后才一起送到磁盤上,

  • 如果從磁盤向計算機讀入資料,則從磁盤檔案中讀取資料輸入到記憶體緩沖區(充滿緩沖區),然后再從緩沖區逐個地將資料送到程式資料區(程式變數等),

  • 緩沖區的大小根據C編譯系統決定的,
    在這里插入圖片描述

驗證緩沖區的存在

void pfunc() 
{
	FILE* pf = fopen("test.txt", "w");
	if (!pf)
	{
		perror("fopen:");
		exit(-1);
	}
	fputs("abcdef",pf);//先將資料放在輸出緩沖區
	printf("已經休眠10秒鐘 -- 打開test.txt檔案,發現檔案沒有內容\n");
	Sleep(10000);
	printf("重繪緩沖區\n");
	//函式重繪緩沖區時,才將緩沖區的資料寫到檔案中去,而fflush函式就有重繪緩沖區的作用
	fflush(pf);
	printf("再休眠10秒鐘 -- 打開test.txt檔案,發現檔案有內容\n");
	//fclose函式再關閉檔案時也會重繪緩沖區
	fclose(pf);
	pf = NULL;
}

請添加圖片描述
fputs(“abcdef”,pf);代碼只是將資料輸出到了緩沖區中,等待10秒后當緩沖區被重繪的時候,才會將緩沖區的資料存放到檔案中

這里還可以得到結論:
因為有緩沖區的存在,c語言操作檔案的時候,需要重繪緩沖區或者在檔案結束的時候關閉檔案,如果不做,可能會無法保存

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/304374.html

標籤:其他

上一篇:線下精準大資料獲客

下一篇:【歷史上的今天】9 月 29 日:“美國支付寶” Stripe 正式上線;HotJava 面世;VR/AR 領域先驅誕生

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more