欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux系統(tǒng)下C語言中的標(biāo)準(zhǔn)IO總結(jié)

 更新時(shí)間:2024年01月05日 10:35:34   作者:coolhuhu~  
最近用到了C語言的標(biāo)準(zhǔn)IO庫,由于對(duì)其中的一些細(xì)節(jié)不是非常清楚,導(dǎo)致了許多Bug,花了好長時(shí)間來調(diào)試,所以在此做個(gè)筆記,這篇文章主要給大家介紹了關(guān)于Linux系統(tǒng)下C語言中標(biāo)準(zhǔn)IO的相關(guān)資料,需要的朋友可以參考下

本文對(duì) Linux 下C語言的標(biāo)準(zhǔn)IO進(jìn)行總結(jié),所有代碼示例均在 Ubuntu-20.04、GCC 11.3.0 環(huán)境下運(yùn)行通過。

標(biāo)準(zhǔn)IO中的一些概念

流和FILE對(duì)象

在 Linux 操作系統(tǒng)中,提供給用戶操作文件的接口是“文件描述符”以及對(duì)應(yīng)的函數(shù),例如 read,write等。而在C語言中,提供給用戶的文件操作的接口是“流(stream)”,當(dāng)使用C語言中的標(biāo)準(zhǔn)I/O庫打開或創(chuàng)建一個(gè)文件時(shí),就使得一個(gè)流與一個(gè)文件關(guān)聯(lián)起來。而這個(gè) “流” 的概念,在程序上,使用 FILE 對(duì)象來表示,例如:

#include <stdio.h>

int main()
{
	// 打開或者創(chuàng)建一個(gè)文件,使用 FILE 對(duì)象與該文件進(jìn)行綁定。
	// 也常把 fp 叫為 文件流
	FILE* fp = fopen("example.txt", "a");
	// ...
}

注意:一個(gè)進(jìn)程預(yù)定了三個(gè)流,標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤。而本文的重點(diǎn)討論對(duì)象是 文件流,也即 FILE 對(duì)象以及標(biāo)準(zhǔn)I/O中提供的操作 FILE 對(duì)象的一系列函數(shù)。

流的定向(stream’s orientation)

對(duì)于ASCII字符集,一個(gè)字符用一個(gè)字節(jié)表示。對(duì)于國際字符集,一個(gè)字符可用多個(gè)字節(jié)表示。標(biāo)準(zhǔn)I/O文件流可用于單字節(jié)或多字節(jié)(也稱“寬”字符)字符集。流的定向(stream’s orientation)決定所讀、寫的字符是單字節(jié)還是多字節(jié)的。

下面給出一個(gè)寬字符集輸出到標(biāo)準(zhǔn)輸出的示例:

#include <iostream>
#include <cstring>

int main() {

    const char* charString = "你好,世界!";
    std::cout << "Length of charString: " << std::strlen(charString) << std::endl;

    const char* multibyteString = u8"你好,世界!";  // UTF-8編碼的多字節(jié)字符串
    std::cout << "Length of multibyteString: " << std::strlen(multibyteString) << std::endl;

    const wchar_t* wideString = L"你好,世界!";  // UTF-16編碼的寬字符字符串
    std::wcout << L"Length of wideString: " << std::wcslen(wideString) << std::endl;
    return 0;
}

/*
運(yùn)行結(jié)果為:
Length of charString: 18
Length of multibyteString: 18
Length of wideString: 6
*/

注意:下文討論的都是單字節(jié)的流向。

緩沖(buffer)

在 Linux 中,使用 read、write 函數(shù)對(duì)文件描述符進(jìn)行讀寫操作屬于系統(tǒng)調(diào)用,用于直接讀寫磁盤文件。(其實(shí)也不是直接讀寫讀寫磁盤文件,Linux內(nèi)核中會(huì)維護(hù)高速緩沖區(qū)用于提高磁盤文件的讀寫效率,這部分內(nèi)容超出的本文的討論范疇,略過。)而C語言I/O標(biāo)準(zhǔn)庫提供的I/O操作在用戶態(tài),通常帶有緩沖(當(dāng)然,也可以沒有緩沖),使用標(biāo)準(zhǔn)I/O庫提供的I/O操作先將數(shù)據(jù)寫入緩沖中,然后等待某個(gè)條件達(dá)成,在將緩沖中的數(shù)據(jù)寫入磁盤文件(調(diào)用 write 函數(shù))。標(biāo)準(zhǔn)I/O庫提供緩沖的目的是減少 read 和 write 調(diào)用的次數(shù),提高 I/O 效率。(而在實(shí)際的開發(fā)中,I/O緩沖的利用需要根據(jù)實(shí)際的場景來使用,并不是說有了緩沖,I/O效率就提高了。)

