Linux中國

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 類是一個模板類,它對常見的字元類型進行了 模板特化 specialization ,稱為 ios。因此,當你在標準 I/O 的上下文中讀到 ios 時,它是 basic_ioschar 類型的模板特化。

格式化流

一般來說,基於 ostream 的流有三種格式化的方法。

  1. 使用 ios_base 提供的格式標誌。
  2. 在頭文件 <iomanip><ios> 中定義的流修改函數。
  3. 通過調用 << 操作符的 特定重載

所有這些方法都有其優點和缺點,通常取決於使用哪種方法的情況。下面顯示的例子混合使用所有這些方法。

右對齊

默認情況下,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(&apos;.&apos;) << setw(30) << 500 << " pcs" << endl;
cout << right << setfill(&apos;.&apos;) << setw(30) << 3000 << " pcs" << endl;
cout << right << setfill(&apos;.&apos;) << setw(30) << 24500 << " pcs" << endl;

代碼輸出如下:

...........................500 pcs
..........................3000 pcs
.........................24500 pcs

組合

想像一下,你的 C++ 程序記錄了你的儲藏室庫存。不時地,你想列印一份當前庫存的清單。要做到這一點,你可以使用以下格式。

下面的代碼是左對齊和右對齊輸出的組合,使用點作為填充字元,可以得到一個漂亮的列表:

cout << left << setfill(&apos;.&apos;) << setw(20) << "Flour" << right << setfill(&apos;.&apos;) << setw(20) << 0.7 << " kg" << endl;
cout << left << setfill(&apos;.&apos;) << setw(20) << "Honey" << right << setfill(&apos;.&apos;) << setw(20) << 2 << " Glasses" << endl;
cout << left << setfill(&apos;.&apos;) << setw(20) << "Noodles" << right << setfill(&apos;.&apos;) << setw(20) << 800 << " g" << endl;
cout << left << setfill(&apos;.&apos;) << setw(20) << "Beer" << right << setfill(&apos;.&apos;) << 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 位的可執行文件產生了上述輸出。

整數

列印整數是很簡單的。為了演示,我使用 setfsetiosflags 來指定數字的基數。應用流修改器 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(&apos;0&apos;) << setw(7) << 3 << endl;
cout << setfill(&apos;0&apos;) << setw(7) << 35 << endl;
cout << setfill(&apos;0&apos;) << setw(7) << 357 << endl;
cout << setfill(&apos;0&apos;) << 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;

iosimbue 方法讓你指定一個地區。通過命令 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(&apos;!&apos;);
    }
    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++ 應用程序中,coutprintf 的新鄰居。雖然使用 printf 仍然有效,但我可能總是喜歡使用 cout。特別是與定義在 <ios> 中的修改函數相結合,會產生漂亮的、可讀的代碼。

via: https://opensource.com/article/21/11/c-stdcout-cheat-sheet

作者:Stephan Avenwedde 選題:lujun9972 譯者:wxy 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國