C/C++標準輸入輸出函數終極最全解析(不全捶我)

来源:https://www.cnblogs.com/midoq/archive/2022/12/06/16961817.html
-Advertisement-
Play Games

JSON&Ajax01 JSON 線上文檔 AJAX 線上文檔 1.JSON介紹 JSON指的是JavaScript對象表示法( JavaScript Object Notation),JSON的本質仍然是JavaScript對象 JSON是輕量級的文本數據交互格式,也是前後端進行數據通訊的一種格式 ...


C/C++的一眾輸入輸出函數的區別常常搞得人暈頭轉向,二者之中又以輸入函數更加令人頭疼。本文嘗試整理C/C++的各種輸入輸出函數。

由於輸入涉及空格、換行符的讀取、忽略等問題,因此輸入比輸出更麻煩。所以本文將以輸入為主線,對應的輸出用法是類似的。

水平有限,如有疏漏,歡迎提出。

標準輸入流

C 標準輸入

C語言使用標準輸入輸出函數,需要包含頭文件<stdio.h>。而在 C++ 中,只要包含頭文件<iostream>,就完全可以使用這些 C 中的輸入輸出函數。

標準輸入流及對緩衝區的理解

stdin是一個文件描述符(Linux)或句柄(Windows),它在 C 程式啟動時就被預設分配好。在 Linux 中一切皆文件,stdin也相當於一個可讀文件,它對應著鍵盤設備的輸入。因為它不斷地被輸入,又不斷地被讀取,像流水一樣,因此通常稱作輸入流

stdin是一種行緩衝I/O。當在鍵盤上鍵入字元時,它們首先被存放在鍵盤設備自身的緩存中(屬於鍵盤硬體設備的一部分)。只有輸入換行符時,操作系統才會進行同步,將鍵盤緩存中的數據讀入到stdin的輸入緩衝區(存在於記憶體中)。所有從stdin讀取數據的輸入流,都是從記憶體中的輸入緩衝區讀入數據。當輸入緩衝區為空時,函數將被阻塞。

若無特殊說明,以下所有的“緩衝區”均是指記憶體中的stdin輸入緩衝區。用戶程式中自定義的buffer數組、str數組等,將稱作“數組”、“變數”,以免產生混淆。

scanf()

按照特定格式從stdin讀取輸入。

用法示例:

char str[100];
int a;
scanf("%s %d", str, &a);    // 註意,傳入的一定是變數的地址

對空白字元的處理:

  1. 緩衝區開頭:丟棄空白字元(包括空格、Tab、換行符),直到第一個非空白字元才認為是第一個數據的開始。
  2. 緩衝區中間:開始讀取第一個數據後,一旦遇到空白字元(非換行符), 就認為讀取完畢一次。遇到的空白字元殘留在緩衝區,直到下一次被讀取或刷新。例如輸入字元串this is test,則會被認為是3個字元串。
  3. 緩衝區末尾:按下回車鍵時,換行符\n殘留在緩衝區。換行符之前的空格可以認為是中間的空白字元,處理同上。

註意,格式控制符只會讀取正確類型的變數。如果輸入格式不正確,比如在%d處輸入了一個字元a,則會使讀取中斷,即後續不讀取任何變數。

格式控制符說明:

類型 類型輸入 參數的類型
%d 十進位整數 int *
%u 無符號十進位整數 unsigned int *
%o 八進位整數 int *
%x 十六進位整數 int *
%f、%e、%g 浮點數 float *
%lf、%le、%lg 雙精度浮點數 double *
%c 單個字元(含空白字元) char *
%s 字元串 char *
%% 讀 % 符號

註意,%c是一個比較特殊的格式符號,它將會讀取所有空白字元,包括緩衝區開頭的空格、Tab、換行符,使用時要特別註意。

scanf()的讀取也沒有邊界,所以並不安全。C11 標準提供了安全輸入scanf_s()

