本文是筆者拜讀《UNIX環境高階程式設計》第5章(標準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
只能用於讀寫在同一系統上的數據。而在如今的異構系統中,常常在一個系統上寫數據,在另一個系統上進行處理。這種情況下,fread
和fwrite
可能就不能正常工作,因爲:
(1)
在一個結構中,同一成員的偏移量可能隨編譯程式和系統的不同而不同。
(2)
多位元組整數和浮點值的二進制格式在不同的系統結構間也可能不同。
在不同系統之間交換二進制數據的實際解決方法是使用互認的規範格式。
有3
種方法定位標準I/O
流。
(1)
ftell
和fseek
函數。這些函數假定檔案的位置可以存放在一個長整型中。
(2)
ftello
和fseeko
函數。使用off_t
數據型別表示檔案偏移量。
(3)
fgetpos
和fsetpos
函數。這些函數使用一個抽象數據型別fpos_t
記錄檔案的位置,這種數據型別可以根據需要定義爲一個足夠大的數。
ftell
返回當前檔案位置指示;fseek
設定檔案位置。檔案位置相當於檔案偏移量,fseek
相當於lseek
。使用rewind
可將一個流設定到檔案的起始位置。
除了偏移量型別以外,ftello
和ftell
相同,fseeko
和fseek
相同。
fgetpos
將檔案位置指示器的當前值存入由pos
指向的物件中(功能類似於ftell
),呼叫fsetpos
時,可以使用此值將流重新定位到該位置。
printf
將格式化數據寫到標準輸出,fprintf
寫至指定的流,dprintf
寫至指定的檔案描述符,sprintf
寫至指定的陣列(在陣列尾端自動加一個'\0'
)。
爲了解決緩衝區溢位問題,引入了指定緩衝區長度的snprintf
函數,超過緩衝區尾端的字元都會被丟棄。
格式說明控制其餘參數如何編寫,如何顯示。每個參數按照轉換說明編寫,轉換說明以%
開始。除轉換說明外,格式字串中的其它字元將按原樣輸出。一個轉換說明有4
個可選擇部分:
%[flags][fldwidth][precision][lenmodifier]convtype
flags | 說明 |
---|---|
' |
將整數按千位分組 |
- |
左對齊 |
+ |
帶正負號 |
(空格) |
如果第一個字元不是正負號,則在前面加上一個空格 |
# |
指定另一種轉換格式 |
0 |
使用0 進行填充 |
fldwidth
指定了最小欄位寬度。轉換後參數字元數若小於寬度,則多餘字元用空格填充。字元寬度是一個非負的十進制數,或者*
。
precision
指定了精度。例如整型的數位位數、浮點數的小數位數、字串的長度。精度表示爲.
後面跟個可選的非負十進制數或*
。
寬度和精度欄位皆可爲*
。此時,一個整型參數指定寬度或精度的值,該整型參數正好位於被轉換的參數之前。
lenmodifier
說明參數長度。
長度修飾符 | 說明 |
---|---|
hh |
將相應的參數按signed 或unsigned char 型別輸出 |
h |
將相應的參數按signed 或unsigned short 型別輸出 |
l |
將相應的參數按signed 或unsigned long 型別輸出 |
ll |
將相應的參數按signed 或unsigned long long 型別輸出 |
j |
intmax_t 或uintmax_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 ,e 或E |
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
函數產生一個與現有檔名不同的一個有效路徑名字串。
若s
是NULL
,則產生的路徑名存放在靜態區中,指向該靜態區的指針作爲函數的返回值。後續呼叫tmpnam
時,會重寫該靜態區。
若s
不是NULL
,則認爲它指向一個字元陣列,所產生的路徑名存放在該陣列中,返回s
。
tmpfile
使用tmpnam
生成的路徑名(通常在/tmp/
目錄下)建立一個臨時二進制檔案(型別wb+
),在關閉該檔案或程式結束時會自動刪除該檔案。
mkdtemp
和mkstemp
也能建立臨時檔案。
mkdtemp
函數建立了一個目錄,該目錄有一個唯一的名字;mkstemp
函數建立了一個檔案,該檔案有一個唯一的名字。名字是通過template
字串進行選擇的。這個字串是後6
位設定爲XXXXXX
的路徑名,函數將這些佔位符替換成不同的字元來構建一個唯一的路徑名。
由mkdtemp
函數建立的目錄使用下列存取許可權位集:S_IRUSR | S_IWUSR | S_IXUSR
,可以呼叫umask
進一步限制這些許可權。mkdtemp
返回新目錄的名字。
由mkstemp
建立的臨時檔案使用存取許可權位S_IRUSR | S_IWUSR
,mkstemp
建立的臨時檔案不會被自動刪除,必須對它解除鏈接。
例:
臨時檔案的建立和關閉
// 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;
}
執行結果: