1. 程式人生 > >C++的cin/cout高階格式化操作

C++的cin/cout高階格式化操作

這篇文章主要講解如何在C++中使用cin/cout進行高階的格式化輸出操作,包括數字的各種計數法(精度)輸出,左或右對齊,大小寫等等。通過本文,您可以完全脫離scanf/printf,僅使用cin/cout來完成一切需要的格式化輸入輸出功能(從非效能的角度而言)。更進一步而言,您還可以在、上使用這些格式化操作,從而代替sscanf/sprintf和fscanf/fprintf函式。為方便描述,下文僅以cin/cout為例進行介紹。

一、綜述

cin/cout是STL庫提供的一個iostream例項,擁有ios_base基類的全部函式和成員資料。進行格式化操作可以直接利用setf/unsetf函式和flags函式。cin/cout維護一個當前的格式狀態,setf/unsetf函式是在當前的格式狀態上追加或刪除指定的格式,而flags則是將當前格式狀態全部替換為指定的格式。cin/cout為這兩個函式提供瞭如下引數(可選格式):

ios::dec 以10進製表示整數

ios::hex 以16進製表示整數

ios::oct 以8進製表示整數

ios::showbase 為整數新增一個表示其進位制的字首

ios::internal 在符號位和數值的中間插入需要數量的填充字元以使串兩端對齊

ios::left 在串的末尾插入填充字元以使串居左對齊

ios::right 在串的前面插入填充字元以使串居右對齊

ios::boolalpha 將bool型別的值以true或flase表示,而不是1或0

ios::fixed 將符點數按照普通定點格式處理(非科學計數法)

ios::scientific 將符點數按照科學計數法處理(帶指數域)

ios::showpoint 在浮點數表示的小數中強制插入小數點(預設情況是浮點數表示的整數不顯示小數點)

ios::showpos 強制在正數前新增+號

ios::skipws 忽略前導的空格(主要用於輸入流,如cin)

ios::unitbuf 在插入(每次輸出)操作後清空快取

ios::uppercase 強制大寫字母

以上每一種格式都佔用獨立的一位,因此可以用“|”(位或)運算子組合使用。呼叫setf/unsetf或flags設定格式一般按如下方式進行:

1 cout.setf(ios::right | ios::hex); //設定16進位制右對齊

2 cout.setf(ios::right, ios::adjustfield); //取消其它對齊,設定為右對齊

setf可接受一個或兩個引數,一個引數的版本為設定指定的格式,兩個引數的版本中,後一個引數指定了刪除的格式。三個已定義的組合格式為:

ios::adjustfield 對齊格式的組合位

ios::basefield 進位制的組合位

ios::floatfield 浮點表示方式的組合位

設定格式之後,下面所有使用cout進行的輸出都會按照指定的格式狀態執行。但是如果在一次輸出過程中需要混雜多種格式,使用cout的成員函式來處理就顯得很不方便了。STL另提供了一套庫可以滿足這種使用方式。庫中將每一種格式的設定和刪除都進行了函式級的同名封裝,比如fixed函式,就可以將一個ostream的物件作為引數,在內部呼叫setf函式對其設定ios::fixed格式後再返回原物件。此外還提供了setiosflags、setbase、setfill、setw、setprecision等方便的格式控制函式,下文會逐一進行介紹。大多數示例程式碼都會使用到,因此預設包含的標頭檔案均為:

1 #include

2 #include

二、縮排

將輸出內容按指定的寬度對齊,需要用到ios::right、ios::left、ios::internal和iomanip裡的setw。其中setw用於指定要輸出內容的對齊寬度。以下兩段程式碼的結果完全相同,前面是一個浮點數-456.98,後面緊跟著一個字串“The End”以及換行符“endl”。

程式碼一:

01 #include

02 #include

03 using namespace std;

04 int main(void) {

05 cout.flags(ios::left); //左對齊

06 cout << setw(10) << -456.98 << “The End” << endl;

07 cout.flags(ios::internal); //兩端對齊

08 cout << setw(10) << -456.98 << “The End” << endl;

09 cout.flags(ios::right); //右對齊

10 cout << setw(10) << -456.98 << “The End” << endl;

11 return 0;

12 }

程式碼二:

1 #include

2 #include

3 using namespace std;

4 int main(void) {

5 cout << left << setw(10) << -456.98 << “The End” << endl; //左對齊

6 cout << internal << setw(10) << -456.98 << “The End” << endl; //兩端對齊

7 cout << right << setw(10) << -456.98 << “The End” << endl; //右對齊

8 return 0;

9 }

結果:

-456.98 The End

- 456.98The End

-456.98The End

這裡要額外說明的一點是,setw函式會用當前的填充字元控制對齊位置,預設的填充字元是空格。可以通過的setfill來設定填充字元,比如下面的程式碼用字元“0”作為填充字元:

1 #include

2 #include

3 using namespace std;

4 int main(void) {

5 cout << setfill(‘0’) << setw(10) << 45698 << endl;

6 return 0;

7 }

結果:

0000045698

三、整數

輸出整數的格式有按不同進位制數出:ios::hex(16進位制)、ios::dec(10進位制)、ios::oct(8進位制),也可強制其輸出符號(正數也加上“+”號字首),對於16進位制的輸出還可配合ios::uppercase使所有字母以大寫表示。程式碼示例如下:

01 #include

02 #include

03 using namespace std;

04 int main(void) {

05 cout.setf(ios::showpos | ios::uppercase);

06 cout << hex << setw(4) << 12 << setw(12) << -12 << endl;

07 cout << dec << setw(4) << 12 << setw(12) << -12 << endl;

08 cout << oct << setw(4) << 12 << setw(12) << -12 << endl;

09 cout.unsetf(ios::showpos | ios::uppercase);

10 cout << hex << setw(4) << 12 << setw(12) << -12 << endl;

11 cout << dec << setw(4) << 12 << setw(12) << -12 << endl;

12 cout << oct << setw(4) << 12 << setw(12) << -12 << endl;

13 return 0;

14 }

結果:

C FFFFFFF4

+12 -12

14 37777777764

c fffffff4

12 -12

14 37777777764

利用的setbase函式同樣可以設定整數的三種進位制,引數分別為8、10和16,但使用起來比上面的方法還更復雜一些,除非是特殊的程式碼規範要求(有些規範要求避免將常量直接作為表示式),一般不建議使用setbase。此外,還可以利用ios::showbase來為整數的前面加一個表示進位制的字首,程式碼如下:

1 #include

2 #include

3 using namespace std;

4 int main(void) {

5 cout << showbase << setw(4) << hex << 32 << setw(4) << oct << 32 << endl;

6 cout << noshowbase << setw(4) << hex << 32 << setw(4) << oct << 32 << endl;

7 return 0;

8 }

結果:

0x20 040

20 40

上面程式碼中的showbase/noshobase也可以用cout的setf來代替,其結果是完全相同的:

01 #include

02 #include

03 using namespace std;

04 int main(void) {

05 cout.setf(ios::showbase);

06 cout << setw(4) << hex << 32 << setw(4) << oct << 32 << endl;

07 cout.unsetf(ios::showbase);

08 cout << setw(4) << hex << 32 << setw(4) << oct << 32 << endl;

09 return 0;

10 }

四、小數

小數可分為兩種格式型別,一種是定點表示“ios::fixed”(不帶指數域),另一種是科學計數法表示“ios::scientific”(帶指數域)。與的setprecision配合使用,可以表示指定小數點後面的保留位數(四捨五入)。示例程式碼如下:

01 #include

02 #include

03 using namespace std;

04 int main(void) {

05 cout.setf(ios::fixed);

06 cout << setprecision(0) << 12.05 << endl;

07 cout << setprecision(1) << 12.05 << endl;

08 cout << setprecision(2) << 12.05 << endl;

09 cout << setprecision(3) << 12.05 << endl;

10 cout.setf(ios::scientific, ios::floatfield);

11 cout << setprecision(0) << 12.05 << endl;

12 cout << setprecision(1) << 12.05 << endl;

13 cout << setprecision(2) << 12.05 << endl;

14 cout << setprecision(3) << 12.05 << endl;

15 return 0;

16 }

結果:

12

12.1

12.05

12.050

1.205000e+001

1.2e+001

1.21e+001

1.205e+001

需要注意的是,有時會因為機器的精度問題導致四捨五入的結果不正確。這種問題一般需要手動修正,見如下程式碼示例:

1 #include

2 #include

3 using namespace std;

4 int main(void) {

5 cout << fixed << setprecision(1) << 2.05 << endl;

6 cout << fixed << setprecision(1) << 2.05 + 1e-8 << endl;

7 return 0;

8 }

結果:

2.0

2.1

四、字串

字串的輸出處理主要是對齊,這一點在第二部分已經介紹過了,下面主要介紹字串的輸入方法。為了方便起見,我們使用庫。在輸入字串時,可以利用庫提供的getline函式讀取整行資料。getline函式有兩個版本,第一個版本有兩個引數,第一個引數指定輸入流(比如cin),第二個引數指定一個string物件。getline會讀取螢幕上輸入的字元,直到遇到換行符“\n”為止;第二個版本有三個引數,前兩個與第一個版本相同,第三個引數為指定的結束字元。注意,getline不會讀入預設或指定的結束字元,但在呼叫之後讀取的位置已經跳過結束字元。呼叫示例程式碼如下:

01 #include

02 #include

03 #include

04 using namespace std;

05 int main(void) {

06 string str1, str2;

07 getline(cin, str1);

08 cin >> str2;

09 cout << str1 << endl << str2 << endl;

10 return 0;

11 }

輸入:

abc

abc

結果:

abc

abc

五、緩衝區

由於呼叫系統函式在螢幕上逐個顯示字元是很慢的,因此cin/cout為了加快速度使用緩衝區技術,粗略的講就是暫時不輸出指定的字元,而是存放在緩衝區中,在合適的時機一次性輸出到螢幕上。如果單純使用C++的輸入/輸出流來操作字元是不存在同步的問題的,但是如果要和C標準庫的stdio庫函式混合使用就必須要小心的處理緩衝區了。如果要與scanf和printf聯合使用,務必在呼叫cout前加上cout.sync_with_stdio(),設定與stdio同步,否則輸出的資料順序會發生混亂。

flush和endl都會將當前緩衝區中的內容立即寫入到螢幕上,而unitbuf/nounitbuf可以禁止或啟用緩衝區。示例程式碼如下:

1 #include

2 #include

3 using namespace std;

4 int main(void) {

5 cout << 123 << flush << 456 << endl;

6 cout << unitbuf << 123 << nounitbuf << 456 << endl;

7 return 0;

8 }

結果:

123456

123456

六、綜合使用

示例程式碼:

01 #include

02 #include

03 #include

04 using namespace std;

05 struct COMMODITY { string Name; int Id; int Cnt; double Price; };

06 int main(void) {

07 COMMODITY cmd[] = {

08 {“Fruit”, 0x101, 50, 5.268},

09 {“Juice”, 0x102, 20, 8.729},

10 {“Meat”, 0x104, 30, 10.133},

11 };

12 cout << left << setw(8) << “NAME” << right << setw(8) << “ID”;

13 cout << right << setw(8) << “COUNT” << right << setw(8) << “PRICE” << endl;

14 for (int i = 0; i < sizeof(cmd) / sizeof(cmd[0]); ++i) {

15 cout << left << setw(8) << cmd[i].Name;

16 cout << right << hex << showbase << setw(8) << cmd[i].Id;

17 cout << dec << noshowbase << setw(8) << cmd[i].Cnt;

18 cout << fixed << setw(8) << setprecision(2) << cmd[i].Price << endl;

19 }

20 return 0;

21 }

結果:

NAME ID COUNT PRICE

Fruit 0x101 50 5.27

Juice 0x102 20 8.73

Meat 0x104 30 10.13