scanf()對應的輸出函數是printf()

gets() - 不建議

按下回車鍵時,從stdin讀取一行。

用法示例:

char str[100];
gets(str);

對空白字元的處理:

  1. 所有空格、Tab等空白字元均被讀取,不忽略。
  2. 按下回車鍵時,緩衝區末尾的換行符被丟棄,字元串末尾沒有換行符\n,緩衝區也沒有殘留的換行符\n

註意,gets()不能指定讀取上限,因此容易發生數組邊界溢出,造成記憶體不安全。C11 使用了gets_s()代替gets(),但有時編譯器未必支持,因此總體來說不建議使用gets()函數來讀取輸入。

gets()對應的輸出函數是puts()

fgets()

從指定輸入流讀取一行,輸入可以是stdin,也可以是文件流,使用時需要顯式指定。

讀取文件流示例:

char str[100];
memset(str, 0, sizeof(str));
int i = 1;
 
FILE *fp = fopen("...test.txt", "r");
if (fp == NULL) {
    printf("File open Error!\n");
    exit(1);
}
 
while (fgets(str, sizeof(str), fp) != NULL)
    printf("line%d [len %d]: %s", i++, strlen(str), str);
 
fclose(fp);

讀取stdin示例:

char str[100];
memset(str, 0, sizeof(str));
int i = 1;
while (fgets(str, sizeof(str), stdin) != NULL)
    printf("line%d [len %d]: %s", i++, strlen(str), str);

對空白字元的處理:

  1. 所有空格、Tab等空白字元均被讀取,不忽略。
  2. 按下回車鍵時,緩衝區末尾的換行符也被讀取,字元串末尾將有一個換行符\n。例如,輸入字元串hello,再按下回車,則讀到的字元串長度為6

fgets()函數會自動在字元串末尾加上\0結束符。

第 2 個參數n指定了讀取的最大長度。函數讀到n-1個字元(包括換行符\n)就會停止,併在末尾加上\0結束符。剩餘字元將殘留在緩衝區。

建議使用fgets()完全替代gets()

fgets()對應的輸出函數是fputs()

fgetc() & getc()

從指定輸入流讀取一個字元,輸入可以是stdin,也可以是文件流,使用時需要顯式指定。

這兩個函數完全等效,getc()fgetc()巨集定義而來。不同的是,前述的gets()fgets()相互之間沒有關係。

用法示例:

char a, b;
a = fgetc(stdin);
b = getc(stdin);

對空白字元的處理:

  1. 所有空格、Tab、換行等空白字元,無論在緩衝區開頭、中間還是結尾,均會被讀取,不忽略。
  2. 因為只讀取一個字元,所以如果輸入多於1個字元(包括換行符),則它們均會殘留在緩衝區。具體地說,如果什麼字元都不輸入,直接按下回車鍵,則讀取到的是換行符\n,緩衝區無任何殘留;如果輸入一個字元如a,然後按下回車鍵,則讀取到的是字元a,同時換行符\n殘留在緩衝區。

fgetc()getc()對應的輸出函數是fputc()putc()

getchar()

stdin讀取一個字元。

getchar()實際上也由fgetc()巨集定義而來,只是預設輸入流為stdin

用法示例:

char a;
a = getchar();

getchar()常常用於清理緩衝區開頭殘留的換行符。當知道緩衝區開頭有\n殘留時,可以調用getchar()但不賦值給任何變數,即可實現沖刷掉\n的效果。

getchar()對應的輸出函數是putchar()

C++ 標準輸入

C++中使用標準輸入輸出需要包含頭文件<iostream>。一般使用iostream類進行流操作,其封裝很完善,也比較複雜,本文只介紹一部分。

cin

cin是 C++ 的標準輸入流對象,即istream類的一個對象實例。cin有自己的緩衝區,但預設情況下是與stdin同步的,因此在 C++ 中可以混用 C++ 和 C 風格的輸入輸出(在不手動取消同步的情況下)。

