C++ 控制台格式化列印技巧
我寫文章主要是為了給自己寫文檔。我在編程時非常健忘,所以我經常會寫下有用的代碼片段、特殊的特性,以及我使用的編程語言中的常見錯誤。這篇文章完全切合我最初的想法,因為它涵蓋了從 C++ 控制台格式化列印時的常見用例。
像往常一樣,這篇文章帶有大量的例子。除非另有說明,代碼片段中顯示的所有類型和類都是 std
命名空間的一部分。所以當你閱讀這段代碼時,你必須在類型和類的前面加上using namespace std;
。當然,該示例代碼也可以在 GitHub 上找到。
面向對象的流
如果你曾經用過 C++ 編程,你肯定使用過 cout。當你包含 <iostream>
時,ostream 類型的 cout
對象就進入了作用域。這篇文章的重點是 cout
,它可以讓你列印到控制台,但這裡描述的一般格式化對所有 ostream 類型的流對象都有效。ostream
對象是 basic_ostream
的一個實例,其模板參數為 char
類型。頭文件 <iosfwd>
是 <iostream>
的包含層次結構的一部分,包含了常見類型的前向聲明。
類 basic_ostream
繼承於 basic_ios
,該類型又繼承於 ios_base
。在 cppreference.com 上你可以找到一個顯示不同類之間關係的類圖。
ios_base
類是所有 I/O 流類的基類。basic_ios
類是一個模板類,它對常見的字元類型進行了 模板特化 ,稱為 ios
。因此,當你在標準 I/O 的上下文中讀到 ios
時,它是 basic_ios
的 char
類型的模板特化。
格式化流
一般來說,基於 ostream
的流有三種格式化的方法。
- 使用
ios_base
提供的格式標誌。 - 在頭文件
<iomanip>
和<ios>
中定義的流修改函數。 - 通過調用
<<
操作符的 特定重載。
所有這些方法都有其優點和缺點,通常取決於使用哪種方法的情況。下面顯示的例子混合使用所有這些方法。
右對齊
默認情況下,cout
佔用的空間與要列印的數據所需的空間一樣大。為了使這種右對齊的輸出生效,你必須定義一個行允許佔用的最大寬度。我使用格式標誌來達到這個目的。
右對齊輸出的標誌和寬度調整隻適用於其後的行。
cout.setf(ios::right, ios::adjustfield);
cout.width(50);
cout << "This text is right justified" << endl;
cout << "This text is left justified again" << endl;
在上面的代碼中,我使用 setf
配置了右對齊的輸出。我建議你將位掩碼 ios::adjustfield
應用於 setf
,這將使位掩碼指定的所有標誌在實際的 ios::right
標誌被設置之前被重置,以防止發生組合碰撞。
填充空白
當使用右對齊輸出時,默認情況下,空的地方會用空白字元填充。你可以通過使用 setfill
指定填充字元來改變它:
cout << right << setfill('.') << setw(30) << 500 << " pcs" << endl;
cout << right << setfill('.') << setw(30) << 3000 << " pcs" << endl;
cout << right << setfill('.') << setw(30) << 24500 << " pcs" << endl;
代碼輸出如下:
...........................500 pcs
..........................3000 pcs
.........................24500 pcs
組合
想像一下,你的 C++ 程序記錄了你的儲藏室庫存。不時地,你想列印一份當前庫存的清單。要做到這一點,你可以使用以下格式。
下面的代碼是左對齊和右對齊輸出的組合,使用點作為填充字元,可以得到一個漂亮的列表:
cout << left << setfill('.') << setw(20) << "Flour" << right << setfill('.') << setw(20) << 0.7 << " kg" << endl;
cout << left << setfill('.') << setw(20) << "Honey" << right << setfill('.') << setw(20) << 2 << " Glasses" << endl;
cout << left << setfill('.') << setw(20) << "Noodles" << right << setfill('.') << setw(20) << 800 << " g" << endl;
cout << left << setfill('.') << setw(20) << "Beer" << right << setfill('.') << setw(20) << 20 << " Bottles" << endl;
輸出:
Flour...............................0.70 kg
Honey..................................2 Glasses
Noodles..............................800 g
Beer..................................20 Bottles
列印數值
當然,基於流的輸出也能讓你輸出各種變數類型。
布爾型
boolalpha
開關可以讓你把布爾型的二進位解釋轉換為字元串:
cout << "Boolean output without using boolalpha: " << true << " / " << false << endl;
cout << "Boolean output using boolalpha: " << boolalpha << true << " / " << false << endl;
以上幾行產生的輸出結果如下:
Boolean output without using boolalpha: 1 / 0
Boolean output using boolalpha: true / false
地址
如果一個整數的值應該被看作是一個地址,那麼只需要把它投到 void*
就可以了,以便調用正確的重載。下面是一個例子:
unsigned long someAddress = 0x0000ABCD;
cout << "Treat as unsigned long: " << someAddress << endl;
cout << "Treat as address: " << (void*)someAddress << endl;
該代碼產生了以下輸出:
Treat as unsigned long: 43981
Treat as address: 0000ABCD
該代碼列印出了具有正確長度的地址。一個 32 位的可執行文件產生了上述輸出。
整數
列印整數是很簡單的。為了演示,我使用 setf
和 setiosflags
來指定數字的基數。應用流修改器 hex
/oct
也有同樣的效果。
int myInt = 123;
cout << "Decimal: " << myInt << endl;
cout.setf(ios::hex, ios::basefield);
cout << "Hexadecimal: " << myInt << endl;
cout << "Octal: " << resetiosflags(ios::basefield) << setiosflags(ios::oct) << myInt << endl;
注意: 默認情況下,沒有指示所使用的基數,但你可以使用 showbase
添加一個。
Decimal: 123
Hexadecimal: 7b
Octal: 173
用零填充
0000003
0000035
0000357
0003579
你可以通過指定寬度和填充字元得到類似上述的輸出:
cout << setfill('0') << setw(7) << 3 << endl;
cout << setfill('0') << setw(7) << 35 << endl;
cout << setfill('0') << setw(7) << 357 << endl;
cout << setfill('0') << setw(7) << 3579 << endl;
浮點值
如果我想列印浮點數值,我可以選擇「固定」和「科學」格式。此外,我還可以指定精度:
double myFloat = 1234.123456789012345;
int defaultPrecision = cout.precision(); // == 2
cout << "Default precision: " << myFloat << endl;
cout.precision(4);
cout << "Modified precision: " << myFloat << endl;
cout.setf(ios::scientific, ios::floatfield);
cout << "Modified precision & scientific format: " << myFloat << endl;
/* back to default */
cout.precision(defaultPrecision);
cout.setf(ios::fixed, ios::floatfield);
cout << "Default precision & fixed format: " << myFloat << endl;
上面的代碼產生以下輸出:
Default precision: 1234.12
Modified precision: 1234.1235
Modified precision & scientific format: 1.2341e+03
Default precision & fixed format: 1234.12
時間和金錢
通過 put_money
,你可以用正確的、與當地有關的格式來列印貨幣單位。這需要你的控制台能夠輸出 UTF-8 字符集。請注意,變數 specialOffering
以美分為單位存儲貨幣價值。
long double specialOffering = 9995;
cout.imbue(locale("en_US.UTF-8"));
cout << showbase << put_money(specialOffering) << endl;
cout.imbue(locale("de_DE.UTF-8"));
cout << showbase << put_money(specialOffering) << endl;
cout.imbue(locale("ru_RU.UTF-8"));
cout << showbase << put_money(specialOffering) << endl;
ios
的 imbue
方法讓你指定一個地區。通過命令 locale -a
,你可以得到你系統中所有可用的地區標識符的列表。
$99.95
99,950€
99,950₽
(不知道出於什麼原因,在我的系統上,它列印的歐元和盧布有三個小數位,對我來說看起來很奇怪,但這也許是官方的格式。)
同樣的原則也適用於時間輸出。函數 put_time
可以讓你以相應的地區格式列印時間。此外,你可以指定時間對象的哪些部分被列印出來。
time_t now = time(nullptr);
tm localtm = *localtime(&now);
cout.imbue(locale("en_US.UTF-8"));
cout << "en_US : " << put_time(&localtm, "%c") << endl;
cout.imbue(locale("de_DE.UTF-8"));
cout << "de_DE : " << put_time(&localtm, "%c") << endl;
cout.imbue(locale("ru_RU.UTF-8"));
cout << "ru_RU : " << put_time(&localtm, "%c") << endl;
格式指定符 %c
會列印一個標準的日期和時間字元串:
en_US : Tue 02 Nov 2021 07:36:36 AM CET
de_DE : Di 02 Nov 2021 07:36:36 CET
ru_RU : Вт 02 ноя 2021 07:36:36
創建自定義的流修改器
你也可以創建你自己的流。下面的代碼在應用於 ostream
對象時插入了一個預定義的字元串:
ostream& myManipulator(ostream& os) {
string myStr = ">>>Here I am<<<";
os << myStr;
return os;
}
另一個例子: 如果你有重要的事情要說,就像互聯網上的大多數人一樣,你可以使用下面的代碼在你的信息後面根據重要程度插入感嘆號。重要程度被作為一個參數傳遞:
struct T_Importance {
int levelOfSignificance;
};
T_Importance importance(int lvl){
T_Importance x = {.levelOfSignificance = lvl };
return x;
}
ostream& operator<<(ostream& __os, T_Importance t){
for(int i = 0; i < t.levelOfSignificance; ++i){
__os.put('!');
}
return __os;
}
這兩個修飾符現在都可以簡單地傳遞給 cout
:
cout << "My custom manipulator: " << myManipulator << endl;
cout << "I have something important to say" << importance(5) << endl;
產生以下輸出:
My custom manipulator: >>>Here I am<<<
I have something important to say!!!!!
結語
下次你再糾結於控制台輸出格式時,我希望你記得這篇文章及其 速查表。
在 C++ 應用程序中,cout
是 printf 的新鄰居。雖然使用 printf
仍然有效,但我可能總是喜歡使用 cout
。特別是與定義在 <ios>
中的修改函數相結合,會產生漂亮的、可讀的代碼。
via: https://opensource.com/article/21/11/c-stdcout-cheat-sheet
作者:Stephan Avenwedde 選題:lujun9972 譯者:wxy 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive