linux下c标准库位置,C 标准库 IO 使用详解
其實(shí)輸入與輸出對(duì)于不管什么系統(tǒng)的設(shè)計(jì)都是異常重要的,比如設(shè)計(jì) C 接口函數(shù),首先要設(shè)計(jì)好輸入?yún)?shù)、輸出參數(shù)和返回值,接下來才能開始設(shè)計(jì)具體的實(shí)現(xiàn)過程。C 語言標(biāo)準(zhǔn)庫提供的接口功能很有限,不像 Python 庫。不過想把它用好也不容易,本文總結(jié) C 標(biāo)準(zhǔn)庫基礎(chǔ) IO 的常見操作和一些特別需要注意的問題,如果你覺著自己還不是大神,那么請(qǐng)相信我,讀完全文后你肯定會(huì)有不少收獲。
一、操作句柄
打開文件其實(shí)就是在操作系統(tǒng)中分配一些資源用于保存該文件的狀態(tài)信息及文件的標(biāo)識(shí),以后用戶程序可以用這個(gè)標(biāo)識(shí)做各種讀寫操作,關(guān)閉文件則釋放占用的資源。
打開文件的函數(shù):
#include
FILE *fopen(const char *path, const char *mode);
FILE 是 C 標(biāo)準(zhǔn)庫定義的結(jié)構(gòu)體類型,其包含文件在內(nèi)核中的標(biāo)識(shí)(文件描述符)、I/O 緩沖區(qū)和當(dāng)前讀寫位置信息,調(diào)用者不需知道 FILE 的具體成員,由庫函數(shù)內(nèi)部維護(hù),調(diào)用者不應(yīng)該直接訪問這些成員。像 FILE* 這樣的文件指針稱為句柄(Handle)。
打開文件操作是對(duì)文件資源進(jìn)行操作的,所以有可能打開文件失敗,所以在打開函數(shù)時(shí)一定要判斷返回值,如果失敗則返回錯(cuò)誤信息,以方便快速定位錯(cuò)誤。
打開文件應(yīng)該與關(guān)閉文件成對(duì)存在,雖然程序在退出時(shí)會(huì)釋放相應(yīng)的資源,但是對(duì)于一個(gè)長時(shí)間運(yùn)行服務(wù)程序來說,經(jīng)常打開而不關(guān)閉文件是會(huì)造成進(jìn)程資源耗盡的,因?yàn)檫M(jìn)程的文件描述符個(gè)數(shù)是有限的,及時(shí)關(guān)閉文件是個(gè)好習(xí)慣。
關(guān)閉文件的函數(shù):
#include
int fclose(FILE *fp);
fopen 函數(shù)參數(shù) mode 總結(jié):
"r":只讀,文件必須存在。
"w":只寫,如果不存在則創(chuàng)建,存在則覆蓋。
"a":追加,如果不存在則創(chuàng)建。
"r+":允許讀和寫,文件必須存在。
"w+":允許讀和寫,文件不存在則創(chuàng)建,存在則覆蓋。
"a+":允許讀和追加,文件不存在則創(chuàng)建。
二、關(guān)于stdin/stdout/stderr
在用戶程序啟動(dòng)時(shí),main 函數(shù)還沒開始執(zhí)行之前,會(huì)自動(dòng)打開三個(gè) FILE* 指針分別是:stdin、stdout、stderr,這三個(gè)文件指針是 libc 中定義的全局變量,在 stdio.h 中聲明,printf 向 stdout 寫,而 scanf 從 stdin 讀,用戶程序也可以直接使用這三個(gè)文件指針。
stdin 只用于讀操作,稱為標(biāo)準(zhǔn)輸入
stdout 只用于寫操作,稱為標(biāo)準(zhǔn)輸出
stderr 也用于寫操作,稱為標(biāo)準(zhǔn)錯(cuò)誤輸出
通常程序的運(yùn)行結(jié)果打印到標(biāo)準(zhǔn)輸出,而錯(cuò)誤提示打印到標(biāo)準(zhǔn)錯(cuò)誤輸出,一般標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤都是屏幕。通常可以標(biāo)準(zhǔn)輸出重定向到一個(gè)常規(guī)文件,而標(biāo)準(zhǔn)錯(cuò)誤輸出仍然對(duì)應(yīng)終端設(shè)備,這樣就可以將運(yùn)行結(jié)果與錯(cuò)誤信息分開。
三、以字節(jié)為單位的IO函數(shù)
fgetc 函數(shù)從指定的文件中讀一個(gè)字節(jié),getchar從標(biāo)準(zhǔn)輸入讀一個(gè)字節(jié),調(diào)用 getchar() 相當(dāng)于 fgetc(stdin)
#include
int fgetc(FILE *stream);
int getchar(void);
fputc 函數(shù)向指定的文件寫入一個(gè)字節(jié),putchar 向標(biāo)準(zhǔn)輸出寫一個(gè)字節(jié),調(diào)用 putchar() 相當(dāng)于調(diào)用 fputc(c, stdout)。
#include
int fputc(int c, FILE *stream);
int putchar(int c);
參數(shù)和返回值類型為什么使用 int 類型?可以看到這幾個(gè)函數(shù)的參數(shù)和返回值類型都是 int,而非 unsigned char 型。因?yàn)殄e(cuò)誤或讀到文件末尾時(shí)將返回 EOF,即 -1,如果返回值是 unsigned char(0xff),與實(shí)際讀到字節(jié) 0xff 無法區(qū)分,如果使用 int 就可以避免這個(gè)問題。
四、操作讀寫位置函數(shù)
當(dāng)我們?cè)诓僮魑募r(shí),有一個(gè)叫「文件指針」的家伙來記錄當(dāng)前操作的文件位置,比如剛打開文件,調(diào)用了 1 次 fgetc 后,此時(shí)文件指針指向了第 1 個(gè)字節(jié)后邊,注意是以字節(jié)為單位記錄的。
改變文件指針位置的函數(shù):
#include
int fseek(FILE *stream, long offset, int whence);
whence:從何處開始移動(dòng),取值:SEEK_SET | SEEK_CUR | SEEK_END
offset:移動(dòng)偏移量,取值:可取正 | 負(fù)
void rewind(FILE *stream);
舉幾個(gè)簡單例子:
fseek(fp, 5, SEEK_SET); // 從文件頭向后移動(dòng)5個(gè)字節(jié)
fseek(fp, 6, SEEK_CUR); // 從當(dāng)前位置向后移動(dòng)6個(gè)字節(jié)
fseek(fp, -3, SEEK_END); // 從文件尾向前移動(dòng)3個(gè)字節(jié)
offset 可正可負(fù),負(fù)值表示向文件開頭的方向移動(dòng),正值表示向文件尾方向移動(dòng),如果向前移動(dòng)的字節(jié)數(shù)超過文件開頭則出錯(cuò)返回,如果向后移動(dòng)的字節(jié)數(shù)超過了文件末尾,再次寫入會(huì)增加文件尺寸,文件空洞字節(jié)都是 0
$ echo "5678" > file.txt
fp = fopen("file.txt", "r+");
fseek(fp, 10, SEEK_SET);
fputc('K', fp)
fclose(fp)
// 通過結(jié)果可以看出字母K是從第10個(gè)位置開始寫的
liwei:/tmp$ od -tx1 -tc -Ax file.txt
0000000 35 36 37 38 0a 00 00 00 00 00 4b
5 6 7 8 \n \0 \0 \0 \0 \0 K
rewind(fp) 等價(jià)于 fseek(fp, 0, SEEK_SET)
ftell(fp) 函數(shù)比較簡單,直接返回當(dāng)前文件指針在文件中的位置
// 實(shí)現(xiàn)計(jì)算文件字節(jié)數(shù)的功能
fseek(fp, 0, SEEK_END);
ftell(fp);
五、以字符串為單位的IO函數(shù)
fgets 從指定的文件中讀一行字符到調(diào)用者提供的緩沖區(qū),讀入內(nèi)容不超過 size 。
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
首先要說明 gets() 函數(shù)強(qiáng)烈不推薦使用,類似 strcpy 函數(shù),用戶不可以指定緩沖區(qū)大小,很容易造成緩沖區(qū)溢出錯(cuò)誤。不過 strcpy 程序員還是可以避免,而 gets 的輸入用戶可以提供任意長的字符串,唯一避免方法就是不使用 gets,而使用 fgets(buf, size, stdin)
fgets 函數(shù)從 stream 所指文件讀取以 '\n' 結(jié)尾的一行,包括 '\n' 在內(nèi),存到緩沖區(qū)中,并在該行結(jié)尾添加一個(gè) '\0' 組成完整的字符串。如果文件一行太長,fgets 從文件中讀了 size-1 個(gè)字符還沒有讀到 '\n',就把已經(jīng)讀到的 size-1 個(gè)字符和一個(gè) '\0' 字符存入緩沖區(qū),文件行剩余的內(nèi)容可以在下次調(diào)用 fgets 時(shí)繼續(xù)讀。
若一次 fgets 調(diào)用在讀入若干字符后到達(dá)文件末尾,則將已讀到的字符加上 '\0' 存入緩沖區(qū)并返回,如果再次調(diào)用則返回 NULL,可以據(jù)此判斷是否讀到文件末尾。
fputs 向指定文件寫入一個(gè)字符串,緩沖區(qū)保存的是以 '\0' 結(jié)尾的字符串,與 fgets 不同的是,fputs 不關(guān)心字符串中的 '\n' 字符。
int fputs(const char *s, FILE *stream);
int puts(const char *s);
六、以記錄為單位的IO函數(shù)
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
fread 和 fwrite 用于讀寫記錄,這里的記錄是指一串固定長度的字節(jié),比如一個(gè) int、一個(gè)結(jié)構(gòu)體貨或一個(gè)定長數(shù)組。
參數(shù) size 指出一條記錄的長度,nmemb 指出要讀或?qū)懚嗌贄l記錄,這些記錄在 ptr 所指內(nèi)存空間連續(xù)存放,共占 size * nmemb 個(gè)字節(jié)。
fread 和 fwrite 返回的記錄數(shù)有可能小于 nmemb 指定的記錄數(shù)。例如當(dāng)讀寫位置距文件末尾只有一條記錄長度,調(diào)用 fread 指定 nmemb 為 2,則返回值為 1。如果寫文件時(shí)出錯(cuò),則 fwrite 的返回值小于 nmemb 指定的值。
struct t{
int a;
short b;
};
struct t val = {1, 2};
FILE *fp = fopen("file.txt", "w");
fwrite(&val, sizeof(val), 1, fp);
fclose(fp);
liwei:/tmp$ od -tx1 -tc -Ax file.txt
0000000 01 00 00 00 02 00 00 00
001 \0 \0 \0 002 \0 \0 \0
從結(jié)果可以看出,寫入的是 8 個(gè)字節(jié),有興趣的同學(xué)可以就此分析下系統(tǒng)的「大小端」和結(jié)構(gòu)體的「對(duì)齊補(bǔ)齊」問題。
七、格式化IO函數(shù)
(1). printf / scanf
int printf(const char *format, ...);
int scanf(const char *format, ...);
這兩個(gè)函數(shù)是我們學(xué)習(xí) C 語言最早接觸,可能也是接觸比較多的了,沒什么特別要說的。printf 就是格式化打印到標(biāo)準(zhǔn)輸出。下面總結(jié)下 printf 常用的方式。
printf("%d\n", 5); // 打印整數(shù) 5
printf("-%10s-\n", "hello") // 設(shè)置顯示寬度并左對(duì)齊:- hello-
printf("-%-10s-\n", "hello") // 設(shè)置顯示寬度并右對(duì)齊:- hello-
printf("%#x\n", 0xff); // 0xff 不加#則顯示ff
printf("%p\n", main); // 打印 main 函數(shù)首地址
printf("%%\n"); // 打印一個(gè) %
scanf 就是從標(biāo)準(zhǔn)輸入中讀取格式化數(shù)據(jù),簡單舉個(gè)例子:
int year, month, day;
scanf("%d/%d/%d", &year, &month, &day);
printf("year = %d, month = %d, day = %d\n", year, month, day);
(2). sprintf / sscanf / snprintf
sprintf 并不打印到文件,而是打印到用戶提供的緩沖區(qū)中并在末尾加 '\0',由于格式化后的字符串長度很難預(yù)計(jì),所以很可能造成緩沖區(qū)溢出,強(qiáng)烈推薦 snprintf 更好一些,參數(shù) size 指定了緩沖區(qū)長度,如果格式化后的字符串超過緩沖區(qū)長度,snprintf 就把字符串截?cái)嗟?size - 1 字節(jié),再加上一個(gè) '\0',保證字符串以 '\0' 結(jié)尾。如果發(fā)生截?cái)?#xff0c;返回值是截?cái)嘀暗拈L度,通過對(duì)比返回值與緩沖區(qū)實(shí)際長度對(duì)比就知道是否發(fā)生截?cái)唷?/p>
int sscanf(const char *str, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
sscanf 是從輸入字符串中按照指定的格式去讀取相應(yīng)的數(shù)據(jù),函數(shù)功能非常的強(qiáng)大,支持類似正則表達(dá)式匹配的功能。具體的使用格式請(qǐng)自行查詢官方手冊(cè),這里總結(jié)出最常用、最重要的幾種使用場(chǎng)景和方式。
最基本的用法
char buf[1024] = 0;
sscanf("123456", "%s", buf);
printf("%s\n", buf);
// 結(jié)果為:123456
取指定長度的字符串
sscanf("123456", "%4s", buf);
printf("%s\n", buf);
// 結(jié)果為:1234
取第1個(gè)字符串
sscanf("hello world", "%s", buf);
printf("%s\n", buf);
// 結(jié)果為:hello 因?yàn)槟J(rèn)是以空格來分割字符串的,%s讀取第一個(gè)字符串hello
讀取到指定字符為止的字符串
sscanf("123456#abcdef", "%[^#]", buf);
// 結(jié)果為:123456
// %[^#]表示讀取到#符號(hào)停止,不包括#
讀取僅包含指定字符集的字符串
sscanf("123456abcdefBCDEF", "%[1-9a-z]", buf);
// 結(jié)果為:123456abcdef
// 表達(dá)式是要匹配數(shù)字和小寫字母,匹配到大寫字母就停止匹配了。
讀取指定字符集為止的字符串
sscanf("123456abcdefBCDEF", "%[^A-Z]", buf);
// 結(jié)果為:123456abcdef
讀取兩個(gè)符號(hào)之間的內(nèi)容(@和.之間的內(nèi)容)
sscanf("liwei0526vip@linuxblogs.cn", "%*[^@]@%[^.]", buf);
// 結(jié)果為:linuxblogs
// 先讀取@符號(hào)前邊內(nèi)容并丟棄,然后讀@,接著讀取.符號(hào)之前的內(nèi)容linuxblogs,不包含字符.
給一個(gè)字符串
sscanf("hello, world", "%*s%s", buf);
// 結(jié)果為:world
// 先忽略一個(gè)字符串"hello,",遇到空格直接跳過,匹配%s,保存 world 到 buf
// %*s 表示第 1 個(gè)匹配到的被過濾掉,即跳過"hello,",如果沒有空格,則結(jié)果為 NULL
稍微復(fù)雜點(diǎn)的
sscanf("ABCabcAB=", "%*[A-Z]%*[a-z]%[^a-z=]", buf);
// 結(jié)果為:AB 自己嘗試分析哈
包含特殊字符處理
sscanf("201*1b_-cdZA&", "%[0-9|_|--|a-z|A-Z|&|*]", buf);
// 結(jié)果為:201*1b_-cdZA&
如果能將上述幾個(gè)例子搞明白,相信基本上已經(jīng)掌握了 sscanf 的用法,實(shí)踐才是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),只有多使用,多思考才能真正理解它的用法。
(3). fprintf / fscanf
fprintf 打印到指定的文件 stream 中,fscanf 從文件中格式化讀取數(shù)據(jù),類似 scanf 函數(shù)。相關(guān)函數(shù)的聲明如下:
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
還是通過簡單實(shí)例來說明基本用法。
FILE *fp = fopen("file.txt", "w");
fprintf(fp, "%d-%s-%f\n", 32, "hello", 0.12);
fclose(fp);
liwei:/tmp$ cat file.txt
32-hello-0.120000
而 fscanf 函數(shù)的使用基本上與 sscanf 函數(shù)使用方式相同。
八、IO緩沖區(qū)
還有個(gè)關(guān)于 IO 非常重要的概念,就是 IO 緩沖區(qū)。
C 標(biāo)準(zhǔn)庫為每個(gè)打開的文件分配一個(gè) I/O 緩沖區(qū),用戶調(diào)用讀寫函數(shù)大多數(shù)都在 I/O 緩沖區(qū)中讀寫,只有少數(shù)請(qǐng)求傳遞給內(nèi)核。
以 fgetc/fputc 為例,當(dāng)?shù)谝淮握{(diào)用 fgetc 讀一個(gè)字節(jié)時(shí),fgetc 函數(shù)可能通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核讀 1k 字節(jié)到緩沖區(qū),然后返回緩沖區(qū)中第一個(gè)字節(jié)給用戶,以后用戶再調(diào)用 fgetc,就直接從緩沖區(qū)讀取。
另一方面,fputc 通常只是寫到緩沖區(qū)中,如果緩沖區(qū)滿了,fputc 就通過系統(tǒng)調(diào)用把緩沖區(qū)數(shù)據(jù)傳遞給內(nèi)核,內(nèi)核將數(shù)據(jù)寫回磁盤。如果希望把緩沖區(qū)數(shù)據(jù)立即寫入磁盤,可以調(diào)用 fflush 函數(shù)。
C 標(biāo)準(zhǔn)庫 IO 緩沖區(qū)有三種類型:全緩沖、行緩沖和無緩沖區(qū),不同類型的緩沖區(qū)具有不同的特性。
全緩沖:如果緩沖區(qū)寫滿了就寫回內(nèi)核。常規(guī)文件通常是全緩沖的。
行緩沖:如果程序?qū)懙臄?shù)據(jù)中有換行符就把這一行寫回內(nèi)核,或者緩沖區(qū)滿就寫回內(nèi)核。標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出對(duì)應(yīng)終端設(shè)備時(shí)通常是行緩沖的。
無緩沖:用戶程序每次調(diào)用庫函數(shù)做寫操作都要通過系統(tǒng)調(diào)用寫回內(nèi)核。標(biāo)準(zhǔn)錯(cuò)誤輸出通常是無緩沖的,用戶程序的錯(cuò)誤信息可以盡快輸出到設(shè)備。
printf("hello world");
while(1);
// 運(yùn)行程序會(huì)發(fā)現(xiàn)屏幕并沒有打印hello world
// 因?yàn)榫彌_區(qū)沒滿,且沒有\(zhòng)n符號(hào)
除了寫滿緩沖區(qū)、寫入換行符之外,行緩沖還有一種情況會(huì)自動(dòng)做 flush 操作,如果:
用戶程序調(diào)用庫函數(shù)從無緩沖的文件中讀取
或從行緩沖的文件中讀取,且這次讀操作會(huì)引發(fā)系統(tǒng)調(diào)用從內(nèi)核讀取數(shù)據(jù),那么會(huì)讀之前自動(dòng) flush 所有行緩沖
程序退出時(shí)通常也會(huì)自動(dòng) flush 緩沖區(qū)
如果不想完全依賴自動(dòng)的 flush 操作,可以調(diào)用 fflush 函數(shù)手動(dòng)操作。若調(diào)用 fflush(NULL) 可以對(duì)所有打開文件的 IO 緩沖區(qū)做 flush 操作。緩沖區(qū)大小也可以自定義設(shè)置,一般情況無需設(shè)置,默認(rèn)即可。
總結(jié)
以上是生活随笔為你收集整理的linux下c标准库位置,C 标准库 IO 使用详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: wind 下装mysql,windows
- 下一篇: 笔刷怎么做_原来是这样:用PS笔刷做出颜