天天看點

2021-08-24 标準IO學習筆記标準IO前言一、為什麼需要标準IO二、标準IO的函數接口總結

标準IO

文章目錄

  • 标準IO
  • 前言
  • 一、為什麼需要标準IO
          • "檔案": 普通的文本檔案和二進制檔案
          • 文本檔案: 無組織、無格式的檔案,以字元的ASCII碼等其他編碼來解析的檔案。
          • 二進制檔案: 有特定格式的檔案。
          • ​還提供了對"檔案"操作的接口函數:
          • 使用标準IO的實作過程:
          • ​ 标準IO帶緩存的IO,IO流,它的效率要比系統IO要高,why?
          • beacase
          • 标準IO庫,會自動為每個程序,打開三個标準IO流(檔案):
          • 标準輸入流:FILE *stdin
          • 标準輸出流: FILE *stdout
          • 标準出錯:FILE *stderr
  • 二、标準IO的函數接口
    • (1)标準IO打開或關閉一個檔案流
      • fopen
      • fclose:用來關閉指定的檔案流
    • (2)讀寫流:一旦讀寫成功啦,光标會自動往後移n個位置(n就是你讀寫成功的位元組數)
      • (2.1)每次一個字元讀寫
      • (2.2)每次一行讀寫
      • (2.3)直接讀寫 fread/ fwrite
        • fread
        • fwrite
      • (3)沖洗流 fflush,同步
    • (4)定位流
    • (5)檔案出錯/結束标記
    • (6)格式化IO
      • (6.1)格式化輸入
  • 總結

前言

一、為什麼需要标準IO

因為每個作業系統下面,對檔案的管理和接口是不一樣的。

linux:open/read/write/close…,struct file, struct inode

windows:winopen,winclose,…

同一個檔案,在不同的作業系統下面,我們的操作檔案的代碼都不一樣。

c語言标準委員會,就統一了檔案操作的接口

–》标準IO庫: 主要統一對檔案操作的接口

“檔案”: 普通的文本檔案和二進制檔案
文本檔案: 無組織、無格式的檔案,以字元的ASCII碼等其他編碼來解析的檔案。

​ 如:.txt .c .h .s .cpp .java …

二進制檔案: 有特定格式的檔案。

​ 如:.jpg .bmp .gif .mp3 .mp4

在标準IO庫,用結構體 FILE結構體來描述或表示一個檔案,然後在這個結構體中建立了兩個緩沖區(一段記憶體),一個讀緩沖區,一個寫緩沖區

​		FILE  
​		{
​			char *in; //指向讀的緩沖區
​			char *out //指向寫的緩沖區 
​		
​		};  
           

​還提供了對"檔案"操作的接口函數:

fopen/fclose/fread/fwrite/fseek/…

puts/gets/fputs/fgets/scanf/printf…

使用标準IO的實作過程:

​ Your Code —>标準IO庫(如:fopen/fclose…) -->系統IO -->核心–>Hardware

​ FILE 有兩個緩沖區(标準IO庫中開辟的一段記憶體)

​ *in —> 讀的緩沖區

​ *out --> 寫的緩沖區

​ 标準IO帶緩存的IO,IO流,它的效率要比系統IO要高,why?
beacase

系統IO: read 1byte 從硬碟中讀一個位元組出來

​标準IO:

​ read 1byte,它會從硬碟上讀一塊(如:512byte)出來,放到 标準IO緩沖區。(記憶體條)

​ 标準IO:IO流,stream跟水流一樣,流走了就沒有了,讀走了就沒有了

​ 緩沖: 同步的問題。

​ 緩沖區中的資料,何時同步到外設上去呢?

​ 緩存區開多大呢? 标準IO緩沖區有三種類型:行緩沖、全緩沖、無緩沖

​ 行緩沖:緩沖區的資料達到一行,同步到外設上去。

​ 假設你設定一行頂多80個位元組。遇到\n(稱換行符,一行結束的标志)就會把緩沖區中的資料同步到外設上去

​ printf —> 行緩存

​			main()
​			{
​				printf("hello");
​				while(1);
​			
​			}
           

此時在螢幕上沒有“hello”列印出來,因為沒有hello後面沒有‘\n’,hello一直在緩沖區中,并沒有把緩沖區中的資料同步到外設上去。

​ 全緩沖: 緩沖區中資料要填滿整個緩沖區,才同步到外設上去。

​ 無緩沖: 緩沖區中有一個位元組,就同步到外設上去。

​ perror --> 無緩沖

标準IO庫,會自動為每個程序,打開三個标準IO流(檔案):
标準輸入流:FILE *stdin

​ stdin 是定義在<stdio.h>中的一個全局變量,它指向标準輸入裝置(一般為終端或鍵盤)

​ scanf() <-----stdin

标準輸出流: FILE *stdout

​ stdout是定義在<stdio.h>中的一個全局變量,它指向标準輸出裝置(控制台console,終端或螢幕)

​ printf() <----stdout

标準出錯:FILE *stderr

​ stderr是定義在<stdio.h>中的一個全局變量,它指向标準出錯裝置(一般為終端)

​ perror() <---- stdout

二、标準IO的函數接口

(1)标準IO打開或關閉一個檔案流

fopen

頭檔案

​ #include <stdio.h>

函數功能

​ 用來打開一個普通檔案(文本檔案/二進制檔案)

函數原型

​ FILE *fopen(const char *pathname, const char *mode);

函數參數

​ const char *pathname //要打開的那個檔案的檔案名

​ const char *mode //打開檔案的方式,有如下集中:

“r”: 隻讀打開。檔案不存在,則報錯; 打開後,光标在開頭。

​"r+": 讀寫打開。檔案不存在,則報錯;打開後,光标在開頭。

​"w": 隻寫打開。檔案不存在,則建立; 打開後,檔案内容截短(檔案内容被清掉)。

​"w+": 讀寫打開。檔案不存在,則建立; 打開後,檔案内容截短(檔案内容被清掉)

“a”: append 追加打開,檔案不存在,則建立; 打開後,光标在末尾。檔案内容不會被截短。

“a+”:讀寫打開。檔案不存在,則建立。 原始讀的位置在開頭,原始寫的位置在末尾

傳回值

​ 成功:傳回打開檔案指針。 FILE *

​ 在标準IO中,FILE *代表一個打開的檔案,後續标準IO庫的函數都需要要到它。

​ 失敗:傳回NULL,同時errno被設定

fclose:用來關閉指定的檔案流

頭檔案

​ #include <stdio.h>

函數功能

​ 用來關閉stream指定的檔案流

函數原型

​ int fclose(FILE *stream);

函數參數

​ FILE *stream //要關閉的檔案的FILE *

傳回值

​ 成功: 傳回0

​ 失敗: 傳回-1,同時errno被設定

(2)讀寫流:一旦讀寫成功啦,光标會自動往後移n個位置(n就是你讀寫成功的位元組數)

​ a:每次讀一個字元讀寫

​ fgetc/getc/getchar

​ fput/putc/putchar

​ b:每次一行讀寫

​ fgets/gets

​ fputs/puts

​ c:直接讀寫,你想要讀多少個對象都可以

​ fread

​ rwrite

(2.1)每次一個字元讀寫

​ fgetc/getc/getchar

​ fput/putc/putchar

頭檔案

​ #include <stdio.h>

函數功能

​ 用來從stream指定的檔案流中,讀取一個字元。

函數原型

​ int fgetc(FILE *stream);

函數參數

​ FILE *stream //指定要從哪個檔案流中讀取字元

傳回值

​ 成功: 傳回讀到的那個字元的ASCII碼

​ 失敗:傳回-1,同時errno被設定。

​ get和fget一樣,也是用來從stream指定的檔案流中,讀取一個字元。getc和fget的差別在哪裡?

​ fgetc是一個函數;

​ getc可能是用用來實作的

函數原型

​ int getc(FILE *stream);

​ getcha是用來從标準輸入流stdin中擷取一個字元,并把讀取到的字元的ASCII碼傳回。

函數原型

​ int getchar(void); 《==》 fgetc(stdin);

頭檔案

​ #include <stdio.h>

函數功能

​ 用來把c指定的字元,輸出到stream指定的檔案流中去,

函數原型

​ int fputc(int c, FILE *stream);

函數參數

​ int c //要輸出的字元的ASCII碼

​ FILE *stream //檔案流,表示輸出到哪個檔案中去。

傳回值

​ 成功:傳回實際寫入到檔案流中的字元的ASCII碼(c值)

​ 失敗:傳回-1,同時errno被設定。

​ putc與fputc功能與傳回值 一樣,隻不過putc的實作可能是由宏來實作的

函數原型

​ int putc(int c, FILE *stream);

​ putchar用來把c指定的字元,輸出到"标準輸出流檔案中"

函數原型

​ int putchar(int c); 《==》 fputc(c,stdout);

(2.2)每次一行讀寫

​ fgets/gets

​ fputs/puts

頭檔案

​ #include <stdio.h>

函數功能

​ 用來從标準輸入流(stdin)擷取一行字元,存儲到s指向的記憶體空間中去。

函數原型

​ char *gets(char *s);

函數參數

​ char *s //指向的空間,用來存儲從輸入緩沖區擷取到的多個字元

傳回值

​ 成功:非空 NULL

​ 失敗:傳回NULL,同時errno被設定

​ 注意:

​ gets有一巨大的bug,你懂的!!!

​ gets沒有考慮到s指向的空間的大小問題,存在越界的可能。 是以從今天開始,gets你們就不要用啦。

​ fgets修正了gets的這個bug

函數功能

​ 從stream所指向的檔案中讀取size個位元組大小的資料到s指向的空間中

函數原型

​ char *fgets(char *s, int size, FILE *stream);

函數參數

​ char *s //指向的空間用來儲存從檔案流中讀取的資料

​ int size //表示你要最多擷取size個位元組,size一般為s指向的

​ 空間的可用長度。

​ fgets輸入結束有兩種情況:

​ (1)遇到\n或檔案結束

​ (2)已經讀取到size-1個位元組啦(後面留一個\0的位置)

​ FILE *stream //FILE *指針,表示從哪個檔案流中讀取資料

傳回值

​ 成功:傳回s的首位址

​ 失敗:傳回NULL,同時errno被設定

頭檔案

​ #include <stdio.h>

函數功能

​ fputs用來把s指向的字元串,輸出到stream指定的檔案流中去

函數原型

​ int fputs(const char *s, FILE *stream);

函數參數

​ const char *s //指向要輸出的字元串的首位址

​ FILE *stream //表示要輸出到哪個檔案流中去

傳回值

​ 成功:傳回一個非負數

​ 失敗:傳回-1,同時errno被設定。

(2.3)直接讀寫 fread/ fwrite

fread

頭檔案

​ #include <stdio.h>

函數功能

​ 從stream指定的檔案流中,讀取nmemb個對象且每個對象size位元組,讀取到ptr指向的記憶體空間中去。

函數原型

​ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

函數參數

​ void *ptr //指向的記憶體空間,用來儲存從檔案流讀取到的"數組"

​ size_t size //每個元素占的位元組大小

​ size_t nmemb //要讀取的n個元素

​ FILE *stream //表示要從哪個檔案流中讀取資料

傳回值

​ 成功:傳回實際讀取到的元素個數 <= nmemb

​ 失敗:傳回-1,同時errno被設定。

fwrite

頭檔案

​ #include <stdio.h>

函數功能

​ 用來把ptr指向的nmemb個元素(每個元素占size位元組)寫入

​ 到stream指向的檔案流中去。

函數原型

​ size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);

函數參數

​ const void *ptr //要寫入的數組的首位址

​ size_t size //每個元素所占的大小

​ size_t nmemb //元素的個數

​ FILE *stream //表示要寫到哪個檔案流中去

傳回值

​ 成功:傳回實際寫入到檔案流中去的元素個數, <= nmemb

​ 失敗:傳回-1,同時errno被設定。

(3)沖洗流 fflush,同步

頭檔案

​ #include <stdio.h>

函數功能

​ 将緩沖區資料沖洗到對應的裝置上或者丢棄(同步到硬體裝置)

​ 對輸出流,fflush把寫緩沖區的内容寫/更新到檔案中去;

​ 對輸入流,fflush把讀緩沖區的内容直接discards(丢棄);

​ strea為NULL,fflush把該程序所有打開的輸出流檔案同步

函數原型

​ int fflush(FILE *stream);

函數參數

​ FILE *stream //要同步的檔案流

傳回值

​ 成功:傳回0

​ 失敗:傳回-1,同時errno被設定。

​ 練習:

​ 請分析如下程式的輸出結果?

​			main()
​			{
​				printf("hello"); //把hello寫到檔案 stdout的 輸出緩沖區
​								 //中去了,但并沒有寫入到外設檔案中去
​				sleep(10);
​				
​				fflush(NULL); //fflsh(stdout);  //if stream 輸出流,把輸出緩沖區的内容寫到
​												//外設檔案中去 
​												//if stream 輸入流,把輸入緩沖區的内容丢棄,這樣的話
​												//你下次讀,就可以重新從外設檔案中讀啦
​									
​				while(1);				
​			
​			}
           

(4)定位流

​ 上面講到的,fread/fwrite …隻是說要從哪個檔案,讀/寫多少個位元組的資料并沒有指定從檔案流的哪個位置開始讀寫,标志IO庫會為每個開打的檔案流儲存一個“檔案偏移量”

​ “檔案偏移量”:offset,“光标”。直接确定下一個讀/寫的起始位置。

​ 一般來說,每次讀或寫之前,先要定位流

​ 頭檔案

​ #include <stdio.h>

​ 函數功能

​ 定位一個檔案的偏移量

​ 函數原型

​ int fseek(FILE *stream, long offset, int whence);

​ 函數參數

​ FILE *stream //檔案流指針,表示你要定位哪個檔案

​ long offse //偏移量,可正可負;要結合第三個參數

​ int whence //從哪裡開始定位 定位的方式,有如下三種:

​ SEEK_SET:基于檔案開頭定位

​ 新光标位置 = 檔案開頭 + offset(>=0)

​ SEEK_CUR:current基于目前光标位置定位

​ 新光标位置=目前位置 + offset(可正可負)

​ SEEK_END:基于檔案末尾定位

​ 新光标位置=檔案末尾 + offset(可正可負)

​ 傳回值

​ 成功:傳回0

​ 失敗:傳回-1,同時errno被設定。

​ ftell傳回目前光标位置離檔案開頭有多少位元組

​ 函數原型

​ long ftell(FILE *stream);

​ rewind把檔案光标,定位在檔案開頭

​ 函數原型

​ long ftell(FILE *stream);

​ rewind(stream) => fseek(stream, 0, SEEK_SET);

(5)檔案出錯/結束标記

​ 頭檔案

​ #include <stdio.h>

​ 函數功能

​ 判斷stream指定的檔案流是否結束

​ 函數原型

​ int feof(FILE *stream);

​ 函數參數

​ FILE *stream //填要判斷的檔案流

​ 傳回值

​ 如果傳回真(非0) 檔案到達末尾啦

​ 如果傳回假(0) 該檔案還沒有到達末尾。

​ 注意:

​ 标準IO庫,在讀到檔案末尾時,會往緩沖區中填入一個EOF(二進制 11111111)

(6)格式化IO

(6.1)格式化輸入

​ scanf/sscanf/fscanf

​ “格式化輸入”?:按照我指定的格式來輸入。

​ 頭檔案

​ #include <stdio.h>

​ 函數功能

​ 按照我指定的格式來輸入

​ 函數原型

​ int scanf(const char *format, …);

​ 函數參數

​ const char *format,…

​ scanf可以帶很多個參數,scanf的參數可以分為兩類:

​ 第一個參數為第一類,格式化字元串,format string:

​ "格式化字元串"就是告訴使用者怎麼輸入的,意思是你必須​按照它指定的格式去輸入。

​ 在“格式化字元串”中有三類字元:

​ a.空白符(space tab)

​ 訓示使用者 你可以輸入任意數量的空白符(包含0個)scanf把\n當作是輸入結束

​ b.非轉義字元(普通字元,除空白符和%以外的字元)精準比對,你得原樣輸入

​ c.轉義字元(以%開頭的字元),有特殊含義:

​ %d -> [0-9]+

​ %c -> 比對一個字元(可以輸入的字元)

​ %f -> 浮點數

​ %s -> 字元串(不帶空白符,scanf把空白符當作是一個分割符号)

​ 其他參數為第二類,位址清單:

​ 格式化字元串中一個轉義字元會對應一個位址,把一個轉移字元的輸入存儲到指定的位址中去,如果轉移字元的個數 多餘 位址個數,程式行為将是 undefined(未定義的)

​ 傳回值

​ 成功比對的變量個數!!!

​ scanf擷取輸入時,如何結束?scanf從stdin的讀緩沖去中擷取輸入

​ a.該輸入的都輸入完了

​ sacnf(“abcd%d %cabcd”,&a,&c);

​ 使用者輸入:

​ abcd1234Aabcd -> 該輸入的都輸入啦,scanf結束

​ b.失敗啦

​ scanf(“abcd%d %cabcd”,&a,&c);

​ 使用者輸入:

​ ABCD-》scanf停止比對啦

​ 例子:

​ int r;

​ int a;

​ char c;

​ r = scanf(“abcd%d %c”,&a,&c);

​ 假設使用者入:

​ ABCD123 A

​ a=?

​ c=?

​ r=?

不比對是以a與c不是123與A ,r=0;

​ r = scanf("%d %c",&a,&c);

​ 加入使用者輸入:

​ 123B

​ a=123

​ c=B

​ r=2

​sscanf它的功能與傳回值 ,和scanf一樣的,隻不過sscanf的輸入來源不是stdin,而str指向的字元串。

sscanf的參數有三類:

​ 1.str是輸入來源字元串

​ 2.format是格式化字元串

​ 3.其他參數為位址清單

​函數原型

​ int sscanf(const char *str, const char *format, …);

​ 例子:

​ (1)char *str = “123456sdadwa”

​ int r;

​ int a;

​ char c;

​ r = sscanf(str, “%d %c”, &a,&c);

​ a=?

​ c=?

​ r=?

​ (2)

​ char *s = “123”;

​ int a;

​ sscanf(s,"%d",&a);

​ fscanf它的功能和傳回值,與scanf一樣,隻不過,fscanf它的輸入

​ 來源不是stdin,而stream指定的檔案流,是以fscanf的參數,分為三類:

​ 1.第一個參數FILE *,指定輸入來源

​ 2.第二個參數format,格式化字元串,與scanf是一樣

​ 3.其他參數為 位址清單

​ 函數原型

​ int fscanf(FILE *stream, const char *format, …);

​ scanf(format,…) <==> fscanf(stdin,format,…)

​ (6.2)格式化輸出

​ printf/sprinft/snprintf/fprintf

​ “格式化輸出”?:按照我指定的格式去輸出。

​ 頭檔案

​ #include <stdio.h>

​ 函數功能

​ 按照指定的格式去輸出

​ 函數原型

​ int printf(const char *format, …);

​ 函數參數

​ const char *format, …

​ printf可以帶多個參數 ,這麼多參數,可以分為兩類:

​ 第一個參數為第一類 格式化字元串,就是告訴你怎麼輸出的

​ 格式化輸出字元串有兩類:

​ a.轉義字元:以%開頭的字元

​ %d -> 按十進制有符号整數輸出

​ %u

​ %ld

​ %lu

​ %f

​ %c ->輸出字元的形狀

​ %s ->把後面的位址,字元串去輸出 直到遇到\0

​ …

​ b.非轉義字元

​ 你得原樣輸出

​ 其他參數為第二類,要輸出得變量或對象清單

​ 要輸出得變量或對象個數 應該與轉義字元的個數一緻

​ 傳回值

​ 實際列印的字元個數

​ 例子:

​ int a = 123;

​ char c = ‘A’;

​ int r;

​ r = printf(“a = %d c = %c”,a,c);

​ r =?

​ fprintf它的功能和printf一樣的,隻不過fprintf

​ 輸出不是輸出到stdout,而是輸出到stream指定的

​ 檔案中。是以fprintf它的參數,可以分為三部分:

​ 第一個參數為 FILE *,指定要輸出到哪個檔案中去

​ 第二個參數 format,格式化字元串

​ 其他參數,為要輸出的變量或對象清單

​ 函數原型

​ int fprintf(FILE *stream, const char *format, …);

​ 傳回值 :

​ 為實際輸出到檔案中的字元個數

​ printf(format,…) <==> fprintf(stdout,format,…);

​ sprintf它的功能和printf一樣,隻不過,sprintf

​ 輸出不是輸出到stdout,而是輸出到str指定的記憶體中去。

​ 是以sprintf參數,可以分為三部分:

​ 第一個參數為char *,記憶體位址,指定輸出字元串的輸出位置

​ 第二個參數為format,格式化字元串

​ 其他參數,為實際輸出的變量或對象清單

​ 函數原型

​ int sprintf(char *str, const char *format, …);

​ 傳回值 :

​ 為實際輸出到記憶體中去的字元個數。

​ 例子:

​ char filename[512];

​ char *file =“xxx.mp3”;

​ char *path ="/home/china";

​ sprintf(filename,"%s/%s",path,file);

​注意:但是,sprintf有一個bug

​ str隻是指定了一個記憶體的起始位址,并沒有限定它的記憶體範圍,if輸出字元串的長度 > str指向的記憶體的範圍,有越界的風險。

​ =》記憶體的非法通路

​ so,為了解決sprintf的這個bug,才有了 下面的snprintf ,snprintf中size指定str指向的那段記憶體空間的最大長度,也就是說,格式化的輸出頂多輸出size-1個字元到str 指向的空間中去。

​ 函數原型

​ int snprintf(char *str, size_t size, const char *format, …);

​ 例子:

​ char s[8];

​ char *str = “1234567890”;

​ int r;

​ r = snprintf(s, 8, “%s”,str);

​ r =

​ printf("%s",s);

​ 注意:

​ snprintf它的傳回值 是應該輸出的字元串長度,而不是實際輸出到記憶體中的長度。

總結

​1.标準IO庫是什麼東西? 為什麼需要它?它的實作原理是什麼?

2. 标準IO與系統IO之間的關系?标準IO中用什麼東西來表示一個檔案?

​3. 為什麼把标準IO稱為流?為什麼說标準IO的效率要系統IO高?

4.标準IO隻能操作 “普通檔案”!!!!

繼續閱讀