cinstdin一樣是行緩衝,即遇到換行符時才會將數據同步到輸入緩衝區。

cin的用法非常多,只列舉常用的幾種。最常用的就是使用>>符號(我認為該符號形象地體現了“流”的特點)。

用法示例:

int a, b;
cin >> a >> b;
char str[20];
cin >> str;

cin對空白字元的處理與scanf一致。即:跳過開頭空白字元,遇到空白字元停止讀取,且空白字元(包括換行符)殘留在緩衝區。

如果不想跳過空白字元,可以使用流控制關鍵詞noskipws(no skip white space),但這隻對單個字元有效(類似於scanf中的%c)。

char c;
cin >> noskipws >> c;

註意,cin對象屬於命名空間std,如果想使用cin對象,必須在 C++ 文件開頭寫using namespace std,或者在每次用到的時候寫成std::cin

cin.get()

讀取單個或指定長度的字元,包括空白字元。

用法示例:

char a, b;
char str[20];
 
// 讀取一個字元,讀取失敗時返回0,多餘字元殘留在緩衝區(包括換行符)
a = cin.get();
 
// 讀取一個字元,讀取失敗時返回EOF,多餘字元殘留在緩衝區(包括換行符)
cin.get(b);
 
// 在遇到指定終止字元(參數3)前,至多讀取n-1個(參數2)字元
// 當不指定終止字元時,預設為換行符\n
// 如果輸入的字元個數小於等於n-1(不含終止字元),則終止字元不殘留在緩衝區
// 如果輸入的字元個數多於n-1(不含終止字元),則餘下字元將殘留在緩衝區
cin.get(str, sizeof(str), '\n');

cin.get()讀取單個字元時,類似於 C 中的fgetc(),對空白字元的處理也與其一致。cin.get()讀取的字元也可以賦值給整型變數。

cin.get()讀取指定長度個字元時,類似於 C 中的fgets(),但在換行符的處理上不同。它們都不會使換行符殘留在緩衝區,但fgets()會將緩衝區末尾的換行符\n也寫入字元串,而cin.get()會丟棄緩衝區末尾的\n。即:當輸入test時,用fgets()讀取得到的字元串長度為5,用cin.get()讀取得到的字元串長度為4

cin.getline()

讀取指定長度的字元,包括空白字元。

用法示例:

char str[20];
cin.getline(str, sizeof(str));    // 第3個參數也可以指定終止字元

cin.getline()cin.get()指定讀取長度時的用法幾乎一樣。區別在於,如果輸入的字元個數大於指定的最大長度n-1(不含終止符),cin.get()會使餘下字元殘留在緩衝區,等待下次讀取;而cin.getline()會給輸入流設為 Fail 狀態,在主動恢復之前,無法再進行正常輸入。

getline()

getline()並不是標準輸入流istream的函數,而是字元串流sstream的函數,只能用於讀取數據給string類對象,使用時也需要包含頭文件<string>

如果使用getline()讀取標準輸入流的數據,需要顯式指定輸入流。

用法示例:

string str;
getline(cin, str);

getline()會讀取所有空白字元,且緩衝區末尾的換行符會被丟棄,不殘留也不寫到字元串結尾。同時,由於string對象的空間是動態分配的,所以會一次性將緩衝區讀完,不存在讀不完殘留在緩衝區的問題。

需要註意的是,假如緩衝區開頭就是換行符(比如可能是上一次cin殘留的),則getline()會直接讀取到空字元串並結束,不會給鍵盤輸入的機會。所以這種情況下要註意先清除開頭的換行符。

總結

在 C 中,建議使用scanf()進行格式化讀取,用fgets()讀取整行,用fgetc()getchar()讀取單個字元。

在 C++ 中,建議使用cin >>進行格式化讀取,而cin.get()cin.getlinegetline(string)有各自的適用情況。