標(biāo)準(zhǔn) I/O 提供了三種緩沖類型:

  • 全緩沖。對(duì)于寫操作,當(dāng)緩沖區(qū)寫滿后,才將緩沖中的數(shù)據(jù)寫入文件;讀操作同理。對(duì)于寫操作,可以調(diào)用 flush 函數(shù)主動(dòng)將緩沖中的數(shù)據(jù)寫入文件而不論緩沖是否被寫滿。下文將調(diào)用 flush 函數(shù)的操作稱為 ”刷新緩沖“。
  • 行緩沖。當(dāng)讀或?qū)憯?shù)據(jù)遇到換行符時(shí),將緩沖中的數(shù)據(jù)進(jìn)行輸入或輸出。行緩沖的一個(gè)限制是:當(dāng)緩沖已滿,即使未遇到換行符,也將其進(jìn)行輸入輸出。
  • 不帶緩沖。即I/O操作直接寫入文件。

I/O標(biāo)準(zhǔn)庫中常用的函數(shù)

文件流的打開和關(guān)閉

下面三個(gè)函數(shù)可用于文件流的打開,其中 fopen 最為常用,先重點(diǎn)介紹該函數(shù),剩余兩個(gè)當(dāng)遇到具體的使用場景時(shí)再來補(bǔ)充。在 Linux 中是可以使用 man 命令查看詳情。

#include <stdio.h>

/*
pathname參數(shù)表示打開的文件路徑名;
type參數(shù)指定對(duì)文件流的讀、寫方式。
若打開出錯(cuò),返回 NULL。
*/
FILE* fopen(const char* pathname, const char* type);

FILE* freopen(const char* pathname, const char* type, FILE* fp);

FILE* fdopen(int fd, const char* type);

對(duì)于 fopen 函數(shù),type 參數(shù)的值及表示的讀、寫方式如下所示:

  • r 或 rb,以讀的方式打開。
  • w 或 wb,以寫的方式打開;若指定文件名存在,則將該文件內(nèi)容清空;不存在,創(chuàng)建新文件。
  • a 或 ab,以追加寫的方式打開文件。若指定文件名不存在,創(chuàng)建新文件。
  • r 或 r+b 或 rb+,以讀寫的方式打開文件;指定文件名不存在,則出錯(cuò)。
  • w+ 或 w+b 或 wb+,以讀寫的方式打開文件;若指定文件名存在,則將該文件內(nèi)容清空;不存在,創(chuàng)建新文件。
  • a+ 或 a+b 或 ab+,以讀寫的方式打開文件,讀寫操作在文件尾開始進(jìn)行,若指定文件名不存在,創(chuàng)建新文件。

使用字符b作為type的一部分,使得標(biāo)準(zhǔn)I/O系統(tǒng)可以區(qū)分為文本文件和二進(jìn)制文件。UNIX不對(duì)這兩種文件進(jìn)行區(qū)分。
– 《UNIX 高級(jí)環(huán)境編程》

使用 fopen 函數(shù)開打的文件流默認(rèn)是自帶緩沖的,緩沖模式為全緩沖。

fclose 函數(shù)用于關(guān)閉一個(gè)打開的文件流。注意,對(duì)于一個(gè)已經(jīng)關(guān)閉了的文件流調(diào)用 fclose 函數(shù),行為是未定義的。

#include <stdio.h>

/*
若成功,返回0;若出錯(cuò),返回 EOF
*/
int fclose(FILE* fp);

當(dāng)調(diào)用 fclose 函數(shù)或者當(dāng)一個(gè)進(jìn)程正常終止(調(diào)用 exit 函數(shù)或從 main 函數(shù)返回),會(huì)先刷新緩沖。若是使用標(biāo)準(zhǔn)IO默認(rèn)的緩沖,則會(huì)釋放緩沖。

給文件流設(shè)置自定義的緩沖

若希望自己掌控文件流的緩沖,可以自定義一個(gè)緩沖,將其于打開的文件流的進(jìn)行綁定。主要有如下三個(gè)函數(shù)可以綁定自定義的緩沖:

#include <stdio.h>

void setbuf(FILE* fp, char* buf);

/*
	buf參數(shù)為指定緩沖區(qū),mode表示緩沖類型,size指定了緩沖的大小。
	成功返回0;出錯(cuò)返回非0。
*/
void setvbuf(FILE* fp, char* buf, int mode, size_t size);

void setbuffer(FILE* fp, char* buf, size_t size);

mode 參數(shù)的可選值如下:

  • _IOFBF,全緩沖。
  • _IOLBF,行緩沖。
  • _IONBF,無緩沖。

對(duì)上面三個(gè)函數(shù)進(jìn)行如下補(bǔ)充說明:

  • setbuf 等價(jià)于 setvbuf(fp, buf, buf ? _IOFBF : _IONBF, BUFFSIX) ;其中, BUFZSIZE 在標(biāo)準(zhǔn)庫的默認(rèn)值,我的環(huán)境下為 8096。
  • setbuffer 等價(jià)于 setvbuf(fp, buf, buf ? _IOFBF : _IONBF, size)
  • 若 buf 參數(shù)為空,則文件流為無緩沖。

在使用文件流的進(jìn)行文件操作時(shí),全緩沖的緩沖模式用得最多,因此推薦使用 setbuffer ,不用費(fèi)力去記緩沖模式的參數(shù)值。

文件流的讀寫

打開文件流后,有三種類型的非格式化I/O操作:

  • 每次讀寫一個(gè)字符。一次讀寫一個(gè)字符。
  • 每次讀寫一行。一次讀寫一行,每一行以換行符終止。
  • 直接讀寫,即指定讀寫的字節(jié)數(shù)。每次IO操作讀寫某種類型的對(duì)象,每個(gè)對(duì)象具有指定的長度。

讀寫一個(gè)字符

對(duì)于讀一個(gè)字符,有如下三個(gè)函數(shù)可供選擇:

#include <stdio.h>
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar(void);
/*
以上三個(gè)函數(shù),成功返回讀取的字符,返回前將 unsigned char 類型轉(zhuǎn)換為 int 類型;若已到達(dá)文件尾端或出錯(cuò),返回 EOF。
*/

對(duì)上面三個(gè)函數(shù)進(jìn)行補(bǔ)充說明:

  • getchar(void) 等價(jià)于 getc(stdin)。
  • 在《UNIX 環(huán)境高級(jí)編程》中,” getc 函數(shù)可能被實(shí)現(xiàn)為宏,而 fgetc 一定為函數(shù)“。因此推薦使用 fget 函數(shù),因?yàn)楹甓x的參數(shù)存在副作用。

對(duì)于上述三個(gè)函數(shù)的出錯(cuò),在文件流 FILE 對(duì)象中,每個(gè) FILE 對(duì)象維護(hù)了兩個(gè)標(biāo)志:

  • 出錯(cuò)標(biāo)志;
  • 文件結(jié)束標(biāo)志;

可以使用 ferror 和 feof 函數(shù)進(jìn)行檢查:

#include <stdio.h>
/* 檢查 fp 指定的流是否發(fā)生了錯(cuò)誤。若為真,則返回非0;否則,返回0 */
int ferror(FILE* fp);
/* 檢查 fp 指定的流是否到達(dá)文件尾。若為真,則返回非0;否則,返回0 */
int feof(FILE* fp);
/* 清楚上述兩個(gè)標(biāo)志 */
void clearerr(FILE* fp);

在使用文件流進(jìn)行文件操作時(shí),一個(gè)好的編碼習(xí)慣是,使用 ferror 函數(shù)檢測讀寫后的文件流狀態(tài)。

對(duì)于寫一個(gè)字符,有如下三個(gè)函數(shù)可供選擇:

#include <stdio.h>
int putc(int c, FILE* fp);
int fputc(int c,  FILE* fp);
// putchar(c) 等價(jià)于 putc(c, stdout);
int putchar(int c);
/*
以上三個(gè)函數(shù),成功,返回c;若出錯(cuò),返回EOF。
*/

和 getc、fgetc 類似,putc 可能實(shí)現(xiàn)為宏,fputc 被定義為一個(gè)函數(shù),因此推薦使用 fputc。

下面給出幾個(gè)編碼示例:

假設(shè)文件中的內(nèi)容為 python,一個(gè)字符一個(gè)字符的把文件中的內(nèi)容輸出到標(biāo)準(zhǔn)輸出。

FILE* fp = fopen("example.txt", "a+");
int c;
while ((c = fgetc(fp)) != EOF) {
	printf("%c", (unsigned char)c);
}
fclose(fp); 
/*
輸出為:python
*/

假設(shè)文件中的內(nèi)容為 python,一個(gè)字符一個(gè)字符寫入文件。

