Linux標準I/O概述
文章目錄
1.0 標準I/O 的由來
標準IO是指ANSI C(ANSI C是美國國家標準協會(ANSI)對C語言發佈的標準)中定義的用於I/O操作的一系列函數。
只要操作系統中安裝了C庫,標準I/O函數就可以調用,換句話說,如果程序中使用的是標準I/O函數,那麼源代碼不需要修改就可以在其它操作系統下編譯運行,具有更好的可移植性
在執行系統調用時,Linux必須從用戶態切換到內核態,處理相應的請求,然後再返回到用戶態。如果頻繁執行必定增加系統開銷,爲避免這種情況,標準I/O使用時在用戶空間創建緩衝區,讀寫實先操作緩衝區,再通過系統調用訪問世紀的文件,而減少系統調用的次數。
1.2 流的含義
標準I/O的核心對象就是流。那流到底是什麼呢?
當使用標準I/O打開一個文件時,就會創建一個FILE結構體,這個結構體用來描述改文件(可以理解爲創建一個FILE結構體和實際打開的文件進行關聯起來),我們把這個FILE結構體就稱爲流,標準I/O函數都基於流進行所有的操作。
1.3 標準I/O的緩衝機制
在前面我們提到了標準I./O是有緩衝機制的,緩衝區創建於用戶空間,那麼緩衝的具體類型有哪些呢?
其實,標準I/O中流的緩衝類型有3種:
1、全緩衝: 當填滿標準I/O緩衝區後再進行實際的I/O操作。
對於存放在磁盤上的普通文件,標準I/O打開的默認就是全緩衝。當緩衝區已滿或者執行FULSH操作室纔會進行磁盤操作。
2、行緩衝:當在輸入和輸出中遇到換行符時執行I/O操作。
標準輸入流和輸出流就是使用行緩衝,想想printf();
的格式,就是遇到\n
後進行輸出,行緩衝。
3、無緩衝: 不對I/O操作進行緩衝。
即對流的讀寫時會立刻操作實際的文件。標準出錯流是不帶緩衝的,理論上所有出錯輸出都不應該是緩衝的,因爲出錯就應該立刻輸出。
1.4 標準I/O編程
本節只介紹最常用的函數。本就所討論的I/O操作都是基於流的,它包含ANSI C的標準。
首先扔一個問題,想想 open() 與 fopen()的區別?
1.4.1 流的打開
使用標準I/O打開的函數有fopen(),fdopen(),freopen()。它們可以用不同的模式打開文件,每一個都返回一個指向FILE的指針。 此後,對文件的讀寫都通過這個FILE指針來進行。
fopen():可以指定打開文件的路徑和模式;
fdopen():可以指定打開文件的描述符和模式;
freopen():可以指定打開的文件和模式,還可以指定特定的 I/O 流;
注意:給環境變量賦值需要用 export
, 而不是直接path = "********"
有些人可能會疑惑 “b”
是幹嘛的,其實在每個選項中加入 b
字符是用來告訴函數庫打開的文件是二進制文件,不是純文本文件,不過在linux系統中會忽略該符號。
注意: 當用戶進程運行時,系統會自動打開 3 個流: 標準輸入流stdin;標準輸出流stdout;標準錯誤流 stderr。
標準輸入流stdin:用來從標準輸入設備(默認鍵盤)中讀取輸入內容;
標準輸出流stdout:用來從標準輸出設備(默認當前終端)中輸出內容;
標準錯誤流 stderr:用來向標準錯誤設備(默認當前終端)中輸出錯誤信息;
1.4.2 流的關閉
關閉流的函數爲 fclose()
;該函數將流的緩衝區的數據全部寫入文件中,並釋放相關資源。
注意:程序結束時,會自動關閉所有的打開的流
1.4.3 錯誤信息
標準 I/O 函數執行時如果出現錯誤,會把錯誤碼保存在全局變量 errno
中,程序員可以打印錯誤信息,處理錯誤的相關函數:1. perror(); 2. strerror()
perror():
其實,perror()
函數的使用還是比較簡單的,在之前的錯誤信息輸出中很多同學採用的是標記性輸出,printf("************");
這種方式,這樣可以利用輸出的信息來斷點在某個區間,但無法獲取準確的錯誤信息,採用perror()
可以得到詳細的錯誤原因。
來,咱不止說,拿個例子試試看。
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp; //指定流指針
if ( NULL == ( fp = fopen( "a.txt","r"))) // NULL爲系統宏,用流指針接收 fopen()的返回值;
{
perror(" fail to open");
return -1;
}
fclose(fp);
return 0;
}
接下來,我們看看實際的效果
錯誤信息分析:
fail to open: No such file or directory
錯誤處理相關函數 :
2. strerror()
同樣的,我們拿個例子試試手,看看怎麼操作,什麼結果。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[])
{
FILE *fp; //指定流指針
if ( NULL == ( fp = fopen( "AA.txt", "r")))
{
printf(" fail to fopen: %s\n", strerror(errno));
return -1;
}
fclose(fp);
return 0;
}
在當前路徑下,並沒有“AA.txt”
文件。看看結果:
根據這兩個錯誤信息的打印,能夠知道這倆個都可以將具體的錯誤原因進行羅列輸出。
1.4.4 流的寫入
- 按照字符(字節)輸入
字符輸入 / 輸出函數一次僅讀寫一個字符。要點總結一下,如下:
字符輸入函數原型:
1. int getc ( FILE * stream);
2. int fgetc ( FILE * stream);
3. int getchar ( void );
getc()
和fgetc()
從指定的流中讀取一個字符(節),getchar()
從stdin
中讀取一個字符(節)。 - 按照字符(字節)輸出
字符輸入函數原型:
1. int putc ( FILE * stream);
2. int fputc ( FILE * stream);
3. int putchar ( void );
putc()
和fputc()
從指定的流中讀取一個字符(節),putchar()
向stdout
中輸出一個字符(節)。
同樣,我們還是簡單的實驗一下,結合fputc() 和 fgetc()嘗試一下。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[])
{
int c;
while( 1 ) //循環
{
c = fgetc (stdin); // 從鍵盤讀取一個字符
if (( '0' < c) && ( '9' >= c) ) // 字符判斷是否爲數字
{
fputc( c,stdout); // 滿足標準輸出
}
else if( '\n' == c) // 當遇到‘\n’時跳出
{
break;
}
}
return 0;
}
運行後輸入1321rrerf
,最終結果:1321
。
-
按照行輸入
行輸入/輸出函數一次操作一行。
行輸入函數總結一波如下:
1. char * gets( char * s);
2. char * fgets( char *s, int size, FILE* stream);
注意:gets()函數不安全,容易造成緩衝區溢出 ,不建議使用。(原因:由於gets()不檢查字符串string的大小,必須遇到換行符或文件結尾纔會結束輸入,因此容易造成緩存溢出的安全性問題,導致程序崩潰,可以使用fgets()代替。)
fgets()從指定的流中讀取一個字符,當遇到換行符 \n 時,會讀取 \n 或者讀取sizee -1個字符後返回。
fgets()不能保證每次都能讀取到一行。 -
行輸出函數
1. int puts( const char * s);
2. int fputs( const char * s, FILE * stream);
慣例哈,思考:怎麼獲取一個文本文件的行數。文後提供代碼 -
指定大小爲單位讀寫文件
在文件流被打開以後,可對文件流按照指定大小爲單位進行讀寫操作。這裏簡單介紹一下,畢竟其他都差不多,只是函數參數和返回值的問題
函數:1. fread() ;2. fwrite();
fread():
fwrite();
1.4.5 流的定位
每個打開的流內部都有一個當前讀寫位置。留在打開時,當前讀寫位置爲 0,表示文件的開始位置,每讀寫一次後,當前讀寫位置自動增加實際讀寫的大小,在讀寫流之間可先對流進行定位,即移動到當前指定的位置再進行操作。
函數1. fseek()
, 2. ftell();
fseek()
:
ftell():
慣例哈,思考:怎麼獲取一個文件的大小。文後提供代碼
好了,今天我們就將Linux標準I/O編程進行了簡單的介紹,其中很多東西還需要朋友們自己去消化,比如偏移量是什麼,基準值是什麼,緩衝區在用戶空間什麼位置,怎麼確定緩衝區到底多大等等,篇幅問題就不過多介紹。
1.6 總結
接下來,我們進行兩個問題的解答:
1、 怎麼計算一個文本文件的行數,比如算一下當前路徑下 1.txt
有多少行。
首先拿到題目,看目標,計算文本文件的行數,那麼,回憶一下用什麼行數可以來實現呢?
——行輸入函數
可是行輸入函數有 gets()
和 fgets()
,到底用什麼呢?
——fgets()
那爲什麼要選擇fgets()
而不用gets()
呢?
前面已經都介紹過了,自己回憶回憶,就不再重複介紹了。這道題我們用fgets()
來實現一下。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[])
{
int line = 0; // 保存具體的行數
char buf[128]; // 用數組來裝輸入的字符串,128字節大小
FILE *fp; // 指定流
if( argc < 2) // 參數個數,其實就是要求文件和 a.out一起執行
{
printf( "Usage: %s<file>\n", argv[0]);
return -1;
}
if ( NULL == ( fp = fopen( argv[1], "r"))) // 判斷是否正常打開文件
{
perror( "fail to open");
return -1;
}
while( NULL != fgets( buf, 128, fp)) // 循環讀取,buf是數組名,也是首地址
{
if ( '\n' == buf[ strlen(buf) -1]) // 換行符即代表着新的一行了嘛,這就是行數增加的依據
{
line++;
}
}
printf(" The lines of %s is %d \n",argv[1],line);
return 0;
}
來來來,運行一下試試。在當前路徑下創建一個 1. txt
,然後聯合a.out
運營一下看看實際效果
2、怎麼計算一個文件的大小。
思路理一理:
想想,文件的大小,這是什麼玩意?
文件大小是什麼意思?
大小,在計算機中指的是什麼?
單位是什麼?
這個單位是最小的單位嗎?
如果不是,那和最小的單位換算關係是什麼?
最小的單位又是什麼?
最小的單位怎麼計算呢?
來來來,繼續哈,想清楚了就繼續搞起來。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ( argc <2)
{
printf( "Usage: %s<file>\n ", argv[0]);
return -1;
}
if ( NULL == (fp = fopen( argv[1], "r")))
{
perror(" fail to open");
return -1;
}
fseek(fp, 0, SEEK_END);
printf( " The size of %s is %d k \n", argv[1],ftell(fp)) ;
return 0;
}
fseek()函數在前面流的定位中已經講過相關參數,請理解偏移量,基準值。