註意fgets()cin.get()在對換行符的清理方面有所區別。

標準輸出流

C 標準輸出

標準輸出流及對緩衝區的理解

相應於輸入流的stdin,輸出流也有其預設的文件描述符stdout,對應著命令行終端(Windows 中稱為控制台)的顯示。此外,還有對應錯誤輸出的stderr,預設也是終端的顯示。它們都可以被重定向到文件中以便持久保存和查看,在此不作贅述。

stdout也是行緩衝I/O,它與stdin類似也有三者之間的數據同步:從用戶程式到stdout的輸出緩衝區,由用戶程式決定;從stdout的輸出緩衝區到終端的顯示,只有緩衝區末尾遇到換行符\n才會進行。如果輸出緩衝區末尾沒有換行符\n,是不會列印顯示輸出的。

例如以下程式:

// 程式 1
int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    while(1){}
    return 0;
}
 
// 程式 2
int main(int argc, char* argv[])
{
    printf("Hello World!");
    while(1){}
    return 0;
}
 
// 程式 3
int main(int argc, char* argv[])
{
    printf("Hello World!")
    return 0;
}
 
// 程式 1
int main(int argc, char* argv[])
{
    printf("Hello World!\nABCDE");
    while(1){}
    return 0;
}

程式 1 中,printf()輸出內容的最後有換行符\n,所以將在屏幕上輸出Hello World!並換行,然後進入while(1)迴圈阻塞住。

程式 2 中,把\n去掉了,此時終端不會顯示任何內容。因為程式進入死迴圈後,沒有機會向stdout中寫入\n使其清空緩衝。

程式 3 中,雖然沒有寫入換行符,但是依然能夠在終端列印Hello World!(只是沒有換行)。這是因為程式結束時會自動清空緩衝區。(除此之外,當緩衝區被填滿時也會自動清空)

程式 4 能夠進一步加深對行緩衝的理解。它在程式 1 的基礎上,在換行符之後又加上了幾個字元。運行可以發現終端只列印了Hello World!並換行,而沒有列印ABCDE

輸出函數通常沒有針對對空格、製表符的特殊行為,比輸入要簡單一些。特殊的處理一般只有換行符。

printf()

按照特定格式將stdout緩衝區的內容列印到終端。

用法示例:

printf("Number a = %d", a);      // 十進位整數
printf("Number b = %.2f", b);    // 浮點數,保留兩位小數
printf("String s = %s", s);      // 字元串

printf()的寫法與scanf()十分相像。區別在於scanf()中一般只有格式控制字元,而沒有其他普通字元,而printf()中常常是在一串字元中把要替換的內容寫為格式控制字元,從而形成格式化輸出的效果。

puts()

將字元串和一個尾隨的換行符\n寫入到stdout的緩衝區。根據行緩衝的性質,終端也會立即進行列印顯示。

用法示例:

puts("hello");    // 立即輸出hello並換行

puts()對換行符的處理與gets()“相反”。gets()會自動丟棄一個換行符,而puts()則是自動寫入一個換行符。

fputs()

字元串寫入指定輸出流,可以是文件流、stdoutstderr等。stderr是標準錯誤流,它是無緩衝的,會立即輸出到屏幕,而不是等待換行符才輸出。

用法示例:

fputs("hello world", stdout);    // 不會立即輸出
fputs("hello world\n", stdout);  // 立即輸出
fputs("hello world", stderr);  // 立即輸出

fgets()一樣,fputs()不會主動操作換行符。如果希望立即輸出,需要自己加上換行符\n

fputc() & putc()

一個字元寫入指定輸出流,可以是文件流、stdoutstderr等。

用法示例:

char c = 'q';
fputc(c, stdout);
c = '\n';
putc(c, stdout);

fputc()putc()只是把字元寫入stdout,沒有任何額外操作。因此如果希望立即輸出,需要自己加上換行符\n

putchar()

一個字元寫入到標準輸出流stdout

