【UNIX/Liux】標準I/O庫【Part 2】

2020-08-12 18:54:39

本文是筆者拜讀《UNIX環境高階程式設計》第5章(標準I/O庫)的學習筆記。本文的主要內容包括二進制I/O、定位流、格式化I/O、臨時檔案。文中不僅包含書中的知識點,也包括筆者的理解。

二進制I/O

在執行二進制I/O操作時,通常一次性讀寫一個完整的數據塊。下面 下麪兩個函數執行二進制I/O操作。
在这里插入图片描述
size表示每個數據項的大小,nmemb表示要讀寫的數據項數目。
這些函數有以下兩種常見的用法:
(1) 讀寫一個二進制陣列。例如,將一個浮點陣列的第2~5(從0開始計數)個元素寫到一個檔案上。

float data[10];
if (fwrite(&data[2], sizeof(float), 4, fp) != 4) {
	perror("fwrite error");
}

(2) 讀或寫一個結構。

struct {
	short count;
	long total;
	char name[NAMESIZE];
} item;
if (fwrite(&item, sizeof(item), 1, fp) != 1) {
	perror("fwrite error");
}

二進制I/O只能用於讀寫在同一系統上的數據。而在如今的異構系統中,常常在一個系統上寫數據,在另一個系統上進行處理。這種情況下,freadfwrite可能就不能正常工作,因爲:
(1)在一個結構中,同一成員的偏移量可能隨編譯程式和系統的不同而不同。
(2)多位元組整數和浮點值的二進制格式在不同的系統結構間也可能不同。
在不同系統之間交換二進制數據的實際解決方法是使用互認的規範格式。

定位流

3種方法定位標準I/O流。
(1) ftellfseek函數。這些函數假定檔案的位置可以存放在一個長整型中。
(2) ftellofseeko函數。使用off_t數據型別表示檔案偏移量。
(3) fgetposfsetpos函數。這些函數使用一個抽象數據型別fpos_t記錄檔案的位置,這種數據型別可以根據需要定義爲一個足夠大的數。
在这里插入图片描述
ftell返回當前檔案位置指示;fseek設定檔案位置。檔案位置相當於檔案偏移量,fseek相當於lseek。使用rewind可將一個流設定到檔案的起始位置。

除了偏移量型別以外,ftelloftell相同,fseekofseek相同。

fgetpos將檔案位置指示器的當前值存入由pos指向的物件中(功能類似於ftell),呼叫fsetpos時,可以使用此值將流重新定位到該位置。

格式化I/O

格式化輸出

在这里插入图片描述
printf將格式化數據寫到標準輸出,fprintf寫至指定的流,dprintf寫至指定的檔案描述符,sprintf寫至指定的陣列(在陣列尾端自動加一個'\0')。

爲了解決緩衝區溢位問題,引入了指定緩衝區長度的snprintf函數,超過緩衝區尾端的字元都會被丟棄。

格式說明控制其餘參數如何編寫,如何顯示。每個參數按照轉換說明編寫,轉換說明以%開始。除轉換說明外,格式字串中的其它字元將按原樣輸出。一個轉換說明有4個可選擇部分:
%[flags][fldwidth][precision][lenmodifier]convtype

flags 說明
' 將整數按千位分組
- 左對齊
+ 帶正負號
(空格) 如果第一個字元不是正負號,則在前面加上一個空格
# 指定另一種轉換格式
0 使用0進行填充

fldwidth指定了最小欄位寬度。轉換後參數字元數若小於寬度,則多餘字元用空格填充。字元寬度是一個非負的十進制數,或者*

precision指定了精度。例如整型的數位位數、浮點數的小數位數、字串的長度。精度表示爲.後面跟個可選的非負十進制數或*

寬度和精度欄位皆可爲*。此時,一個整型參數指定寬度或精度的值,該整型參數正好位於被轉換的參數之前。

lenmodifier說明參數長度。

長度修飾符 說明
hh 將相應的參數按signedunsigned char型別輸出
h 將相應的參數按signedunsigned short型別輸出
l 將相應的參數按signedunsigned long型別輸出
ll 將相應的參數按signedunsigned long long型別輸出
j intmax_tuintmax_t
z size_t
t ptrdiff_t
L long double

convtype是必選的,它控制如何解釋參數。

轉換型別 說明
d,i 有符號十進制
o 無符號八進制
u 無符號十進制
x,X 無符號十六進制
f,F 雙精度浮點數
e,E 指數格式雙精度浮點數
g,G 根據轉換後的值解釋爲f,F,eE
a,A 十六進制指數格式雙精度浮點數
c 字元(若帶長度修飾符l,則爲寬字元)
s 字串(若帶長度修飾符l,則爲寬字元)
p void*
n 到目前爲止,此printf呼叫輸出的字元的數目將被寫入到指針所指向的帶符號整型中
% 一個%字元
C 寬字元(等效於lc
S 寬字串(等效於ls

常規的轉換是按照format...中參數出現的順序的,一種替代的轉換說明語法也允許顯式地用%n$序列來表示第n個參數的形式來命名參數(從1開始計數)。這兩種語法不能在同一格式說明中混用。如果參數既沒有提供欄位寬度也沒有提供精度,萬用字元星號的語法就更改爲*m$m指明提供值的參數的位置。

vprintf族是printf族的變體,它的可變參數表...被替換成了va_list
例:

#include <stdio.h>

int main() {
	char str[] = "hello";
	char ch = 'z';
	int a = -123;
	int b = 12339783;
	double c = 13.234;
	printf("str: %-10s!\n", str);
	printf("ch: %c!\n", ch);
	printf("a: %+d!\n", a);
	printf("b: %+10d!\n", b);
	printf("c: %.2f!\n", c);
	return 0;
}

執行結果:
在这里插入图片描述

格式化輸入

在这里插入图片描述
scanf族用於分析輸入字串,並將字元序列轉換成指定型別的變數,格式之後的個參數包含了變數的地址,用轉換結果對這些變數賦值。

格式說明控制如何轉換參數,以%開始。除轉換說明和空白字元外,格式字串中的其它字元必須與輸入匹配,若有一個不匹配,則停止後續處理。
%[*][fldwidth][m][lenmodifier]convtype

*用於抑制轉換。按照轉換說明的其餘部分對輸入進行轉換,但轉換結果並不存放在參數中。

dlfwidth說明最大寬度(最大字元數)。lenmodifier說明要用轉換結果賦值的參數大小。由printf函數族支援的長度修飾符同樣得到scanf族函數的支援。

convtype欄位類似於printf族的轉換型別欄位,但輸入中帶符號的可賦予無符號型別。例如輸入流中的-1被轉換成UINT_MAX

在欄位寬度和長度修飾符之間的m是賦值分配符。它可用於%c%s以及%[轉換符,迫使記憶體緩衝區分配空間以接納轉換字串。

scanf族同樣支援顯式地命名參數。

scanf的轉換型別與printf大概一樣。

轉換型別 說明
[ 匹配列出的字元序列,以]終止
[^ 匹配除列出字元以外的所有字元,以]終止

例:
練習scanf函數。

#include <stdio.h>

int main() {
	int a;
	float b;
	char c[100];
	char d;
	int e;

	int n = fscanf(stdin, "%d %f %s %*[a-zA-Z] %c %d", &a, &b, c, &d, &e);
	fprintf(stdout, "get %d variables\n", n);
	fprintf(stdout, "%d %.2f %s station %c %d\n", a, b, c, d, e);
	return 0;
}

執行結果:
在这里插入图片描述
程式中%*[a-zA-Z]表示,如果匹配到了一個或多個字母,則忽略它們(不用將讀到的字串賦值給某個變數),否則函數返回或匹配下一個轉換說明。
%*[^a-zA-Z]表示,如果匹配到了一個或多個非字母,則忽略它們,否則函數返回或匹配下一個轉換說明。

scanf裡的內容真複雜,筆者說不清楚。

實現細節

每個標準I/O流都有一個與其相關聯的檔案描述符,可以對一個流呼叫fileno函數以獲得其描述符。
在这里插入图片描述
在列印緩衝狀態資訊之前,先對每個流執行I/O操作,第一個I/O操作通常就造成爲該流分配緩衝區。在不同系統中,標準I/O庫的實現有所不同,標準定義的是宣告(介面)。

當標準輸入、標準輸出連至終端時,它們是行緩衝的。當將這兩個流重定向到普通檔案時,它們就是全緩衝的。不論重定向與否,標準錯誤流始終是不帶緩衝的。

臨時檔案

以下函數建立臨時檔案。
在这里插入图片描述
在这里插入图片描述
tmpnam函數產生一個與現有檔名不同的一個有效路徑名字串。
sNULL,則產生的路徑名存放在靜態區中,指向該靜態區的指針作爲函數的返回值。後續呼叫tmpnam時,會重寫該靜態區。
s不是NULL,則認爲它指向一個字元陣列,所產生的路徑名存放在該陣列中,返回s
tmpfile使用tmpnam生成的路徑名(通常在/tmp/目錄下)建立一個臨時二進制檔案(型別wb+),在關閉該檔案或程式結束時會自動刪除該檔案。

mkdtempmkstemp也能建立臨時檔案。
在这里插入图片描述
在这里插入图片描述
mkdtemp函數建立了一個目錄,該目錄有一個唯一的名字;mkstemp函數建立了一個檔案,該檔案有一個唯一的名字。名字是通過template字串進行選擇的。這個字串是後6位設定爲XXXXXX的路徑名,函數將這些佔位符替換成不同的字元來構建一個唯一的路徑名。

mkdtemp函數建立的目錄使用下列存取許可權位集:S_IRUSR | S_IWUSR | S_IXUSR,可以呼叫umask進一步限制這些許可權。mkdtemp返回新目錄的名字。

mkstemp建立的臨時檔案使用存取許可權位S_IRUSR | S_IWUSRmkstemp建立的臨時檔案不會被自動刪除,必須對它解除鏈接。
例:
臨時檔案的建立和關閉

// temp.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
	char buf1[100];
	tmpnam(buf1);
	FILE *fp = tmpfile();
	fprintf(fp, "%s: %s", buf1, "hello");
	rewind(fp);
	fgets(buf1, 100, fp);
	printf("%s\n", buf1);
	fclose(fp);
	
	char buf2[] = "/tmp/testXXXXXX";
	int fd = mkstemp(buf2);
	fp = fdopen(fd, "r+");
	fprintf(fp, "%s: %s", buf2, "hello");
	rewind(fp);
	fgets(buf1, 100, fp);
	printf("%s\n", buf1);
	unlink(buf2);
	fclose(fp);	
	return 0;
}

執行結果:
在这里插入图片描述