Linux標準IO概述

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()

  1. 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. 按照字符(字節)輸入
    字符輸入 / 輸出函數一次僅讀寫一個字符。要點總結一下,如下:
    字符輸入函數原型:
    1. int getc ( FILE * stream);
    2. int fgetc ( FILE * stream);
    3. int getchar ( void );
    getc()fgetc()從指定的流中讀取一個字符(節),getchar()stdin 中讀取一個字符(節)。
  2. 按照字符(字節)輸出
    字符輸入函數原型:
    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. 按照行輸入
    行輸入/輸出函數一次操作一行。
    行輸入函數總結一波如下:
    1. char * gets( char * s);
    2. char * fgets( char *s, int size, FILE* stream);
    注意:gets()函數不安全,容易造成緩衝區溢出 ,不建議使用。(原因:由於gets()不檢查字符串string的大小,必須遇到換行符或文件結尾纔會結束輸入,因此容易造成緩存溢出的安全性問題,導致程序崩潰,可以使用fgets()代替。)
    fgets()從指定的流中讀取一個字符,當遇到換行符 \n 時,會讀取 \n 或者讀取sizee -1個字符後返回。
    fgets()不能保證每次都能讀取到一行。

  2. 行輸出函數
    1. int puts( const char * s);
    2. int fputs( const char * s, FILE * stream);
    慣例哈,思考:怎麼獲取一個文本文件的行數。文後提供代碼

  3. 指定大小爲單位讀寫文件
    在文件流被打開以後,可對文件流按照指定大小爲單位進行讀寫操作。這裏簡單介紹一下,畢竟其他都差不多,只是函數參數和返回值的問題
    函數: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()函數在前面流的定位中已經講過相關參數,請理解偏移量,基準值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章