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

Linux系統(tǒng)下C語言中的標準IO總結(jié)

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

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

標準IO中的一些概念

流和FILE對象

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

#include <stdio.h>

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

注意:一個進程預定了三個流,標準輸入、標準輸出和標準錯誤。而本文的重點討論對象是 文件流,也即 FILE 對象以及標準I/O中提供的操作 FILE 對象的一系列函數(shù)。

流的定向(stream’s orientation)

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

下面給出一個寬字符集輸出到標準輸出的示例:

#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;
}

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

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

緩沖(buffer)

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

標準 I/O 提供了三種緩沖類型:

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

I/O標準庫中常用的函數(shù)

文件流的打開和關閉

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

#include <stdio.h>

/*
pathname參數(shù)表示打開的文件路徑名;
type參數(shù)指定對文件流的讀、寫方式。
若打開出錯,返回 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);

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

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

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

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

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

#include <stdio.h>

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

當調(diào)用 fclose 函數(shù)或者當一個進程正常終止(調(diào)用 exit 函數(shù)或從 main 函數(shù)返回),會先刷新緩沖。若是使用標準IO默認的緩沖,則會釋放緩沖。

給文件流設置自定義的緩沖

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

#include <stdio.h>

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

/*
	buf參數(shù)為指定緩沖區(qū),mode表示緩沖類型,size指定了緩沖的大小。
	成功返回0;出錯返回非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,無緩沖。

對上面三個函數(shù)進行如下補充說明:

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

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

文件流的讀寫

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

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

讀寫一個字符

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

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

對上面三個函數(shù)進行補充說明:

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

對于上述三個函數(shù)的出錯,在文件流 FILE 對象中,每個 FILE 對象維護了兩個標志:

  • 出錯標志;
  • 文件結(jié)束標志;

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

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

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

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

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

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

下面給出幾個編碼示例:

假設文件中的內(nèi)容為 python,一個字符一個字符的把文件中的內(nèi)容輸出到標準輸出。

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

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

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
*/

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

讀寫一行

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

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

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

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

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

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

直接讀寫

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

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

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

下面給出幾個編碼示例:

讀寫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 讀寫一個類的示例對象(有bug)。(無意中測試出來的一個bug,暫未解決。一個初步的思路為,需要去學習了解 C++ 的對象模型,即一個C++的對象在內(nèi)存中是如何布局的,然后在深入 fread 和 fwrite 的源碼中,去了解,其底層是如何讀寫的。在此文中先留個坑,后面再來填補)

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 五個函數(shù),下面介紹最常用的 printf 和 fprintf 函數(shù)。

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

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

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

多線程的安全性

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

總結(jié)

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

相關文章

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

    C++ pair的用法案例詳解

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

    vscode刷acm、leetcode的題目

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

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

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

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

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

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

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

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

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

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

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

    C語言中的鏈接編寫教程

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

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

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

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

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

最新評論