FILE* fp = fopen("example.txt", "a+");
char buf[7] = "golang";
for (int i = 0; i < 6; ++i) {
	fputc(buf[i], fp);
}
fclose(fp);  
/*
文件中的內(nèi)容為:pythongolang
*/

(以上只是簡單的編碼示例,更復(fù)雜的操作可以結(jié)合文件流的定位來進(jìn)行,碰到實(shí)際的場景在來補(bǔ)充。)

讀寫一行

對(duì)于讀一行,有以下兩個(gè)函數(shù)可供選擇:

#include <stdio.h>
// n 為指定的緩沖區(qū)大小
// 將 fp 文件中的內(nèi)容寫入 buf 中,直至遇到換行符或者buf寫滿。
char* fgets(char* buf, int n, FILE* fp);
// gets 從標(biāo)準(zhǔn)輸入進(jìn)行讀
char* gets(char* buf);
/*
以上兩個(gè)函數(shù),若成功,返回buf;若已達(dá)到文件末尾或出錯(cuò),返回NULL。
*/

gets 函數(shù)不能指定緩沖區(qū)大小,建議只使用 fgets 函數(shù)。

對(duì)于寫一行,有以下兩個(gè)函數(shù)可供選擇:

#include <stdio.h>
// fputs 不會(huì)將換行符寫入到文件流中
int fputs(const char* str, FILE* fp);
// puts 將字符串輸出到標(biāo)準(zhǔn)輸出,會(huì)將換行符作為輸出。
int puts(const char* str);
/*
以上兩個(gè)函數(shù),若成功,返回非負(fù)責(zé);若出錯(cuò),返回 EOF。
*/

建議只是用 fputs 函數(shù)。

直接讀寫

在《UNIX環(huán)境高級(jí)編程》一書中,直接讀寫也即二進(jìn)制讀寫,指一次讀寫一個(gè)完整的結(jié)構(gòu),例如一個(gè)結(jié)構(gòu)體對(duì)象。通常用來讀寫指定的字節(jié)大小的數(shù)據(jù)。

常用的直接讀寫的函數(shù)如下:

#include <stdio.h>
// 若出錯(cuò)或到達(dá)文件末尾,返回值可以小于 nobj;需要調(diào)用 ferror 或 feof 來判斷是哪一種情況。
size_t fread(void* ptr, size_t size, size_t nobj, FILE* fp);
// 若出錯(cuò),返回值小于 nobj
size_t fwrite(const void* ptr, size_t size, size_t nobj, FILE* fp);
/*
以上兩個(gè)函數(shù),返回讀寫的對(duì)象數(shù)量。
ptr 指向待寫入的對(duì)象
size 表示對(duì)象的大小
nobj 表示寫入的對(duì)象數(shù)量
*/

下面給出幾個(gè)編碼示例:

讀寫char數(shù)組形式的字符串。

char buffer[16] = "python";
FILE* wfp = fopen("test2.txt", "w");
fwrite(buffer, 1, strlen(buffer), wfp);
fclose(wfp);
printf("%s\n", buffer);
char buffer2[16];
FILE *rfp = fopen("test2.txt", "r");
fread(buffer2, 1, strlen(buffer), rfp);
fclose(rfp);
printf("%s\n", buffer2);

使用 fread 和 fwrite 讀寫一個(gè)類的示例對(duì)象(有bug)。(無意中測試出來的一個(gè)bug,暫未解決。一個(gè)初步的思路為,需要去學(xué)習(xí)了解 C++ 的對(duì)象模型,即一個(gè)C++的對(duì)象在內(nèi)存中是如何布局的,然后在深入 fread 和 fwrite 的源碼中,去了解,其底層是如何讀寫的。在此文中先留個(gè)坑,后面再來填補(bǔ))

void test1()
{
    Person p1("Jack", 25);
    FILE* wfp = fopen("Person", "wb");
    fwrite((void*)&p1, sizeof(Person), 1, wfp);
    fclose(wfp);
    Person* p2 = new Person("Lisa", 19);
    std::cout << p2->name() << std::endl;
    FILE* rfp = fopen("Person", "rb");
    fread(p2, sizeof(Person), 1, rfp);
    fclose(rfp);
    std::cout << p2->name() << std::endl;
    // delete p2;   /* 若在這行執(zhí)行此語句,出現(xiàn) Segmentation fault */
    Person p3;
    FILE* rfp2 = fopen("Person", "rb+");
    fread(&p3, sizeof(Person), 1, rfp2);
    fclose(rfp2);
    std::cout << p3.name() << std::endl;
    /* 程序結(jié)束,Segmentation fault */
}