用法示例:

char c = 'x';
putchar(c);

同上,putchar()不操作換行符。如果希望立即輸出,需要自己加上換行符\n

fflush()

該函數的功能是強制刷新緩衝區,將數據立即寫到對應的文件(或設備)。其參數可以是文件流指針,也可以是stdout

用法示例:

fputs("Hello World!", stdout);
fflush(stdout);
while (1);

上面的程式在進入死迴圈前,會輸出Hello World!字元串到屏幕。

註意:不能夠將fflush()用於stdin!這可能導致不可預料的後果。

C++ 標準輸出

cout

coutostream類的一個實例。cout是行緩衝的。

用法示例:

char str[] = "hello world";
cout << "str: " << str << endl;

插入endl對象時,將立即清空輸出緩衝區並顯示,然後輸出一個換行符\n

也有cout.put()等函數,不常用。

cerr

cerr是標準錯誤流,也是ostream類的一個實例,並預設輸出設備為顯示屏上的命令行終端。它預設與stderr同步。

cerr非緩衝的,即插入數據時會立即輸出。

用法示例:

char str[] = "File open FAILED!";
cerr << "[Error] " << str;

clog

clog是標準日誌流,也是ostream類的一個實例,並預設輸出設備為顯示屏上的命令行終端。

clog是有緩衝的,但具體的刷新條件沒有找到資料。實測以下代碼是可以輸出在屏幕的:

clog << "Failed!";
while(1){}

總結

標準輸出相比輸入來說較為簡單。需要註意的是stdoutcout行緩衝的,而stderrcerr無緩衝的。

C++ 流的高級用法請參考其他資料。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 以前真機調試手機頁面,都是使用數據線連接手機和電腦,近日身邊沒有USB數據線,折騰了下如何不依賴數據線只用無線調試手機頁面,教程如下。 本教程適用於安卓11以及以上版本。否則應該使用USB數據線連接。 一、安裝adb工具 下載地址:https://developer.android.com/stud ...
  • 前面的文章分享了組件庫的開發、example、組件庫文檔,本文分享組件庫 cli 開發。 1 為什麼要開發組件庫 cli 回顧一個新組件的完整開發步驟: 1 在 packages 目錄下創建組件目錄 xxx: 1.1 使用 pnpm 初始化 package.json,修改 name 屬性; 1.2 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 因公司業務需要,需要開發水印相機功能,而項目代碼用的uniapp框架,App端只能簡單調用系統的相機,無法自定義界面,在此基礎上,只能開發自定義插件來完成功能(自定義原生插件,即是用原生代碼來編寫組件實現功能,然後供uniapp項目調用) ...
  • Type 描述:全稱叫做 '類型別名',為類型字面量提供名稱。比 Interface 支持更豐富的類型系統特性。 Type 與 Interface 區別 Interface 只能描述對象的形狀,Type 不止 Interface 能多次聲明進行擴展,Type 不行 在性能方面,Type 介面檢查能夠 ...
  • 在過往,我們想要實現一個圖片的漸隱消失。最常見的莫過於整體透明度的變化,像是這樣: <div class="img"></div> div { width: 300px; height: 300px; background: url(image.jpg); transition: .4s; } .i ...
  • 我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 本文作者:正則 作為一名前端開發人員,平時開發中使用最多的就是 Chrome devtools,但可能很多同學像我一樣平時用的最多也就 Console、Elements ...
  • 軟體設計模式(Design pattern),又稱設計模式,是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性、程式的重用性。 ...
  • 摘要:華為雲Solution as Code推出基於Ploto構建自動駕駛平臺解決方案。 本文分享自華為雲社區《基於Ploto構建自動駕駛平臺》,作者:阿米托福 。 2022年6月15日,主題為“因聚而生 為你所能”的華為伙伴暨開發者大會 2022 正式開啟,在自動駕駛專場中,華為雲攜手合作伙伴聯合 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...