20191223張俊怡-學習筆記

20191223張俊怡 2021-09-19 17:32:22 阅读数:70

第九章-I/O庫函數

一、梗概

本章討論了I/O庫函數;解釋了I/O庫函數的作用及其相對於系統調用的優勢;使用示例程序來說明I/O庫函數和系統調用之間的關系 並解釋了它們之間的相似性和基本區 別;詳細介紹了I/O庫函數的算法,包括 fread、fw rite 和 fclose 的算法,重點介紹了它們與 read、write和close 系統調用的交互;介紹了I/O庫函數的不同模式,包括字符模式、行模式、結構化記錄模式和格式化I/0操作:闡述了文牛流緩沖方案,並通過示例程序說明了不同緩沖方案的效果;闡釋了有不同參數的函數以及女如何使用stdarg 宏訪問參數。
進入本章的學習之前,我覺得有必要了解一下什麼是流。
C裏的文件流,C裏標准文件方式把文件當作流來看,也就是有一個內部緩沖buffer,每次以標准方式打開文件時,讀入時先將數據讀到這個緩沖區裏,寫的時候也是先寫入緩沖區裏。
流分為兩種:文本流和二進制流
1.文本流在不同的系統中實現不太相同。
2.二進制流中的字節完全是安照程序編寫的形式寫入到文件和設備中,而且完全根據他們從文件或者設備讀取的方式讀入到程序。

文件
說起文件,很多人就會想到在stdio.h裏面定義了一個FILE的結構,但是在這裏,我要說明的是,千萬不要把它和磁盤上的文件混淆,我之前就總是認為FILE嘛,不就是個文件嘛!再次聲明,絕對不是!!!
FILE是一種數據結構,用於訪問一個流,如果你激活了幾個流,那麼每個流都會對應一個FILE結構。
對於每一個ANSI C程序而言,至少打開三個流:標准輸入(stdin)、標准輸出(stdout)、標准錯誤(stderr),它們都是指向FILE結構的指針。
注:標准輸入就是鍵盤設備輸入進去,標准輸出就是屏幕終端顯示的。

常見的I/O常量
最常見的也是我們所熟知的必定是EOF了,它是文件結束的標志,錶示文件到了結尾。 此外還有一些其他的I/O常量: FOPEN_MAX 錶示一個程序最多打開文件數。 FILENAME_MAX錶示文件名稱的最大長度。
I/O函數 文件I/O的一般概况:
1.程序必須給每個文件聲明一個指針變量,這個變量的類型為FILE*,當它處於活動狀態時為流所用。
2.流通過fopen函數打開,打開的時候必須指定需要訪問的文件或者設備以及訪問方式。例如:
FILE* fopen(const char* filename,const char*mode)
3.根據需要對文件進行讀取和寫入。
4.fclose函數可以關閉流,防止與它相關的文件再次被訪問,保證存儲在緩沖區的數據被正確的寫入到文件中。

二、知識點

1、I/O庫函數與系統調用

書中9.2節講到了系統調用函數和I/O庫函數的相同點和不同點。示例9.1的代碼中,他們的區別體現在:
(1)系統調用程序文件描述符fd是一個整數,庫I/O程序中,fp是一個文件流指針。
(2)系統調用open()執行失敗返回-1,I/O庫函數fopen()執行失敗返回NULL。
(3)fopen()發出open()系統調用以獲取文件描述符fd。如果open()調用失敗,將返回一個NULL指針。否則,它會在程序的堆區分配一個FILE結構體。FILE 結構體包含一個內部緩沖區char fbuf[BLKSIZE]和一個整數fd字段。它記錄open()在FILE結構體中返回的文件描述符,將fbuf[]初始化為空,並將FIL E結構體的地址作為fp返回。
(4)fgetc(c,fp)嘗試從文件流fp中獲取一個字符。如果FILE結構體中的fbufl] 為空,則發出read(fd,fbuf,BLKSIZE)系統調用, 從文件中讀取BLKSIZE字節,其中 BLKSIZE與文件系統塊大小匹配。然後它從fbuf]返回一個char。隨後,fgetc(從fbufl]返回一個char,只要它仍然有數據。這樣,庫I/O ead函數發出read()系統調用,僅用於重新填充fbufT1,它們總是將BLKSZISE字節的數據從操作系統內核傳輸到用戶空間。
總結:系統調用是需要時間的,程序中頻繁使用系統調用會降低程序的運行效率。
當運行內核代碼時,CPU工作在內核態,在系統調用發生前需要保存用的棧和內存環境,然後轉入內核態工作。
系統調用結束後,又要切換回用戶態,這種環境的切會消耗血多時間。
庫函數訪問文件的時候根據需要,設置不同類型的緩沖區,從而减少了直接調用IO系統的次數,提高了訪問效率。