格式化讀寫

格式化輸出常用的有 printffprintfdprintfsprintfsnprintf 五個(gè)函數(shù),下面介紹最常用的 printf 和 fprintf 函數(shù)。

#include <stdio.h>
int printf(const char* format, ...);
int fprintf(FILE* fp, const char* format, ...);
/*
以上兩個(gè)函數(shù),成功,返回輸出的字符數(shù);出錯(cuò),返回復(fù)制。
format 表示格式化字符串;
... 為C語言的可變參數(shù),需要與 format 中的格式化進(jìn)行匹配。
*/

格式化輸入常用的有如 scanffscanf 和 sscanf 。

對(duì)于標(biāo)準(zhǔn)庫中格式化讀寫的更多細(xì)節(jié),太過瑣碎,可參考:https://en.cppreference.com/w/cpp/io/c/fscanf

多線程的安全性

上述的章節(jié)中介紹的文件流的讀寫函數(shù)都是線程安全的,會(huì)在正常進(jìn)行磁盤文件讀寫時(shí)進(jìn)行加鎖操作。標(biāo)準(zhǔn)庫中也提供非線程安全的版本,它們都以 _unlocked 后綴結(jié)尾。例如對(duì)于 fread 和 fwrite 的非線程安全版本為 fread_unlocked 和 fwrite_unlocked。

總結(jié)

到此這篇關(guān)于Linux系統(tǒng)下C語言中的標(biāo)準(zhǔn)IO的文章就介紹到這了,更多相關(guān)C語言標(biāo)準(zhǔn)IO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++ pair的用法案例詳解

    C++ pair的用法案例詳解

    這篇文章主要介紹了C++ pair的用法案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-09-09
  • vscode刷acm、leetcode的題目

    vscode刷acm、leetcode的題目

    vscode是一款越來越受碼農(nóng)們喜愛的軟件,大多數(shù)人學(xué)習(xí)編程繞不開的一部分就是算法,很多人都喜歡刷LeetCode的題目,本文就來介紹一下
    2021-06-06
  • c++?error:crosses?initialization?of問題解決分析

    c++?error:crosses?initialization?of問題解決分析

    這篇文章主要介紹了c++?error:crosses?initialization?ofde?問題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • C語言實(shí)現(xiàn)opencv提取直線、輪廓及ROI實(shí)例詳解

    C語言實(shí)現(xiàn)opencv提取直線、輪廓及ROI實(shí)例詳解

    這篇文章主要介紹了C語言實(shí)現(xiàn)opencv提取直線、輪廓及ROI實(shí)例詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • C語言技巧提升之回調(diào)函數(shù)的掌握

    C語言技巧提升之回調(diào)函數(shù)的掌握

    這篇文章主要為大家詳細(xì)介紹一下C語言中回調(diào)函數(shù)的用法教程,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C語言有一定幫助,需要的可以參考一下
    2022-12-12
  • C++中MFC Tab Control控件的使用詳解

    C++中MFC Tab Control控件的使用詳解

    這篇文章主要介紹了C++中MFC Tab Control控件的使用詳解的相關(guān)資料,需要的朋友可以參考下
    2015-06-06
  • C語言實(shí)現(xiàn)排序算法之歸并排序詳解

    C語言實(shí)現(xiàn)排序算法之歸并排序詳解

    這篇文章主要介紹了C語言實(shí)現(xiàn)排序算法之歸并排序,對(duì)歸并排序的原理及實(shí)現(xiàn)過程做了非常詳細(xì)的解讀,需要的朋友可以參考下
    2014-07-07
  • C語言中的鏈接編寫教程

    C語言中的鏈接編寫教程

    這篇文章主要介紹了C語言中的鏈接編寫教程,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-08-08
  • C++ move()函數(shù)案例詳解

    C++ move()函數(shù)案例詳解

    這篇文章主要介紹了C++ move()函數(shù)案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-09-09
  • C++實(shí)現(xiàn)編碼轉(zhuǎn)換的示例代碼

    C++實(shí)現(xiàn)編碼轉(zhuǎn)換的示例代碼

    這篇文章主要介紹了C++實(shí)現(xiàn)編碼轉(zhuǎn)換的示例代碼,幫助大家快捷的實(shí)現(xiàn)編碼轉(zhuǎn)換,感興趣的朋友可以了解下
    2020-08-08

最新評(píng)論