muduo網路庫學習筆記(15):關於使用stdio和iostream的討論
C/C++程式中需要執行輸入/輸出時,我們一般會用到stdio或iostream。stdio指C語言的scanf/printf系列格式化輸入輸出函式,iostream指C++語言的cin/cout輸入輸出物件等。
但是,在真實的專案中很少用到iostream(muduo網路庫也不例外),本篇就對二者的優、缺點進行一個小結(主要考慮x86 Linux平臺,不考慮跨平臺的可移植性,但是要考慮32-bit和64-bit的相容性)。
stdio
(一)缺點:對程式設計初學者不友好
我們在C語言入門時、在輸出第一行“Hello World”程式碼時,用到了標準輸入輸出stdio,而在學習C++語言的過程中,我們又接觸到了iostream庫的輸入輸出機制。相對於iostream給初學者提供了一個方便的命令列輸入輸出實驗環境,stdio對於初學者來說則繁瑣很多,看下面這個示例:
#include <stdio.h>
int main()
{
int i;
short s;
float f;
double d;
char name[80];
scanf("%d %hd %f %lf %s", &i, &s, &f, &d, name);
printf("%d %d %f %f %s\n", i, s, f, d, name);
return 0;
}
可以看到:
1.輸入和輸出用的格式字串不一樣。輸入short要用%hd,輸出用%d;輸入double要用%lf,輸出用%f。
2.輸入的引數不統一。對於i,s,f,d等變數,在傳入scanf()的時候要取地址(&);而對於字元陣列name,則不用取地址。
3.程式有緩衝區溢位的危險。上面的例子在讀入name的時候沒有指定大小,這是用C語言程式設計的安全漏洞的主要來源。
向剛開始學程式設計的初學者清楚解釋這幾條背後的原因可謂是相當困難(涉及傳遞函式不定引數時的型別轉換、函式呼叫棧的記憶體佈局、指標的意義、字元陣列退化為字元指標等等)。
iostream則對初學者很友好,用iostream重寫與前面同樣功能的程式碼,如下:
#include <iostream>
#include <string>
using namespace std;
int main()
{
int i;
short s;
float f;
double d;
string name;
cin >> i >> s >> f >> d >> name;
cout << i << " " << s << " " << f << " " << d << " " << name << endl;
return 0;
}
這段程式碼對於初學者來說更易懂,而且沒有安全性方面的問題。
(二)缺點:安全性問題
輸出方面的安全性問題,可用snprintf()等能夠指定輸出緩衝區大小的函式來解決;但輸入方面似乎沒有太大的進展,例如需要從檔案或標準輸入讀入一行不確定長度的字串,C語言標準庫函式gets()、fgets()和getline()都不能完美地完成這個任務,還要靠程式設計師自己動手(一種讀取不定長字串輸入的實現:https://segmentfault.com/a/1190000000360944)。
另外,引數型別繁多導致的型別安全問題也不容忽視。如果printf()的整數引數型別是int、long等內建型別,那麼printf()的格式化字串很容易寫,但是如果引數是系統檔案裡typedef的型別呢,例如clock_t、in_addr_t、pid_t等?
這些問題在C++裡都不存在,在這方面iostream是個進步。
(三)缺點:不可擴充套件
舉例來說:
#include <stdio.h>
struct Date
{
int year, month, day;
};
int main()
{
Date date;
printf("%D\n", &date); // 錯誤
return 0;
}
即C stdio無法支援自定義的型別,iostream則可通過函式過載來實現可擴充套件性,如下:
#include <iostream>
class Date
{
public:
Date(int year, int month, int day) : year_(year), month_(month), day_(day)
{
}
void writeTo(std::ostream& os)const
{
os << year_ << '-' << month_ << '-' << day_;
}
private:
int year_, month_, day_;
};
std::ostream& operator<<(std::ostream& os, const Date& date)
{
date.writeTo(os);
return os;
}
int main()
{
Date date(2017, 5, 28);
std::cout << date << std::endl;
return 0;
}
(四)優點:通用性廣
在C語言之外,有其他很多語言也支援printf()風格的格式化。學會 printf() 的格式化方法,這個知識還可以用到其他語言中。但是 C++ iostream 只此一家別無分店,反正都是格式化輸出,stdio 的投資回報率更高。
所以,我們不必深究 iostream 的格式化方法,只需要用好它最基本的型別安全輸出即可。在真的需要格式化的場合,可以考慮 snprintf() 列印到棧上緩衝,再用 ostream 輸出。
(五)優點:外部可配置性強
比方說,我想用一個外部的配置檔案來定義日期的格式。C stdio很好辦,把格式字串”%d-%02d-%02d”儲存到配置裡就行。但是iostream呢?它的格式是寫死在程式碼裡的,靈活性大打折扣。
iostream
通過以上的討論,我們可以知道iostream相對於stdio具有型別安全、型別可擴充套件等優點。但是深入一點,就會發現iostream在使用和設計方面的不可避免的缺點,“瑜不掩瑕”。以下是iostream在使用方面的一些缺點:
(一)輸入方面,istream不適合輸入帶格式的資料,因為“糾錯”能力不強。
(二)輸出方面,格式化輸出很繁瑣。舉一個例子來說明,以“2017-05-28”的格式來輸出前面定義的Date class:
// 用iostream
void writeTo(std::ostream& os)const
{
os << year_ << '-'
<< std::setw(2) << std::setfill('0') << month_ << '-'
<< std::setw(2) << std::setfill('0') << day_;
}
// 用stdio
void writeTo(std::ostream& os)const
{
char buf[32];
snprintf(buf, sizeof buf, "%d-%02d-%02d", year_, month_, day_);
os << buf;
}
正如前面的第(四)點所言。
(三)stream的狀態易受影響。例如,我們想輸出一個16進位制的數字x,那麼可以用 hex 操控符,但是這會改變 ostream 的狀態:
// 這段程式碼會將數字123也以16進位制的方式輸出,這恐怕不是我們想要的
int x = 666;
cout << hex << showbase << x << endl; // forget to reset state
cout << 123 << endl;
再舉一個例子,setprecision() 也會造成持續影響:
double d = 123.45;
printf("%8.3f\n", d); // 輸出:123.450
cout << d << endl; // 輸出:123.45
cout << setw(8) << fixed << setprecision(3) << d << endl; // 輸出:123.450
cout << d << endl; // 輸出:123.450
可見程式碼中的setprecision()影響了後續輸出的精度。注意:setw()不會造成影響,它只對下一個輸出有效。
這說明,如果使用manipulator來控制格式,需要時刻小心防止影響了後續程式碼。而使用C stdio就沒有這個問題,它是“上下文無關的”。
(四)執行緒安全方面。stdio的函式是執行緒安全的,而且 C 語言還提供了flockfile(3)/funlockfile(3)之類的函式來明確控制 FILE* 的加鎖與解鎖。
iostream 線上程安全方面沒有保證,就算單個 operator<< 是執行緒安全的,也不能保證原子性。因為 cout << a << b; 是兩次函式呼叫,相當於 cout.operator<<(a).operator<<(b)。兩次呼叫中間可能會被打斷進行上下文切換,造成輸出內容不連續,插入了其他執行緒列印的字元。
而 fprintf(stdout, “%s %d”, a, b); 是一次函式呼叫,而且是執行緒安全的,列印的內容不會受其他執行緒影響。
因此,iostream並不適合在多執行緒程式中做logging。
(五)效能方面。iostream在某些場合比 stdio快,在某些場合比stdio慢,對於效能要求較高的場合,我們應該自己實現字串轉換。(tips:線上 ACM/ICPC 判題網站上,如果一個簡單的題目發生超時錯誤,那麼把其中iostream的輸入輸出換成stdio,有時就能過關)
因此,iostream在實際專案中的應用就大為受限了。
參考資料:
C++ 工程實踐(7):iostream 的用途與侷限(博文對iostream的侷限做了更全面、深層次的剖析,值得一看)
http://www.cnblogs.com/Solstice/archive/2011/07/17/2108715.html
相關文章
- 關於神經網路的討論神經網路
- 關於網路安全的逆向分析方向學習筆記筆記
- Web 開發學習筆記——關於網際網路和網際網路應用Web筆記
- muduo網路庫Timestamp類
- 一個關於組織學員學習技術的筆試題--求討論筆試
- muduo網路庫Exception異常類Exception
- muduo網路庫編譯安裝編譯
- 關於http(自己的學習筆記)HTTP筆記
- Myth 關於Git的學習筆記Git筆記
- muduo網路庫AtomicIntegerT原子整數類
- 網路流學習筆記筆記
- 【學習筆記】網路流筆記
- Beautiful Soup庫的使用(學習筆記)筆記
- 一個關於月球車的筆試題--求討論筆試
- 網路學習筆記(一):TCP連線的建立與關閉筆記TCP
- 關於撲克牌的一些討論——《Fluent Python 2》讀書筆記Python筆記
- 關於網站設計的一點點討論網站
- 群論學習筆記筆記
- 關於Apache Tika的學習和使用Apache
- Mudo C++網路庫第七章學習筆記C++筆記
- Mudo C++網路庫第十一章學習筆記C++筆記
- Mudo C++網路庫第五章學習筆記C++筆記
- substrate學習筆記5:使用substrate構建私有網路筆記
- 學習筆記16:殘差網路筆記
- Git 的工作區、暫存區、版本庫—— Git 學習筆記 15Git筆記
- 【Python學習】學習筆記 14-15 字串Python筆記字串
- 關於django reset_framework學習之路的筆記DjangoFramework筆記
- 關於 Service Worker 和 Web 應用對應關係的討論Web
- Mudo C++網路庫第六章學習筆記C++筆記
- Mudo C++網路庫第八章學習筆記C++筆記
- Mudo C++網路庫第十章學習筆記C++筆記
- 機率論 學習筆記筆記
- substrate學習筆記7:使用substrate構建授權網路筆記
- Adaptive AUTOSAR 學習筆記 16 - 時間同步和網路管理APT筆記
- 關於最近學習的Less預編譯語言的筆記,歡迎大家一起探討~編譯筆記
- iOS學習筆記14 網路(三)WebViewiOS筆記WebView
- 強化學習-學習筆記15 | 連續控制強化學習筆記
- Django學習筆記(15)——中介軟體Django筆記
- XML學習筆記(一):關於字元編碼的理解XML筆記字元