2、I/O庫函數的算法

(1)fread()
頭文件:#include<stdio.h>
功能:是用於讀取二進制數據

原型:
size_t fread(voidbuffer,size_t size,size_t count,FILEstream);
buffer: 是讀取的數據存放的內存的指針,
(可以是數組,也可以是新開辟的空間)
#ps: 是一個指向用於保存數據的內存比特置的指針(為指向緩沖區保存或讀取的數據或者是用於接收數據的內存地址)
size: 是每次讀取的字節數
count: 是讀取的次數
stream: 是要讀取的文件的指針
#ps: 是數據讀取的流(輸入流)

返回值:
成功:是實際讀取的元素(並非字節)數目
失敗:返回0
ps:如果輸入過程中遇到了文件尾或者輸出過程中出現了錯誤,這個數字可能比請求的元素數目要小

(2)fwrite()
功能:是用於寫入二進制數據
頭文件:#include<stdio.h>

原型:
size_t fwrite(voidbuffer,size_ size,size_t count,FILEstream)
buffer:是一個指向用於保存數據的內存比特置的指針
(是一個指針,對於fwrite來說,是要獲取數據的地址)
size: 是每次讀取的字節數
count: 是讀取的次數
stream: 是數據寫入的流(目標指針的文件)

返回值:
是實際寫入的元素(並非字節)數目
ps:如果輸入過程中遇到了文件尾或者輸出過程中出現了失誤,這個數字可能比請求的元素數目要小

(3) fopen/fclose
在操作文件之前要用fopen打開文件,操作完畢要用fclose關閉文件。打開文件就是在操作系統中分配一些資源用於保存該文件的狀態信息,並得到該文件的標識,以後用戶程序就可以用這個標識對文件做各種操作,關閉文件則釋放文件在操作系統中占用的資源.
<#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
返回值:成功返回文件指針,出錯返回NULL並設置errno
path是文件的路徑名,mode錶示打開方式。如果文件打開成功,就返回一個FILE *文件指針來標識這個文件。以後調用其它函數對文件做讀寫操作都要提供這個指針,以指明對哪個文件進行操作。FILE是C標准庫中定義的結構體類型,其中包含該文件在內核中標識、I/O緩沖區和當前讀寫比特置等信息。
mode參數是一個字符串,由rwatb+六個字符組合而成,r錶示讀,w錶示寫,a錶示追加(Append),在文件末尾追加數據使文件的尺寸增大。t錶示文本文件,b錶示二進制文件,有些操作系統的文本文件和二進制文件格式不同,而在UNIX系統中,無論文本文件還是二進制文件都是由一串字節組成,t和b沒有區分,用哪個都一樣,也可以省略不寫。如果省略t和b,rwa+四個字符有以下6種合法的組合:
"r"
只讀,文件必須已存在

"w"
只寫,如果文件不存在則創建,如果文件已存在則把文件長度截斷(Truncate)為0字節再重新寫,也就是替換掉原來的文件內容

"a"
只能在文件末尾追加數據,如果文件不存在則創建

"r+"
允許讀和寫,文件必須已存在

