使用範例
#include<iostream>using namespace std;//此句也可以在main函式體中出現。int main(){ int a; cout << "請輸入一個數字,按回車結束" << endl; cin >> a; cout << a << endl; return 0;}//用戶輸入的數字由cin保存於變數a中,並通過cout輸出。#include<iostream>using namespace std;int main(){ cout << "Hello,World!" << endl; return 0;}//HelloWorld示例
案例分析
由於以前學過C,所以這段代碼的其它部分在我看來都還算“正常”,然而cout卻很獨特:既不是函式,似乎也不是C++特別規定出來的像if,for一類有特殊語法的“語句”。由於只是初步介紹,所以那本書只是簡單的說cout是C++中的“標準輸入輸出流”對象……這對於我而言實在是一個很深奧的術語。這還沒完,之後又遇見了cin……因為不知底細,從此使用它們的時候都誠惶誠恐,幾欲逃回C時代那簡明的printf(),畢竟好歹我可以說:我在調用的是一個函式。那有著一長串<<、>>的玩意,究竟算怎么回事呢?我一直想把它們當作關鍵字,可偏偏不是,而且居然是用C++語言“做”出來的,呵!但printf()用多了就開始有人好心地批判我的程式“C語言痕跡過重”……
後來隨著學習的深入,總算大概明白了cout/cin/cerr/...的鬼把戲:那些東東不過是變著法兒“哄人”,其實說到底還是
函式調用,不過這函式有些特殊,用的是
運算符重載,確切地說(以下還是以cout為例)是
重載了“<<”運算符。我們就讓它現出函式的本來面目,請看HelloWorld!的等效版本:
#include<iostream>using namespace std;int main(){ cout.operator<<("Hello,World!");cout.operator<<(endl); return 0;}
編譯運行,結果與經典版無二。上面程式應該更容易理解了:cout是一個ostream類的對象,它有一個成員運算符函式operator<<,每次調用的時候就會向
輸出設備(一般就是螢幕啦)輸出東東。嗯,這裡有一個問題:為什麼函式operator<<能夠接受不同類型的數據,如
整型、
浮點型、字元串甚至
指針,等等呢?
我想你已經猜到了,沒錯,就是用
運算符重載。
運算符函式與
一般函式基本無異,可以任意
重載。標準庫的設計者們早已經為我們定製了iostream::operator<<對於各種C++基本數據類型的重載版本,這才使得我們這些初學者們一上來就享受到cout<<"Hello,World!"<<endl;。
cout.operator<<("Hello,World!").operator<<(endl);
才算“強等效”。究竟可不可以這樣寫?向
編譯器確認一下……OK,NoProblem!
還有為什麼可以連續寫多個呢?請見如下的定義:
ostream& std::cout.operator<<();
注意前面的ostream&表示返回對象的引用,也就是可以繼續cout了
對於cin,則是istream流類的對象,其重載了>>運算符,用法與cout大致相同
技巧套用
嗯,我們已經基本上看出了cout的實質,不妨動動手,自己來實現一個cout的簡化版(Lite),為了區分,我們把我們設計的cout對象命名的myout,myout對象所屬的類為MyOutstream。我們要做的就是為MyOutstream類
重載一系列不同類型的operator<<運算符函式,簡單起見,這裡我們僅實現了對
整型(int)與字元串型(char*)的重載。為了表示與iostream斷絕關係,我們不再用頭檔案iostream,而使用古老的stdio中的printf函式進行輸出,程式很簡單,包括完整的
main函式,均列如下:
#include <cstdio> // 在C和一些古老的C++中是stdio.h,新標準為了使標準庫 // 的頭檔案與用戶頭檔案區別開,均推薦使用不用擴展名 // 的版本,對於原有C庫,不用擴展名時頭檔案名稱前面要加cclass MyOutstream{ public: const MyOutstream& operator << (int value)const;//對整型變數的重載 const MyOutstream& operator << (char* str)const;//對字元串型的重載};const MyOutstream& MyOutstream::operator <<(int value)const{ printf("%d",value); return* this;//注意這個返回……}const MyOutstream& MyOutstream::operator <<(char* str)const{ printf("%s",str); return* this;//同樣,這裡也留意一下……}MyOutstream myout;//隨時隨地為我們服務的全局對象myoutint main(){ int a=2003; char* myStr="Hello,World!"; myout << myStr << "\n"; return 0;}
我們定義的myout已經初具形態,可以為我們工作了。程式中的注釋指出兩處要我們特別注意的:即是operator<<函式執行完畢之後,總是返回一個它本身的引用,輸出已經完成,為何還要多此一舉?
還記得那個有點奇異的cout.operator<<("Hello,World!").operator<<(endl)么?它能實現意味著我們可以連著書寫
cout<<"Hello,World!"<<endl;
而不是
cout<<"Hello,World!";cout<<endl;
為何它可以這樣連起來寫?我們分析一下:按執行順序,系統首先調用cout.operator<<("Hello,World!"),然後呢?然後cout.operator<<會返回它本身,就是說在函式的最後一行會出現類似於return *this這樣的語句,因此cout.operator<<("Hello,World!")的調用結果就返回了cout,接著它後面又緊跟著.operator<<(endl),這相當於cout.operator<<(endl)——於是又會進行下一個輸出,如果往下還有很多<<算符,調用就會一直進行……哇噢,是不是很聰明?現在你明白我們的MyOutstream::operator<<最後一行的奧妙了吧!
我們知道,最後出現的"\n"可以實現一個換行,不過我們在用C++時教程中總是有意無意地讓我們使用endl,兩者看上去似乎一樣——究竟其中有什麼玄妙?查書,書上說endl是一個操縱符(manipulator),它不但實現了換行操作,而且還對輸出
緩衝區進行刷新。什麼意思呢?原來在執行輸出操作之後,數據並非立刻傳到
輸出設備,而是先進入一個緩衝區,當適宜的時機(如設備空閒)後再由緩衝區傳入,也可以通過操縱符flush,ends,或unitbuf進行強制刷新:
cout<<"Hello,World!"<<"Flush the screen now!!!"<<flush;
這樣當程式執行到operator<<(flush)之前,有可能前面的字元串數據還在
緩衝區中而不是顯示在螢幕上,但執行operator<<(flush)之後,程式會強制把緩衝區的數據全部搬運到輸出設備並將其清空。而操縱符endl相當於<<"\n"<<flush;
不過可能在螢幕上顯示是手動刷新與否區別看來都不大。但對於檔案等輸出對象就不大一樣了:過於頻繁的刷新意味著老是寫盤,會影響速度。因此通常是寫入一定的位元組數後再刷新,如何操作?靠的就是這些操縱符。
cout控制符
要使用下面的控制符,你需要在相應的源檔案中包含頭檔案“iomanip”,也就是添加如下代碼:
控制符 | 描 述 |
---|
dec | 置基數為10,後由十進制輸出(系統默認形式) |
hex | 置基數為16,後由十六進制輸出 |
oct | 置基數為8,後由八進制輸出 |
setfill(c) | 設填充字元為c |
setprecision(n) | 設定實數的精度為n位 |
setw(n) | |
setiosflags(ios::fixed) | 固定的浮點顯示 |
setiosflags(ios::scientific) | 指數表示 |
setiosflags(ios::left) | |
setiosflags(ios::right) | |
setiosflags(ios::skipws) | 忽略前導空白 |
setiosflags(ios::uppercase) | 16進制數大寫輸出 |
setiosflags(ios::lowercase) | 16進制數小寫輸出 |
其中:setw設定域寬,使用一次就得設定一次。其他的函式,設定一次永久有效。
cout的相關信息
1 cout的類型是 iostream
2 ostream使用了單例模式,
保護的構造函式,不能在類外創建另一個對象(用 ostream os 測試)
拷貝構造私有,不能通過已有對象,構造新對象(用 ostream os(cout) 測試)
拷貝賦值私有,(用 cout=cout 測試)
3 cout在命名空間std中,使用前需要using namespace std,或者std::cout
4 可以使用引用,或指針指向這個對象,意思想說,想用ostream 做一個函式的形式參數,就必須使用引用或指針。因為實參肯定是cout,且只能有這一個對象。
5 cout<<對象; 對象的類型用OO表示,如想用cout列印一個對象,即cout<<對象,可使用如下程式
friend ostream& operator<< (ostream& os,const OO& c) {//為什麼必須使用友元return os << c.成員1 <<" : "<<c.成員2;}
運算符重載
用法:把成員函式/友元函式的名字改為 operator運算符 就行了
調用的時候這么調用
例如:
class Obj{public:void operator--(int s){cout <<s;}}int main(void){ Obj o;o--4;//此時列印出一個4來return 0;}
其他信息
C++的iostream家族
好了,說了這么多,C++的iostream家族與C的printf/scanf/gets/puts/getchar/putchar家庭相比究竟有何優勢?首先是類型處理更安全、智慧型,想想printf中對付int、float等的"%d"、"%f"等說明符真是多餘且麻煩,萬一用錯了搞不好還會死掉;其次是擴展性更強:我要是新定義一個複數類Complex,printf對其是無能為力,最多只能分別輸出實、虛部,而iostream使用的<<、>>操作符都是可
重載的,你只要重載相關的
運算符就可以了;而且流風格的寫法也比較自然簡潔,不是么?
但是,iostream也有缺點:他們的速度比prints/scanf等函式慢得多(畢竟得檢測類型),而且如果你要進行一些特殊操作(如保留小數點後n位),printf比cout方便得多。