"w+"
允許讀和寫,如果文件不存在則創建,如果文件已存在則把文件長度截斷為0字節再重新寫

"a+"
允許讀和追加數據,如果文件不存在則創建

(4)其他I/O庫函數
fseek()、ftell(、rewind():更改文件流中的讀/寫字節比特置。
feofO、ferr()、fileno():測試文件流狀態。
fdopen():用文件描述符打開文件流。
freopen(:以新名稱重新打開現有的流。
setbufO、setvbuf():設置緩沖方案。
popen():創建管道,複刻子進程來調用sh。

3、結構體文件的讀寫

20191223 張俊怡
20191000 小明

1.定義結構體
//定義一個結構體
typedef struct Student{
int stu_id;
char name[100];
} Stu;

2.寫數據
// 定義一個文件指針
FILE *fp ;

// 初始化一個結構體數組
Stu stuw[2] = {
{20191223, "張俊怡" },
{20191000, "小明"}
} ;

// 打開文件,沒有文件自動創建
fp = fopen("student.dat","wb"); // b:錶示以二進制寫入
// 寫入數據
fwrite( (char*)stuw,sizeof(Stu),2,fp); //2:錶示將數組中兩個元素寫入文件
// 關閉文件
fclose(fp);

3.讀數據
// 定義一個文件指針
FILE *fp ;
// 定義一個buf結構體,用於得到文件內容
struct stat buf;
// 定義一個文件行數記錄變量
int rows;
// 定義一個Student結構體
Stu stur[MAX]; // MAX通過#define設置為100

// 求文件中的行數(記錄個數)
stat("student.dat",&buf);
rows = buf.st_size/sizeof(Stu);

// 打開文件
fp = fopen("student.dat","rb");
// 讀取數據到數組中
fread((char *)stur,sizeof(Stu),rows,fp);
// 關閉文件
fclose(fp);

// 遍曆數組,打印數據信息
for(int i=0;i<rows;i++)
printf("%d\t%s\n",stur[i].stu_id,stur[i].name);

三、最有收獲的內容

本章最有收獲的內容是深入理解庫函數與系統調用的區別和聯系。
·庫函數:庫函數是語言或應用程序的一部分,可以運行在用戶空間中。
·系統調用:又稱廣義指令,它是由操作系統向程序提供的程序接口,而非直接向用戶提供,用戶只能通過程序間接的使用這些接口。
1.在概念對比中,可以直觀的感觸到系統調用是依賴於操作系統的,由於其依賴於平臺,所以系統調用的平臺移植性較差。
2.而函數庫,是將一些已經編寫好函數進過封裝,存放到函數庫(靜態庫或動態庫)中,是具有特定功能函數的集合。 通過庫文件向程序員提供相關的函數,以便於調用。程序員不需要關心平臺的差异性,如linux或windows,由庫對不同平臺差异屏蔽。
·關於系統調用的幾點說明:
1、系統調用的目的:為了是系統更加穩定安全,防止小白用戶、惡意用戶進行非法的越權操作。
2、從用戶態切換到內核態必須要通過”中斷”,只要發生中斷,就需要對中斷進行處理,也不然會切換到內核點。

總結:

庫函數是語言或應用程序的一部分,可以運行在用戶空間中。而系統調用是操作系統的一部分,是內核提供給用戶的程序接口,運行在內核空間中,而且許多的庫函數都會使用系統調用實現功能,如在linux下C中的fopen、fclose、fwrite等文件操作函數其底層就是通過open、close、write等系統調用是實現的。沒有使用系統調用的庫函數,執行效率通常比系統調用高。因為使用系統調用時,需要通過中斷進行上下文的切換以及由用戶態向內核態的轉移。

四、問題與解决思路

1、文本文件與二進制文件的優缺點是什麼?

解决思路:我想文本文件與二進制文件的區別體現在編碼方式的不同,簡單來說,文本文件是基於字符編碼的文件,常見的編碼有ASCII編碼,UNICODE編碼等等。二進制文件是基於值編碼的文件。所以他們的優缺點就是編碼的優缺點。
文本文件編碼基於字符定長,譯碼容易些;二進制文件編碼是變長的,所以它靈活,存儲利用率要高些,譯碼難一些。文本文件的可讀性要好些,存儲要花費轉換時間(讀寫要編譯碼),而二進制文件可讀性差,存儲不存在轉換時間。,因為我們用通用的記事本工具就幾乎可以瀏覽所有文本文件,所以說文本文件可讀性好;而讀寫一個具體的二進制文件需要一個具體的文件解碼器,所以說二進制文件可讀性差。

2、文件操作有哪些?

1.cat命令
一般格式: cat [選項] 文件
有兩項功能:在標准輸出上顯示文件的內容;連接兩個或多個文件
如: $ cat f1 f2>f3
常用選項:
-b,–number-noblank 從1開始對所有非空輸出行進行編號。
-n,–number 從1開始對所有輸出行編號。
-s,–squeeze-blank 將多個相鄰的空行合並成一個空行。
–help 打印該命令用法,並退出,其返回碼錶示成功。
2.more命令
一般格式: more [選項] 文件
說明:該命令一次顯示一屏文本,滿屏後停下來,並且在屏幕的底部出現一個提示信息,給出至今已顯示的該文件的百分比:–More–(XX%)。
常用選項:
-num,這個選項指定一個整數,錶示一屏顯示多少行。
-d,在每屏的底部顯示以下更友好的提示信息:
–More–(XX%)[Press space to continue,'q' to quit.]
-c或-p,不滾屏,在顯示下一屏之前先清屏。
-s,將文件中連續的空白行壓縮成一個空白行顯示。
+/,該選項後的模式(Pattern)指定顯示每個文件之前進行搜索的字符串。
+num,從行號num開始。

3.less命令

less命令允許用戶向前或向後瀏覽文件,而more命令只能向前瀏覽。
4.head命令

一般格式: head [選項] file
說明:head命令在屏幕上顯示指定文件的開頭若幹行,行數由參數值來確定。顯示行數的默認值是10。
選項:
-c,–bytes=SIZE 顯示前面SIZE個字節。
-n,–lines=NUMBER NUMBER的值指定顯示前面多少行。默認為10行。
-q,-quiet,–silent 不顯示給定文件的標題。
-v,–verbose 始終顯示給定文件的標題。

5.tail命令

一般格式: tail [選項] [file] …
說明:tail命令在屏幕上顯示指定文件的末尾10行。如果給定的文件不止一個,則在顯示的每個文件前面加一個文件名標題。如果沒有指定文件或者文件名為“-”,則讀取標准輸入。
選項:
-c,–bytes=N 輸出最後N個字節。
-f 當文件增長時輸出附加的數據。
-n,–lines=N 輸出最後的N行,而不是默認的10行。
-q,-quiet,–silent 不輸出包含給定文件名的標題。
-v,–verbose 始終輸出包含給定文件名的標題。
6.touch命令

一般格式: touch [選項] 文件名 …
說明:touch命令將會修改指定文件的時間標簽,把已存在文件的時間標簽更新為系統當前的時間(默認方式),它們的數據將原封不動地保留下來。如果該文件尚未存在,則建立一個空的新文件。
選項:
-a 僅改變指定文件的存取時間。
-c 不創建任何文件。
-m 僅改變指定文件的修改時間。
-t STAMP 使用STAMP指定的時間標簽,而不是系統當前的時間 。

五、實踐內容

1、編寫一個c程序,將文本文件中的字母由大寫轉換為小寫。

首先在linux下創建一個文本文件,內容為大寫字母。

接著編寫c語言程序

·編譯,運行,輸入文本文件路徑,顯示結果:

2、編寫一個c程序,計算文本文件的行數

代碼如圖:

文本文件內容:

運行結果:

版权声明:本文为[20191223張俊怡]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210919173221879w.html