跳轉到內容

C++ 程式設計/程式碼/IO

來自 Wikibooks,開放書籍,開放世界

也常被稱為 C++ 標準庫 的 C++ I/O,因為庫還包括 C 標準庫及其 I/O 實現,如之前在 標準 C I/O 部分 所示。

輸入輸出 對任何計算機軟體都是必不可少的,因為它們是程式與使用者通訊的唯一途徑。最簡單的輸入/輸出形式是純文字的,即應用程式以控制檯形式顯示,使用簡單的 ASCII 字元提示使用者輸入,使用者使用鍵盤提供輸入。

程式可以透過多種方式獲得輸入和輸出,包括

  • 檔案 I/O,即讀寫檔案
  • 控制檯 I/O,讀寫控制檯視窗,如基於 UNIX 的作業系統中的終端或 Windows 中的 DOS 提示符。
  • 網路 I/O,從網路裝置讀寫
  • 字串 I/O,將字串視為輸入或輸出裝置進行讀寫

雖然這些看起來可能不相關,但它們的工作方式非常相似。實際上,遵循 POSIX 規範的作業系統使用一種型別的控制代碼來處理檔案、裝置、網路套接字、控制檯以及許多其他東西,即檔案描述符。但是,作業系統提供的低階介面往往難以使用,因此 C++ 與其他語言一樣,提供了抽象來簡化程式設計。這種抽象是

字元編碼

[編輯 | 編輯原始碼]
Clipboard

待辦事項
完成字元編碼資訊


美國資訊交換標準程式碼 (ASCII) 95 圖表

[編輯 | 編輯原始碼]

ASCII 是一種基於 英語字母字元編碼方案。從 0x20 到 0x7E(十進位制 32 到 126)編號的 95 個 ASCII 圖形字元,也稱為可列印字元,代表字母、數字、標點符號 以及一些雜項符號。前 32 個 ASCII 字元,從 0x00 到 0x20,被稱為 控制字元空格字元 表示單詞之間的空格,由鍵盤的空格鍵產生,用程式碼 0x20(十六進位制)表示,被認為是非列印圖形(或不可見圖形)而不是控制字元。

二進位制 八進位制 十進位制 十六進位制 字形
010 0000 040 32 20 空格
010 0001 041 33 21 !
010 0010 042 34 22 "
010 0011 043 35 23 #
010 0100 044 36 24 $
010 0101 045 37 25 %
010 0110 046 38 26 &
010 0111 047 39 27 '
010 1000 050 40 28 (
010 1001 051 41 29 )
010 1010 052 42 2A *
010 1011 053 43 2B +
010 1100 054 44 2C ,
010 1101 055 45 2D -
010 1110 056 46 2E .
010 1111 057 47 2F /
011 0000 060 48 30 0
011 0001 061 49 31 1
011 0010 062 50 32 2
011 0011 063 51 33 3
011 0100 064 52 34 4
011 0101 065 53 35 5
011 0110 066 54 36 6
011 0111 067 55 37 7
011 1000 070 56 38 8
011 1001 071 57 39 9
011 1010 072 58 3A :
011 1011 073 59 3B ;
011 1100 074 60 3C <
011 1101 075 61 3D =
011 1110 076 62 3E >
011 1111 077 63 3F ?
二進位制 八進位制 十進位制 十六進位制 字形
100 0000 100 64 40 @
100 0001 101 65 41 A
100 0010 102 66 42 B
100 0011 103 67 43 C
100 0100 104 68 44 D
100 0101 105 69 45 E
100 0110 106 70 46 F
100 0111 107 71 47 G
100 1000 110 72 48 H
100 1001 111 73 49 I
100 1010 112 74 4A J
100 1011 113 75 4B K
100 1100 114 76 4C L
100 1101 115 77 4D M
100 1110 116 78 4E N
100 1111 117 79 4F O
101 0000 120 80 50 P
101 0001 121 81 51 Q
101 0010 122 82 52 R
101 0011 123 83 53 S
101 0100 124 84 54 T
101 0101 125 85 55 U
101 0110 126 86 56 V
101 0111 127 87 57 W
101 1000 130 88 58 X
101 1001 131 89 59 Y
101 1010 132 90 5A Z
101 1011 133 91 5B [
101 1100 134 92 5C \
101 1101 135 93 5D ]
101 1110 136 94 5E ^
101 1111 137 95 5F _
二進位制 八進位制 十進位制 十六進位制 字形
110 0000 140 96 60 `
110 0001 141 97 61 a
110 0010 142 98 62 b
110 0011 143 99 63 c
110 0100 144 100 64 d
110 0101 145 101 65 e
110 0110 146 102 66 f
110 0111 147 103 67 g
110 1000 150 104 68 h
110 1001 151 105 69 i
110 1010 152 106 6A j
110 1011 153 107 6B k
110 1100 154 108 6C l
110 1101 155 109 6D m
110 1110 156 110 6E n
110 1111 157 111 6F o
111 0000 160 112 70 p
111 0001 161 113 71 q
111 0010 162 114 72 r
111 0011 163 115 73 s
111 0100 164 116 74 t
111 0101 165 117 75 u
111 0110 166 118 76 v
111 0111 167 119 77 w
111 1000 170 120 78 x
111 1001 171 121 79 y
111 1010 172 122 7A z
111 1011 173 123 7B {
111 1100 174 124 7C |
111 1101 175 125 7D }
111 1110 176 126 7E ~


流是一種物件型別,我們可以從中獲取值,或者可以向其傳遞值。這是在底層程式碼方面透明完成的,底層程式碼演示了使用std::cout流,稱為 標準輸出流

// 'Hello World!' program 
 
#include <iostream>
 
int main()
{
  std::cout << "Hello World!" << std::endl;
  return 0;
}


幾乎所有輸入和輸出都可以非常有效地建模為流。擁有一個通用模型意味著只需要學習一次。如果你瞭解流,你就瞭解瞭如何輸出到檔案、螢幕、套接字、管道以及任何其他可能出現的東西的基礎知識。

流是一個物件,允許我們按順序將資料推入或推出介質。通常,流只能輸出或只能輸入。可以有一個既能輸出又能輸入的流,但這很少見。我們可以將流想象成一輛汽車沿著資訊的一條單行道行駛。輸出流可以插入資料並繼續前進。它(通常)不能返回並調整已經寫入的內容。同樣,輸入流可以讀取下一位資料,然後等待接下來的資料。它不會跳過資料,也不會倒帶檢視五分鐘前讀取的內容。

流的讀寫操作的語義取決於流的型別。在檔案的情況下,輸入檔案流按順序讀取檔案的內容,不會倒帶,輸出檔案流按順序寫入檔案。對於控制檯流,輸出表示顯示文字,輸入表示透過控制檯從使用者那裡獲取輸入。如果使用者沒有輸入任何內容,則程式會 阻塞 或等待使用者輸入內容。


Clipboard

待辦事項
請記住使用字串流類來完成幷包含各種 io 流格式標誌。


使用 iostream 將輸出儲存到檔案的 c++ 程式

iostream 是用於輸入/輸出的 標頭檔案。它是 C++ 標準庫的一部分。該名稱代表 Input/Output Stream。在 C++ 中,沒有用於流式傳輸資料輸入或輸出的特殊語法。相反,這些被合併為一個 函式。正如我們已經使用 C 標準庫使用 <cstdio> 標頭檔案 一樣,iostream 為 I/O 提供基本 OOP 服務。

iostream 自動定義並使用一些標準物件

  • cin,istream 類的物件,從標準輸入裝置讀取資料。
  • cout,ostream 類的物件,將資料顯示到標準輸出裝置。
  • cerr,ostream 類的另一個物件,將未緩衝的輸出寫入標準錯誤裝置。
  • clog,與 cerr 相似,但使用緩衝輸出。

用於將資料傳送到 標準流 輸入、輸出、錯誤(未緩衝)和錯誤(緩衝)分別進行。作為 C++ 標準庫的一部分,這些物件是 std 名稱空間 的一部分。

標準輸入、輸出和錯誤

最常用的流是 coutcincerr(分別讀作“c out”、“c in”和“c err(or)”)。它們在標頭檔案 <iostream> 中定義。通常,這些流從控制檯或終端讀寫。在基於 UNIX 的作業系統(如 Linux 和 Mac OS X)中,使用者可以將它們重定向到其他檔案,甚至其他程式,以進行日誌記錄或其他目的。它們類似於stdout, stdin以及stderr在 C 中找到的。cout用於通用輸出,cin用於輸入,以及cerr用於列印錯誤。(cerr通常會轉到與cout相同的位置,除非其中一個或兩個都被重定向,但它沒有緩衝,允許使用者微調程式輸出的哪些部分被重定向到哪裡。)

在這種情況下,輸出到流的標準語法是cout

cout << some_data << some_more_data;

示例

#include <iostream>

using namespace std;

int main()
{
  int iA = 1;
  cout << "Hello, World! " << iA << '\n';

  return 0;
}

執行結果

Hello, World! 1

若要新增換行符,請傳送一個換行符,\n. 使用 std::endl 也會輸出一個換行符,但它還會呼叫 os.flush()。

示例

#include <iostream>
#include <ostream>

using namespace std;

int main()
{
  int iA = 1;
  char ch = 13;
  cout << "Hello, World!" << "\n" << iA << "\n" << ch << endl;

  return 0;
}

執行

Hello, World!
1

始終以空行結束輸出是一個好主意,以免弄亂使用者的終端。

如“Hello, World!”程式所示,我們將輸出定向到 std::cout。這意味著它是 *標準庫* 的一個 *成員*。目前,不必擔心這意味著什麼;我們將在後面的章節中介紹庫和名稱空間。

你需要記住的是,要使用輸出流,必須包含對標準 IO 庫的引用,如以下所示

#include <iostream>

這將開啟許多我們現在可以使用的流、函式和其他程式設計裝置。對於本節,我們感興趣的是其中兩個;std::coutstd::endl

一旦引用了標準 IO 庫,我們就可以非常簡單地使用輸出流。若要使用流,請給出其名稱,然後 *管道* 進出它的內容,如以下所示

std::cout << "Hello, World!";

The<<運算子將其右側的所有內容饋送到流中。我們本質上將一個文字物件饋送到了流中。這就是我們的工作;流現在決定如何處理該物件。對於輸出流,它會在螢幕上打印出來。

我們不僅限於只向流傳送單個物件型別,也不限於一次傳送一個物件。請考慮以下示例

 std::cout << "Hello, " << "Joe"<< std::endl;
 std::cout << "The answer to life, the universe and everything is " << 42 << std::endl;

如您所見,我們在管道字元分隔的不同值中饋送了這些值。結果出來的東西類似

Hello, Joe
The answer to life, the universe and everything is 42

您會注意到使用了std::endl在到目前為止的一些示例中。這是一個特殊的操縱器,它不僅輸出一個換行符,而且還會呼叫 os.flush()。

輸入
[edit | edit source]

一個只輸出資訊但不關心使用者想要什麼的應用程式有什麼用?幾乎沒有。幸運的是,當您使用流時,輸入和輸出一樣容易。

*標準輸入流* 稱為std::cin並且使用方法與輸出流非常相似。我們再次例項化標準 IO 庫

#include <iostream>

這使我們可以訪問std::cin(以及該類的其餘部分)。現在,我們像往常一樣給出流的名稱,並將來自它的輸出管道到一個變數中。這裡必須發生許多事情,以下示例中演示了這些事情

#include <iostream>
int main(int iArgc, char a_chArgv[]) {
  int iA;
  std::cout << "Hello! How old are you? ";
  std::cin >> iA;
  std::cout << "You're really " << iA << " years old?" << std::endl;
 
  return 0;
}

我們像往常一樣例項化標準 IO 庫,並以通常的方式呼叫我們的 main 函式。現在我們需要考慮使用者的輸入去哪裡。這需要一個變數(在後面的章節中討論),我們將其宣告為稱為a.

接下來,我們傳送一些輸出,詢問使用者的年齡。真正的輸入現在發生了;使用者輸入的所有內容,直到他們按下 Enter,都將儲存在輸入流中。我們將其從輸入流中提取並儲存到我們的變數中。

最後,我們輸出使用者的年齡,將變數的內容管道到輸出流中。

注意:您會注意到,如果輸入的不是整數,程式將會崩潰。這是由於我們設定變數的方式。目前不必擔心這個;我們將在後面介紹變數。

使用使用者輸入的程式
[edit | edit source]

以下程式從使用者那裡輸入兩個數字,並打印出它們的總和

 #include <iostream>
 
 int main()
 {
    int iNumber1, iNumber2;
    std::cout << "Enter number 1: ";
    std::cin >> iNumber1;
    std::cout << "Enter number 2: ";
    std::cin >> iNumber2;
    std::cout << "The sum of " << iNumber1 << " and " << iNumber2 << " is "
               << iNumber1 + iNumber2 << ".\n";
    return 0;
 }

就像std::cout代表標準輸出流一樣,C++ 庫提供了(以及iostream標頭檔案宣告)物件std::cin代表標準輸入,它通常從鍵盤獲取輸入。語句

 std::cin >> iNumber1;

使用 *提取運算子* (>>) 從使用者那裡獲取整數輸入。當用於輸入整數時,會跳過任何前導空格,會讀取一個有效數字序列(可選地前面帶有+-符號),並將值儲存在變數中。使用者輸入中的任何剩餘字元都不會 *被消耗*。這些字元將在下次執行某些輸入操作時被考慮。

如果您希望程式使用特定名稱空間中的函式,通常必須指定函式所在的名稱空間。上面的示例呼叫cout,它是std名稱空間的成員(因此為std::cout)。如果您希望程式專門使用 std 名稱空間來識別符號,這本質上消除了對所有未來的作用域解析的需求(例如std:),您可以像這樣編寫上面的程式

#include <iostream>
 
using namespace std;

int main()
{
    int iNumber1, iNumber2;
    cout << "Enter number 1: ";
    cin >> iNumber1;
    cout << "Enter number 2: ";
    cin >> iNumber2;
    cout << "The sum of " << iNumber1 << " and " << iNumber2 << " is "
               << iNumber1 + iNumber2 << ".\n";
    return 0;
}

請注意,'std' 名稱空間是標準 C++ 庫定義的名稱空間。

操縱器
[edit | edit source]

操縱器是一個函式,可以在不同情況下作為引數傳遞給流。例如,操縱器 'hex' 將導致流物件將隨後的整數輸入格式化為十六進位制而不是十進位制。同樣,'oct' 會導致整數以八進位制顯示,而 'dec' 會恢復為十進位制。

示例

#include <iostream>
using namespace std;

int main()
{
  cout << dec << 16 << ' ' << 10 << "\n";
  cout << oct << 16 << ' ' << 10 << "\n";
  cout << hex << 16 << ' ' << 10 << endl;

  return 0;
}

執行

16 10
20 12
10 a

有許多操縱器可以與流一起使用來簡化輸入的格式化。例如,'setw()' 設定下一個顯示的資料項的欄位寬度。與 'left' 和 'right'(設定資料的對齊方式)一起使用時,'setw' 可以很容易地用於建立資料列。

示例

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
	cout << setw(10) << right << 90 << setw(8) << "Help!\n";
	cout << setw(10) << left << 45 << setw(8) << "Hi!" << endl;

	return 0;
}

執行

        90   Help!
45        Hi!

頂行中的資料顯示在由 'setw' 建立的列的右側,而在下一行中,資料在列中左對齊。請注意包含一個新的庫 'iomanip'。大多數格式化操縱器都需要這個庫。

以下是其他一些操縱器及其用途

操縱器 函式
boolalpha 將布林值顯示為 'true' 和 'false',而不是作為整數。
noboolalpha 強制布林值顯示為整數值
showuppercase 在顯示之前將字串轉換為大寫
noshowuppercase 按接收到的方式顯示字串,而不是大寫
fixed 強制浮點數以固定的小數位數顯示
scientific 以科學記數法顯示浮點數
緩衝區
[edit | edit source]

大多數流物件,包括 'cout' 和 'cin',在記憶體中都有一個區域,它們正在傳輸的資訊會停留在那裡,直到被請求。這稱為一個 '緩衝區'。瞭解緩衝區的功能對於掌握流及其使用至關重要。

示例

#include <iostream>
using namespace std;

int main()
{
  int iNumber1, iNumber2;
  cin >> iNumber1;
  cin >> iNumber2;

  cout << "Number1: " << iNumber1 << "\n"
       << "Number2: " << iNumber2 << endl;

  return 0;
}

執行 1

>74
>27
Number1: 74
Number2: 27

輸入是分開給出的,它們之間有一個硬回車。'>' 表示使用者輸入。

執行 2

>74 27
Number1: 74
Number2: 27

輸入在同一行輸入。它們都進入 'cin' 流緩衝區,在那裡它們被儲存起來,直到需要。當執行 'cin' 語句時,緩衝區的內容會被讀入相應的變數。

執行 3

>74 27 56
Number1: 74
Number2: 27

在此示例中,'cin' 接收到的輸入超出了它的請求。它讀入的第三個數字 56 從未插入到變數中。它會一直留在緩衝區中,直到再次呼叫 'cin'。使用緩衝區可以解釋流可能呈現的許多奇怪行為。

示例

#include <iostream>
using namespace std;

int main()
{
  int iNumber1, iNumber2, iNumber3;
  cin >> iNumber1 >> iNumber2;

  cout << "Number1: " << iNumber1 << "\n"
    << "Number2: " << iNumber2 << endl;

  cin >> iNumber3;

  cout << "Number3: " << iNumber3 << endl;

  return 0;
}

執行

>45 89 37
Number1: 45
Number2: 89
Number3: 37

請注意,所有三個數字都在同一行同時輸入,但流只在被請求時才將它們從緩衝區中提取出來。這可能會導致意外的輸出,因為使用者可能會不小心在輸入中輸入額外的空格。編寫良好的程式會測試這種意外輸入並優雅地處理它。

ios 是 C++ 標準庫中的一個 標頭檔案,它定義了 iostream 操作的基本幾個型別和函式。這個標頭檔案通常由其他 iostream 標頭檔案自動包含。程式設計師很少直接包含它。

型別定義
[edit | edit source]
名稱 描述
ios 支援舊的 iostream 庫中的 ios 類。
streamoff 支援內部操作。
streampos 儲存緩衝區指標或檔案指標的當前位置。
streamsize 指定流的大小。
wios 支援舊的 iostream 庫中的 wios 類。
wstreampos 儲存緩衝區指標或檔案指標的當前位置。
操縱器
[edit | edit source]
名稱 描述
boolalpha 指定型別為 bool 的變數在流中顯示為 true 或 false。
dec 指定整數變數以 10 進製表示法顯示。
fixed 指定以固定小數表示法顯示浮點數。
hex 指定整數變數以 16 進製表示法顯示。
internal 導致數字的符號左對齊,而數字右對齊。
left 導致寬度不及輸出寬度的文字在流中與左邊緣對齊。
noboolalpha 指定型別為 bool 的變數在流中顯示為 1 或 0。
noshowbase 關閉指示顯示數字的記數基數。
noshowpoint 僅顯示小數部分為零的浮點數的整數部分。
noshowpos 導致正數不顯示顯式符號。
noskipws 導致空格由輸入流讀取。
nounitbuf 導致輸出被緩衝並在緩衝區滿時處理。
nouppercase 指定十六進位制數字和科學記數法中的指數以小寫顯示。
oct 指定整數變數以 8 進製表示法顯示。
right 使寬度小於輸出寬度 的文字在流中與右邊緣對齊。
scientific 導致浮點數使用科學計數法顯示。
showbase 指示數字顯示的基數。
showpoint 顯示浮點數的整數部分和小數點右邊的數字,即使小數部分為零。
showpos 導致正數顯式帶符號。
skipws 使輸入流不讀取空格。
unitbuf 導致在緩衝區不為空時處理輸出。
uppercase 指定十六進位制數字和科學計數法中的指數以大寫字母顯示。
名稱 描述
basic_ios 模板類描述了輸入流(模板類 basic_istream)和輸出流(模板類 basic_ostream)共有的儲存和成員函式,這些函式依賴於模板引數。
fpos 模板類描述了一個可以儲存恢復任何流中任意檔案位置指示器所需的所有資訊的 物件。
ios_base 類描述了輸入和輸出流共有的儲存和成員函式,這些函式不依賴於模板引數。

coutcin一起,我們可以進行與使用者的基本通訊。對於更復雜的 io,我們希望從檔案中讀取資料並寫入檔案。這是透過檔案流類完成的,這些類在標頭檔案中定義<fstream>. ofstream 是一個輸出檔案流,ifstream 是一個輸入檔案流。

檔案

開啟檔案,可以呼叫open在檔案流上,或者更常見的是,使用建構函式。也可以提供一個開啟模式來進一步控制檔案流。開啟模式包括

  • ios::app保留檔案的原始內容,並將新資料追加到末尾。
  • ios::out輸出檔案中 的新資料,刪除舊內容。(ofstream 的預設值)
  • ios::in從檔案中讀取資料。(ifstream 的預設值)

示例

// open a file called Test.txt and write "HELLO, HOW ARE YOU?" to it
#include <fstream>

using namespace std;

int main()
{
  ofstream file1;

  file1.open("file1.txt", ios::app);
  file1 << "This data will be appended to the file file1.txt\n";
  file1.close();

  ofstream file2("file2.txt");
  file2 << "This data will replace the contents of file2.txt\n";

  return 0;
}

如果不需要返回值(是否成功),可以省略對 close() 的呼叫;解構函式會在物件超出範圍時呼叫 close。

如果操作(例如開啟檔案)不成功,則會在流物件中設定一個標誌。可以使用 bad()fail() 成員函式檢查標誌的狀態,這些函式返回一個布林值。流物件不會在這種情況下丟擲任何異常;因此需要手動狀態檢查。有關 bad()fail() 的詳細資訊,請參見參考文件。

文字輸入直到 EOF/錯誤/無效輸入
[編輯 | 編輯原始碼]

從流infile到變數data直到以下情況之一

  • infile.
  • infile中讀取時發生錯誤(例如,在從遠端檔案讀取時連線關閉)。
  • 輸入項無效,例如當data為型別int.
#include <iostream>

// …

while (infile >> data)
{
    // manipulate data here
}

時,出現非數字字元。

#include <iostream>

// …

while (!infile.eof())
{
    infile >> data; // wrong!
    // manipulate data here
}

請注意,以下內容不正確這會導致輸入檔案中最後一個專案被處理兩次,因為在輸入失敗(由於 EOF)之前不會返回 true。

類和輸出流

讓您自己的類例項與流框架相容通常很有用。例如,如果您定義了類 Foo 如下

 class Foo
 {
 public:

	Foo() : m_iX(1), m_iY(2)
	{
	}

	int m_iX, m_iY;
 };

您將無法使用 '<<' 運算子直接將其例項傳遞給 cout,因為它沒有為這兩個物件(Foo 和 ostream)定義。需要做的是定義這個運算子,從而將使用者定義的類與流類繫結。

 ostream& operator<<(ostream& output, Foo& arg)
 {
	output << arg.m_iX << "," << arg.m_iY;
	return output;
 }

現在這是可能的

 Foo myObject;
 cout << "my_object's values are: " << myObject << endl;

運算子函式需要具有 'ostream&' 作為其返回型別,因此連結輸出像往常一樣在流和 Foo 型別的物件之間工作

 Foo my1, my2, my3;
 cout << my1 << my2 << my3;

這是因為 (cout << my1) 的型別是 ostream&,所以下一個引數 (my2) 可以附加到它,這再次得到一個 ostream&,因此 my3 可以附加,等等。

如果您決定限制對類 Foo 中的成員變數 m_iXm_iY 的訪問(這可能是一個好主意),即

 class Foo
 {
 public:

	Foo() : m_iX(1), m_iY(2)
	{
	}

 private:
	int m_iX, m_iY;
 };

您會遇到麻煩,因為 operator<< 函式無法訪問其第二個引數的私有變數。這個問題有兩個可能的解決方案

1. 在類 Foo 中,將 operator<< 函式宣告為類的 friend,以授予其訪問私有成員的許可權,即在類宣告中新增以下行

 
 friend ostream& operator<<(ostream& output, Foo& arg);

然後像往常一樣定義 operator<< 函式(注意宣告的函式不是 Foo 的成員,只是它的 friend,所以不要嘗試將其定義為 Foo::operator<<)。

2. 新增用於訪問成員變數的公共可用函式,並使 operator<< 函式使用這些函式而不是

 class Foo
 {
 public:

	Foo() : m_iX(1), m_iY(2)
	{
	}

	int getX()
	{
		return m_iX;
	}

	int getY()
	{
		return m_iY;
	}

 private:
	int m_iX, m_iY;
 };

 ostream& operator<<(ostream& output, Foo& arg)
 {
	output << iArg.getX() << "," << iArg.getY();
	return output;
 }

‎四捨五入數字示例

[編輯 | 編輯原始碼]

這是一個將數字四捨五入為字串的示例,稱為 RoundToString 的函式。數字可能包含尾隨零,這些零在使用數字格式時應消失。

常量類包含重複的常量,這些常量在程式碼中只應該出現一次,以避免無意更改。(如果一個常量無意更改,最有可能看到它,因為它在多個位置使用。)

程式碼最初是由 Tangible Software Solutions 的 JAVA 到 C++ 轉換器 交叉編譯的。某些部分可能具有版權宣告要求,阻止將它們包含在此作品中,您可以從該位置獲得 StringConverter 的程式碼。

以下是相關的程式碼及其呼叫。歡迎您編寫一個更短的版本,它能得到相同的結果。

Common.cpp:

#include "StdAfx.h"

namespace common
{
    double const Common::NEARLY_ZERO = 1E-4;
    double const Common::VERY_LARGE = 1E10;
    string const Common::strZERO = *new string(1, Common::ZERO);
    
    bool Common::IsTrimmable(char const chCHARACTER,
        char const chTRIM,
        bool const bIS_NUMERIC)
    {
        return ((chCHARACTER == chTRIM)
            || (bIS_NUMERIC && (chCHARACTER == Common::ZERO)));
    }

    string const& Common::Trim(string const& strVALUE, char const chTRIM)
    {
        return TrimLeft(TrimRight(strVALUE, chTRIM), chTRIM);
    }

    string const& Common::TrimLeft(string const& strVALUE,
            char const chTRIM)
    {
        if (strVALUE.length() == 0) return strVALUE;
        else
        {
            ushort usPosition = 0;

            for (; usPosition < strVALUE.length(); usPosition++)
            {
                if (!IsTrimmable(strVALUE[usPosition], chTRIM)) break;
            }

            return *new string(strVALUE.substr(usPosition));
        }
    }

    string const& Common::TrimRight(string const& strVALUE,
        char const chTRIM)
    {
        if (strVALUE.length() == 0) return strVALUE;
        else
        {
            ushort usPosition = strVALUE.length() - 1;

            for (; usPosition < strVALUE.length(); usPosition--)
            {
                if (!IsTrimmable(strVALUE[usPosition], chTRIM))
                {
                    if (strVALUE[usPosition] != Common::PERIOD) ++usPosition;

                    break;
                }
            }

            return *new string(strVALUE.substr(0, usPosition));
        }
    }
}

Common.h:

#pragma once

#include <string>

namespace common
{
    /// <summary>
    /// Class that comprises of constant values and recurring algorithms.
    /// 
    /// @author Saban
    /// 
    ///</summary>
    class Common
    {
        /// <summary>
        /// Determines, if the character is trimmable or not.
        /// </summary>
        /// <param name="chCHARACTER">Character to be checked</param>
        /// <param name="chTRIM">
        /// Trim character that defaults to a space
        /// </param>
        /// <param name="bIS_NUMERIC">
        /// If numeric, zeros are also considered as trimmable characters
        /// </param>
        /// <returns>Whether the character is trimmable or not</returns>
        static bool IsTrimmable(char const chCHARACTER,
            char const chTRIM = SPACE, bool const bIS_NUMERIC = true);

    public:
        /// <summary>Carriage return constant</summary>
        //static char const CARRIAGE_RETURN = '\r';
        /// <summary>Constant of comma or decimal point in German</summary>
        static char const COMMA = ',';
        /// <summary>Dash or minus constant</summary>
        static char const DASH = '-';
        /// <summary>
        /// The exponent sign in a scientific number, or the letter e.
        /// </summary>
        static char const EXPONENT = 'e';
        /// <summary>The full stop or period</summary>
        static char const PERIOD = '.';
        /// <summary>Space constant</summary>
        static char const SPACE = ' ';
        /// <summary>Space constant</summary>
        static char const ZERO = '0';
        /// <summary>
        //// Value under which the double should switch to fixed-point.
        /// </summary>
        static double const VERY_LARGE;
        /// <summary>
        //// Value above which the double should switch to fixed-point.
        /// </summary>
        static double const NEARLY_ZERO;
          /// <summary>
          /// The zero string constant used at several places
        /// </summary>
          static string const strZERO;

        /// <summary>
        /// Trims the trim character from left and right of the value.
        /// </summary>
        /// <param name="strVALUE">Value to be trimmed</param>
        /// <param name="chTRIM">
        /// Trim character that defaults to a space
        /// </param>
        /// <returns>Trimmed string</returns>
        static string const& Trim(string const& strVALUE,
            char const chTRIM = Common::SPACE);

        /// <summary>
        /// Trims the trim character from left the value.
        /// </summary>
        /// <param name="strVALUE">Value to be trimmed</param>
        /// <param name="chTRIM">
        /// Trim character that defaults to a space
        /// </param>
        /// <returns>Trimmed string</returns>
        static string const& TrimLeft(string const& strVALUE,
            char const chTRIM = Common::SPACE);

        /// <summary>
        /// Trims the trim character from right of the value.
        /// </summary>
        /// <param name="strVALUE">Value to be trimmed</param>
        /// <param name="chTRIM">
        /// Trim character that defaults to a space
        /// </param>
        /// <returns>Trimmed string</returns>
        static string const& TrimRight(string const& strVALUE,
            char const chTRIM = Common::SPACE);
    }; // class Common
}

Math 類是對 <math.h> 庫的增強,包含四捨五入計算。

Math.cpp:

#include "StdAfx.h"
#include <string>

namespace common
{
    string const Maths::strZEROS = "000000000000000000000000000000000";

    byte Maths::CalculateMissingSignificantZeros(
        byte const ySIGNIFICANTS_AFTER,
        char const chSEPARATOR,
        double const dVALUE,
        string const strMANTISSA)
    {
        // Existing significants after decimal separator are
        byte const yAFTER = FindSignificantsAfterDecimal(chSEPARATOR, strMANTISSA);
        // Number of digits to add are
        byte const yZEROS = ySIGNIFICANTS_AFTER - ((yAFTER == 0) ? 1 : yAFTER);

        return ((yZEROS >= 0) ? yZEROS : 0);
    }

    byte Maths::FindDecimalSeparatorPosition(string const& strVALUE)
    {
        byte const ySEPARATOR_AT = (byte)strVALUE.find(Common::PERIOD);

        return (ySEPARATOR_AT > -1)
            ? ySEPARATOR_AT : (byte)strVALUE.find(Common::COMMA);
    }

    byte Maths::FindFirstNonZeroDigit(double const dVALUE)
    {
        return FindFirstNonZeroDigit(StringConverter::ToString
            <double, StringConverter::DIGITS>(dVALUE));
    }

    byte Maths::FindFirstNonZeroDigit(string const& strVALUE)
    {
        // Find the position of the first non-zero digit:
        byte yNonZeroAt = 0;

        for (; (yNonZeroAt < (byte)strVALUE.length())
            && ((strVALUE[yNonZeroAt] == Common::DASH)
            || (strVALUE[yNonZeroAt] == Common::PERIOD)
            || (strVALUE[yNonZeroAt] == Common::ZERO)); yNonZeroAt++) ;

        return yNonZeroAt;
    }

    byte Maths::FindSignificantDigits(byte const ySIGNIFICANTS_AFTER,
        char const chSEPARATOR,
        double const dVALUE)
    {
        if (dVALUE == 0) return 0;
        else
        {
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
            string strMantissa = FindMantissa(TestCommons::SIGNIFICANTS,
                chSEPARATOR, StringConverter::ToString<double,
                StringConverter::DIGITS>(dVALUE));

            if (dVALUE == static_cast<long>(dVALUE))
            {
                strMantissa =
                    strMantissa.substr(0, strMantissa.find(Common::COMMA));
            }

            strMantissa = RetrieveDigits(chSEPARATOR, strMantissa);

            return strMantissa.substr(
                FindFirstNonZeroDigit(strMantissa)).length();
        }
    }

    byte Maths::FindSignificantsAfterDecimal(byte const ySIGNIFICANTS_BEFORE,
        byte const ySIGNIFICANT_DIGITS)
    {
        byte const yAFTER_DECIMAL = ySIGNIFICANT_DIGITS - ySIGNIFICANTS_BEFORE;

        return (yAFTER_DECIMAL > 0) ? yAFTER_DECIMAL : 0;
    }

    byte Maths::FindSignificantsAfterDecimal(char const chSEPARATOR,
        double const dVALUE)
    {
        if (dVALUE == 0) return 1;
        else
        {
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
            string strValue = StringConverter::ToString<double,
                StringConverter::DIGITS>(dVALUE);

            byte const ySEPARATOR_AT = (byte)strValue.find(chSEPARATOR);

            if (ySEPARATOR_AT > -1)
            {
                strValue = strValue.substr(ySEPARATOR_AT + 1);
            }

            short const sE_AT = strValue.find(Common::EXPONENT);

            if (sE_AT > 0) strValue = strValue.substr(0, sE_AT);

            long lValue = StringConverter::FromString
                <long, StringConverter::DIGITS>(strValue);

            if (abs(dVALUE) < 1)
            {
                return (byte)StringConverter::ToString<long,
                    StringConverter::DIGITS>(lValue).length();
            }
            else if (lValue == 0) return 0;
            else
            {
                strValue = "0." + strValue;

                return (byte)(strValue.length() - 2);
            }
        }
    }

    byte Maths::FindSignificantsBeforeDecimal(char const chSEPARATOR,
        double const dVALUE)
    {
        string const strVALUE = StringConverter::ToString<double,
            StringConverter::DIGITS>(dVALUE);

        // Return immediately, if result is clear: Special handling at
        // crossroads of floating point and exponential numbers:
        if ((dVALUE == 0)
            || (abs(dVALUE) >= Common::NEARLY_ZERO) && (abs(dVALUE) < 1))
        {
            return 0;
        }
        else if ((abs(dVALUE) > 0) && (abs(dVALUE) < Common::NEARLY_ZERO))
        {
            return 1;
        }
        else
        {
            byte significants = 0;
            // Significant digits to the right of decimal separator:
            for (byte s = 0; s < (byte)strVALUE.length(); s++)
            {
                if ((strVALUE[s] == chSEPARATOR)
                    || (strVALUE[s] == Common::EXPONENT))
                {
                    break;
                }
                else if (strVALUE[s] != Common::DASH) significants++;
            }

            return significants;
        }
    }

    byte Maths::FindSignificantsAfterDecimal(char const chSEPARATOR,
        string const strVALUE)
    {
        size_t const COMMA_AT = strVALUE.find(chSEPARATOR);
        size_t const LENGTH = strVALUE.length();
        // Existing digits after decimal separator are
        byte yAfter = 0;
        // Existing significants after decimal separator may start at the first
        // non-zero digit:
        if (StringConverter::FromString<double, 5>
            (strVALUE.substr(0, COMMA_AT)) == 0)
        {
            string strRightOf = Common::TrimLeft(strVALUE.substr(COMMA_AT + 1));

            yAfter = strRightOf.length();
        }
        else yAfter = (COMMA_AT < string::npos) ? LENGTH - 1 - COMMA_AT : 0;

        return yAfter;
    }

    double Maths::Power(short const sBASIS, short const sEXPONENT)
    {
        if (sBASIS == 0) return (sEXPONENT != 0) ? 1 : 0;
        else
        {
            if (sEXPONENT == 0) return 1;
            else
            {
                // The Math method power does change the least significant 
                // digits after the decimal separator and is therefore useless.
                double result = 1;

                if (sEXPONENT > 0)
                {
                    for (short s = 0; s < sEXPONENT; s++) result *= sBASIS;
                }
                else if (sEXPONENT < 0)
                {
                    for (short s = sEXPONENT; s < 0; s++) result /= sBASIS;
                }

                return result;
            }
        }
    }

    double Maths::Round(byte const yDIGITS,
        char const chSEPARATOR,
        double const dVALUE)
    {
        if (dVALUE == 0) return 0;
        else
        {
            bool bIsScientific = false;
            double const dCONSTANT = Power(10, yDIGITS);

            if ((abs(dVALUE) < Common::NEARLY_ZERO)
                || (abs(dVALUE) >= Common::VERY_LARGE))
            {
                bIsScientific = true;
            }
            
            short const sEXPONENT =
                FindExponent(dVALUE, chSEPARATOR, bIsScientific);

            short sExponent = sEXPONENT;

            // Determine the correcting power:
            short sPower = sExponent;

            if (sEXPONENT == 0)
            {
                sPower = FindExponent(dVALUE, chSEPARATOR, HasDecimals(dVALUE));
            }

            double dValue1 = dVALUE*dCONSTANT*pow(10., -sPower);

            string const strE_SIGN = (sExponent < 0)
                ? StringConverter::ToString<char,
                StringConverter::DIGITS>(Common::DASH) : "";

            if (sExponent != 0)
            {
                sExponent = static_cast<short>(abs(sExponent));
                dValue1 = Round(dValue1);
            }
            else dValue1 = Round(dValue1)/dCONSTANT/pow(10., -sPower);

            // Power method cannot be used, as the exponentiated number may
            // exceed the maximal long value.
            sExponent -= Signum(sEXPONENT)*(FindSignificantDigits(
                yDIGITS, chSEPARATOR, dValue1) - 1);

            if (sEXPONENT != 0)
            {
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
                string strValue = StringConverter::ToString<double,
                    StringConverter::DIGITS>(dValue1);

//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
                strValue =
                    strValue.substr(0, FindDecimalSeparatorPosition(strValue))
                    + Common::EXPONENT + strE_SIGN
                    + StringConverter::ToString<short,
                    StringConverter::DIGITS>(sExponent);

                dValue1 = StringConverter::FromString<double, 5>(strValue);
            }

            return dValue1;
        }
    }

    double Maths::Round(double const dValue)
    {
        return (double)(long long)(dValue + .5);
    }

    short Maths::FindExponent(double const dVALUE,
        char const chSEPARATOR,
        bool const bSCIENTIFIC)
    {
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
        return (short)StringConverter::FromString<short,
            StringConverter::DIGITS>(FindExponent(StringConverter::ToString
            <double, StringConverter::DIGITS>(dVALUE), chSEPARATOR,
            bSCIENTIFIC));
    }

    string Maths::FindExponent(string const& strVALUE,
        char const chSEPARATOR,
        bool const bSCIENTIFIC)
    {
        if (StringConverter::FromString
            <double, StringConverter::DIGITS>(strVALUE) == 0)
        {
            return Common::strZERO;
        }

        short const sE_AT = strVALUE.find(Common::EXPONENT);
        short sExponent = 0;

        if (sE_AT < 0)
        {
            // If all numbers are to be considered scientific, such as
            // 1 = 1.0000e0…
            if (bSCIENTIFIC)
            {
                // Find the exponent by counting leading zeros:
                byte const ySEPARATOR_AT = strVALUE.find(chSEPARATOR);

                if (ySEPARATOR_AT > -1)
                {
                    string const strAFTER = strVALUE.substr(ySEPARATOR_AT + 1);
                    sExponent = 0;

                    for (; sExponent < (short)strAFTER.length(); sExponent++)
                    {
                        if ((strAFTER[sExponent] >= '1')
                            && (strAFTER[sExponent] <= '9'))
                        {
                            sExponent *= -1;
                            break;
                        }
                    }
                }
            }
            else return Common::strZERO;
        }
        else
        {
            sExponent = (short)StringConverter::FromString
                <double, StringConverter::DIGITS>(strVALUE.substr(sE_AT + 1));
        }

        return StringConverter::ToString<short,
            StringConverter::DIGITS>(sExponent);
    }

    string Maths::FindMantissa(byte const yDIGITS,
        char const chSEPARATOR,
        const string& strVALUE)
    {
        byte yDigits = yDIGITS;
        short const sE_AT = strVALUE.find(Common::EXPONENT);
        string strValue = strVALUE;

        // Remove lagging insignificant zeros, if any:
        if (sE_AT == -1)
        {
            if (StringConverter::FromString<short,
                3>(strValue.substr(0, 2)) == 0)
            {
                byte yPosition = 2;

                for (; (yPosition < (byte)strValue.length())
                    && ((strValue[yPosition] == Common::PERIOD)
                    || (strValue[yPosition] == Common::ZERO)); yPosition++);

                yDigits += yPosition;

                strValue = strValue.substr(0, yDigits);
            }
        }
        else strValue = Common::TrimRight(strValue.substr(0, sE_AT));

        if (StringConverter::FromString<double, StringConverter::DIGITS>(strValue) == 0)
        {
            strValue = RemoveInsignificants(yDIGITS, strValue);
        }
        else strValue = RemoveInsignificants(yDigits, strValue);

        if (FindDecimalSeparatorPosition(strValue) == -1)
        {
            return strValue + ".0";
        }
        else return strValue;
    }

    string Maths::RemoveInsignificants(byte const yDIGITS,
        string const& strVALUE)
    {
        byte const yCHARACTERS = yDIGITS
            + ((FindDecimalSeparatorPosition(strVALUE) < string::npos) ? 1 : 0)
            + ((strVALUE[0] == Common::DASH)
            ? ((strVALUE[1] == Common::ZERO) ? 2 : 1)
            : ((strVALUE[0] == Common::ZERO) ? 1 : 0));

        return strVALUE.substr(0, yCHARACTERS);
    }

    string Maths::RetrieveDigits(char const chSEPARATOR, const string& strNUMBER)
    {
        string strNumber = strNUMBER;

        short const sE_AT = strNumber.find(Common::EXPONENT);
        // Strip off exponent part, if it exists:
        if (sE_AT > -1) strNumber = strNumber.substr(0, sE_AT);

//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'replace':
        return StringConverter::Replace(StringConverter::Replace(strNumber,
            StringConverter::ToString<char, 1>(Common::DASH), ""),
            StringConverter::ToString<char, 1>(chSEPARATOR), "");
    }

    string Maths::RoundToString(byte const ySIGNIFICANT_DIGITS,
        char const chSEPARATOR,
        double dValue)
    {
        // Number of significants that *are* before the decimal separator:
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
        byte const ySIGNIFICANTS_BEFORE =
            FindSignificantsBeforeDecimal(chSEPARATOR, dValue);
        // Number of decimals that *should* be after the decimal separator:
        byte const ySIGNIFICANTS_AFTER = FindSignificantsAfterDecimal(
            ySIGNIFICANTS_BEFORE, ySIGNIFICANT_DIGITS);

        byte const yDIGITS = (dValue != 0)
            ? ySIGNIFICANTS_BEFORE + ySIGNIFICANTS_AFTER : 3 /* = 0.0 */;
        // Round to the specified number of digits after decimal separator:
        double const dROUNDED =
            Maths::Round(ySIGNIFICANTS_AFTER, chSEPARATOR, dValue);

//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
        string const strEXPONENT = FindExponent(StringConverter::ToString
            <double, StringConverter::DIGITS>(dROUNDED));
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
        string const strMANTISSA = FindMantissa(TestCommons::SIGNIFICANTS,
            chSEPARATOR, StringConverter::ToString<double,
            StringConverter::DIGITS>(dROUNDED));

        double const dMANTISSA = StringConverter::FromString
            <double, TestCommons::SIGNIFICANTS>(strMANTISSA);
        StringBuilder* pRESULT = new StringBuilder(strMANTISSA);

        // Determine the significant digits in this number:
        byte const ySIGNIFICANTS = FindSignificantDigits(ySIGNIFICANTS_AFTER,
            chSEPARATOR, dMANTISSA);
        // Add lagging zeros, if necessary:
        if (ySIGNIFICANTS <= ySIGNIFICANT_DIGITS)
        {
            if (ySIGNIFICANTS_AFTER != 0)
            {
                if (dValue != 0)
                {
                    pRESULT->Append(strZEROS.substr(0,
                        CalculateMissingSignificantZeros(ySIGNIFICANTS_AFTER,
                        chSEPARATOR, dMANTISSA, strMANTISSA)));
                }
            }
            else
            {
                // Cut off the decimal separator & after decimal digits:
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
                byte const yDECIMAL =
                    pRESULT->Find(StringConverter::ToString<char,
                    StringConverter::DIGITS>(chSEPARATOR));

                if (yDECIMAL > -1) pRESULT->SetLength(yDECIMAL);
            }
        }
        else if (ySIGNIFICANTS_BEFORE > ySIGNIFICANT_DIGITS)
        {
            dValue /= Power(10, ySIGNIFICANTS_BEFORE - ySIGNIFICANT_DIGITS);

            dValue = Round(dValue);

            byte const yDIGITS = ySIGNIFICANT_DIGITS + ((dValue < 0) ? 1 : 0);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
            string const strVALUE = StringConverter::ToString<double,
                StringConverter::DIGITS>(dValue).substr(0, yDIGITS);
            pRESULT->SetLength(0);

            pRESULT->Append(strVALUE + strZEROS.substr(0,
                ySIGNIFICANTS_BEFORE - ySIGNIFICANT_DIGITS));
        }

        if (StringConverter::FromString<double,
            StringConverter::DIGITS>(strEXPONENT) != 0)
        {
            pRESULT->Append(Common::EXPONENT + strEXPONENT);
        }

//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
        return pRESULT->ToString();
    } // public static String RoundToString(…)
}

Math.h:

#pragma once

#include <string>
#include <cmath>

/// 
namespace common
{
    /// <summary>
    /// Class for special mathematical calculations.
    /// ATTENTION: Should not depend on any other class except Java libraries!
    /// @author Saban
    ///</summary>
    class Maths
    {
    private:
        /// <summary>The string of zeros</summary>
        static string const strZEROS;

        /// <summary>
        /// Determines how many zeros are to be appended after the decimal
        /// digits.
        /// </summary>
        /// <param name="ySIGNIFICANTS_AFTER">
        /// Requested significant digits after decimal
        /// </param>
        /// <param name="chSEPARATOR">
        /// Language-specific decimal separator
        /// </param>
        /// </param>
        /// <param name="dVALUE">Rounded number</param>
        /// <param name="strMANTISSA">
        /// Current string where missing digits are to be determined
        /// </param>
        /// <returns>Requested value</returns>
        static byte CalculateMissingSignificantZeros(
            byte const ySIGNIFICANTS_AFTER, char const chSEPARATOR,
            double const dVALUE,
            string const strMANTISSA = "");

        /// <summary>
        /// Finds the decimal position language-independently.
        /// </summary>
        /// <param name="strVALUE">
        /// Value to be searched for the decimal separator
        /// </param>
        /// <returns>
        /// The position of the decimal separator or string::npos,
        /// if no decimal separator has been found.
        /// </returns>
        static byte FindDecimalSeparatorPosition(string const& strVALUE);

        /// <summary>
        /// Finds the first non-zero decimal position.
        /// </summary>
        /// <param name="dVALUE">
        /// Value to be searched for the decimal position
        /// </param>
        /// <returns>The first non-zero decimal position</returns>
        static byte FindFirstNonZeroDigit(double const dVALUE);

        /// <summary>
        /// Finds the first non-zero decimal position.
        /// </summary>
        /// <param name="strVALUE">
        /// Value to be searched for the decimal position
        /// </param>
        /// <returns>The first non-zero decimal position</returns>
        static byte FindFirstNonZeroDigit(string const& strVALUE);

        /// <summary>
        /// Calculates the number of all significant digits (without the sign
        /// and the decimal separator).
        /// </summary>
        /// <param name="ySIGNIFICANTS_AFTER">
        /// Number of decimal places after the separator</param>
        /// <param name="chSEPARATOR">
        /// Language-specific decimal separator
        /// </param>
        /// <param name="dVALUE">
        /// Value where the digits are to be counted
        /// </param>
        /// <returns>Number of significant digits</returns>
        static byte FindSignificantDigits(byte const ySIGNIFICANTS_AFTER,
            char const chSEPARATOR, double const dVALUE);

        /// <summary>
        /// Determines the number of significant digits after the decimal
        /// separator knowing the total number of significant digits and the
        /// number before the decimal separator.
        /// </summary>
        /// <param name="ySIGNIFICANTS_BEFORE">
        /// Number of significant digits before separator
        /// </param>
        /// <param name="ySIGNIFICANT_DIGITS">
        /// Number of all significant digits
        /// </param>
        /// Number of significant decimals after the separator
        /// </returns>
        static byte FindSignificantsAfterDecimal(
            byte const ySIGNIFICANTS_BEFORE, byte const ySIGNIFICANT_DIGITS);

        /// <summary>
        /// Finds the significant digits after the decimal separator of a
        /// mantissa.
        /// </summary>
        /// <param name="chSEPARATOR">
        /// Language-specific decimal separator
        /// </param>
        /// <param name="dVALUE">Value to be scrutinised</param>
        /// <returns>
        /// Number of insignificant zeros after decimal separator.
        /// </returns>
        static byte FindSignificantsAfterDecimal(char const chSEPARATOR,
            double const dVALUE);

        /// <summary>
        /// Finds the significant digits after the decimal separator of a
        /// mantissa.
        /// </summary>
        /// <param name="chSEPARATOR">
        /// Language-specific decimal separator
        /// </param>
        /// <param name="strVALUE">Value to be scrutinised</param>
        /// <returns>
        /// Number of insignificant zeros after decimal separator.
        /// </returns>
        static byte FindSignificantsAfterDecimal(char const chSEPARATOR,
            string const strVALUE);

        /// <summary>
        /// Determines the number of digits before the decimal point.</summary>
        /// <param name="chSEPARATOR">
        /// Language-specific decimal separator
        /// </param>
        /// <param name="dVALUE">Value to be scrutinised</param>
        /// <returns>Number of digits before the decimal separator</returns>
        static byte FindSignificantsBeforeDecimal(char const chSEPARATOR,
            double const dVALUE);

        /// <summary>
        /// Returns the exponent part of the double number.</summary>
        /// <param name="dVALUE">
        /// Value of which the exponent is of interest
        /// </param>
        /// <param name="chSEPARATOR">Decimal separator</param>
        /// <param name="bSCIENTIFIC">
        /// If true, the number is considered in scientific notation of the form
        /// 9.999e999 (like 1 = 1.0e0 or 0.124 = 1.24e-1).
        /// </param>
        /// <returns>Exponent of the number or zero.</returns>
        static short FindExponent(double const dVALUE,
            char const chSEPARATOR = Common::PERIOD,
            bool const bSCIENTIFIC = false);

        /// <summary>
        /// Finds the exponent of a number.</summary>
        /// <param name="strVALUE">
        /// Value where an exponent is to be searched
        /// </param>
        /// <param name="chSEPARATOR">Decimal separator</param>
        /// <param name="bSCIENTIFIC">
        /// If true, the number is considered in scientific notation of the form
        /// 1 = 1.0e0 or 0.124 = 1.24e-1.
        /// </param>
        /// <returns>Exponent, if it exists, or "0"</returns>
        static string FindExponent(string const& strVALUE,
            char const chSEPARATOR = Common::PERIOD,
            bool const bSCIENTIFIC = false);

        /// <summary>
        /// Finds the mantissa of a number.</summary>
        /// <param name="yDIGITS">
        /// Number of all digits
        /// </param>
        /// <param name="chSEPARATOR">
        /// Language-specific decimal separator
        /// </param>
        /// <param name="strVALUE">
        /// Value where the mantissa is to be found
        /// </param>
        /// <returns>Mantissa of the number</returns>
        static string FindMantissa(byte const yDIGITS,
            char const chSEPARATOR,
            string const& strVALUE);

        /// <summary>
        /// Removes all insignificant digits.</summary>
        /// <param name="yDIGITS">
        /// Number of significant digits
        /// </param>
        /// <returns>Number with the requested number of digits</returns>
        static string RemoveInsignificants(byte const yDigits,
            string const& strVALUE);

        /// <summary>
        /// Retrieves the digits of the value without decimal separator or
        /// sign.
        /// </summary>
        /// <param name="chSEPARATOR"></param>
        /// <param name="strNUMBER">Mantissa to be scrutinised</param>
        /// <returns>The digits only</returns>
        static string RetrieveDigits(char const chSEPARATOR,
            string const& strNUMBER);

    public:
        /// <summary>
        /// Determines whether the number has decimal places after 
        /// the separator or not.
        /// </summary>
        /// <param name="VALUE"></param>
        /// <returns>true, if it has decimals and false otherwise.</returns>
        template<class T> 
        static bool HasDecimals(const T VALUE)
        {
            return ((VALUE - (long long)VALUE) != 0);
        }

        /// <summary>
        /// Calculates the power of the base to the exponent without changing
        /// the least-significant digits of a number.
        /// </summary>
        /// <param name="BASIS"></param>
        /// <param name="EXPONENT"></param>
        /// <returns>BASIS to power of EXPONENT</returns>
        static double Power(short const sBASIS, short const sEXPONENT);

        /// <summary>
        /// Rounds a number to the decimal places.</summary>
        /// <param name="yDIGITS">Number of all decimal places</param>
        /// <param name="chSEPARATOR">
        /// Language-specific decimal separator
        /// </param>
        /// <param name="dVALUE">Number to be rounded</param>
        /// <returns>Rounded number to the requested decimal places</returns>
        static double Round(byte const yDIGITS,
            char const chSEPARATOR, double const dVALUE);

        /// <summary>
        /// Replacement for Math.round(double) of Java.</summary>
        /// <param name="dValue">Number to be rounded</param>
        /// <returns>Rounded number to the requested decimal places</returns>
        static double Round(double const dVALUE);

        /// <summary>Signum function</summary>
        /// <param name="number">Value to be scrutinised</param>
        /// <returns>Sign of the number</returns>
        template<class T> 
        static byte Signum(T number) 
        {
            return (number < T(0)) ? T(-1) : (number > T(0));
        }

        /// <summary>
        /// Rounds to a fixed number of significant digits.</summary>
        /// <param name="ySIGNIFICANT_DIGITS">
        /// Requested number of significant digits
        /// </param>
        /// <param name="chSEPARATOR">
        /// Language-specific decimal separator
        /// </param>
        /// <param name="dValue">Number to be rounded</param>
        /// <returns>Rounded number</returns>
        static string RoundToString(byte const ySIGNIFICANT_DIGITS,
            char const chSEPARATOR, double dValue);
    }; // class Maths
}

使用預編譯頭需要 StdAfx 檔案

StdAfx.cpp:

// StdAfx.cpp : Quelldatei, die nur die Standard-Includes einbindet.
// Commons.pch ist der vorkompilierte Header.
// StdAfx.obj enthält die vorkompilierten Typinformationen.

#include "StdAfx.h"

StdAfx.h:

// StdAfx.h : Includedatei für Standardsystem-Includedateien
// oder häufig verwendete projektspezifische Includedateien,
// die nur in unregelmäßigen Abständen geändert werden.
//

#pragma once

using namespace std;

typedef signed char byte;
typedef unsigned short ushort;

// Used headers:
#include "Common.h"
#include "Maths.h"
#include "StringBuilder.h"
#include "TestCommons.h"

#include "StringConverter.h"

#include <iostream>

StringBuilder 類是在交叉編譯期間由 JAVA 到 C++ 轉換器新增的

StringBuilder.cpp:

#include "StdAfx.h"

namespace common
{
    StringBuilder::StringBuilder()
    {
        strMain = "";
    }
    
    StringBuilder::StringBuilder(string const& strVALUE)
    {
        Append(strVALUE);
    }
    
    size_t const StringBuilder::Find(string const& strSEARCH) const
    {
        return strMain.find(strSEARCH);
    }
 
    string const& StringBuilder::ToString() const
    {
        return strMain; 
    }

    void StringBuilder::Append(string const& strVALUE)
    {
        strMain.append(strVALUE);
    }

    void StringBuilder::SetLength(const string::size_type SIZE)
    {
        strMain.resize(SIZE);
    }
}

StringBuilder.h:

#pragma once

namespace common
{
    class StringBuilder
    {
    private: 
        string strMain; 

    public:
        /// <summary>
        /// Standard constructor
        /// </summary>
        StringBuilder();

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="strSUBJECT">Value to be used</param>
        StringBuilder(string const& strSUBJECT);

        /// <summary>
        /// Finds the search string inside itself.
        /// </summary>
        /// <param name="strSEARCH">Value to be used</param>
        /// <returns>
        /// The position of the searched text or -1, if the search string
        /// has not been found.
        /// </returns>
        size_t const Find(string const& strSEARCH) const;

        /// <summary>
        /// Converts the contents of itself to a string.
        /// </summary>
        /// <returns>String content</returns>
        string const& ToString() const;

        /// <summary>
        /// Appends a text to this object
        /// </summary>
        /// <param name="strVALUE">Value to be appended</param>
        void Append(string const& strVALUE);

        /// <summary>
        /// Sets the length of the content of this object.
        /// </summary>
        /// <param name="SIZE">New reduced size</param>
        void SetLength(const string::size_type SIZE);
    };
}

軟體的廣泛測試對於程式碼質量至關重要。說程式碼經過測試並不能提供太多資訊。問題是什麼經過測試。雖然在本例中不是這樣,但通常也需要知道在哪裡(在哪個環境中)進行測試,以及如何進行測試,即測試順序。以下是用於測試 Maths 類的程式碼。

TestCommons.cpp:

#include "StdAfx.h"


namespace common
{
    void TestCommons::Test()
    {
        // Test rounding
        vector<double>* pa_dValues = new vector<double>();

        vector<double>& a_dValues = *pa_dValues;

        a_dValues.push_back(0.0);
        AddValue(1.4012984643248202e-45, a_dValues);
        AddValue(1.999999757e-5, a_dValues);
        AddValue(1.999999757e-4, a_dValues);
        AddValue(0.000640589, a_dValues);
        AddValue(1.999999757e-3, a_dValues);
        AddValue(0.3396899998188019, a_dValues);
        AddValue(0.34, a_dValues);
        AddValue(7.07, a_dValues);
        AddValue(118.188, a_dValues);
        AddValue(118.2, a_dValues);
        AddValue(123.405009, a_dValues);
        AddValue(30.76994323730469, a_dValues);
        AddValue(130.76994323730469, a_dValues);
        AddValue(540, a_dValues);
        AddValue(12345, a_dValues);
        AddValue(123456, a_dValues);
        AddValue(540911, a_dValues);
        AddValue(9.223372036854776e56, a_dValues);

        byte const ySIGNIFICANTS = 5;

        for (vector<double>::const_iterator element = a_dValues.begin();
            element != a_dValues.end(); ++element)
        {
            cout << "Maths::RoundToString(" << (short)ySIGNIFICANTS << ", '"
                << Common::PERIOD << "', " << StringConverter::ToString
                <double, StringConverter::DIGITS>(*element) << ") = ";
            cout << Maths::RoundToString(ySIGNIFICANTS, Common::PERIOD,
                *element) << endl;
        }

        pa_dValues->clear();
        byte y;

        cin >> y;
    } // void Test()

    void TestCommons::AddValue(double const dVALUE, vector<double>& a_dValues)
    {
        a_dValues.push_back(-dVALUE);
        a_dValues.push_back(dVALUE);
    }
}

TestCommons.h:

#pragma once

#include <string>
#include <vector>

namespace common
{
    /// <summary>
    /// Test class for the common functionality
    /// @author Saban
    ///</summary>
    class TestCommons
    {
    private:
        /// <summary>
        /// Method that adds a negative and a positive value to values.</summary>
        /// <param name="dVALUE"></param>
        /// <param name="a_dValues"></param>
        static void AddValue(double const dVALUE, vector<double>& a_dValues);

    public:
        /// <summary>Number of significant digits</summary>
        static short const SIGNIFICANTS = 5;

        /// <summary>
        /// Test for the common functionality</summary>
        /// <param name="args"></param>
        static void Test();
    }; // class TestCommons
}

您編寫的更好程式碼的結果應該與我得到的結果一致

Maths::RoundToString(5, '.', 0.00000000000000000) = 0.00000
Maths::RoundToString(5, '.', -1.40129846432482020e-045) = -1.4012e-45
Maths::RoundToString(5, '.', 1.40129846432482020e-045) = 1.4013e-45
Maths::RoundToString(5, '.', -1.99999975700000000e-005) = -1.9998e-5
Maths::RoundToString(5, '.', 1.99999975700000000e-005) = 2.0000e-5
Maths::RoundToString(5, '.', -0.00019999997570000) = -0.00019999
Maths::RoundToString(5, '.', 0.00019999997570000) = 0.00020000
Maths::RoundToString(5, '.', -0.00064058900000000) = -0.00064058
Maths::RoundToString(5, '.', 0.00064058900000000) = 0.00064059
Maths::RoundToString(5, '.', -0.00199999975700000) = -0.0019999
Maths::RoundToString(5, '.', 0.00199999975700000) = 0.0020000
Maths::RoundToString(5, '.', -0.33968999981880188) = -0.33967
Maths::RoundToString(5, '.', 0.33968999981880188) = 0.33968
Maths::RoundToString(5, '.', -0.34000000000000002) = -0.33999
Maths::RoundToString(5, '.', 0.34000000000000002) = 0.34000
Maths::RoundToString(5, '.', -7.07000000000000030) = -7.0699
Maths::RoundToString(5, '.', 7.07000000000000030) = 7.0700
Maths::RoundToString(5, '.', -118.18800000000000000) = -118.18
Maths::RoundToString(5, '.', 118.18800000000000000) = 118.19
Maths::RoundToString(5, '.', -118.20000000000000000) = -118.19
Maths::RoundToString(5, '.', 118.20000000000000000) = 118.20
Maths::RoundToString(5, '.', -123.40500900000001000) = -123.40
Maths::RoundToString(5, '.', 123.40500900000001000) = 123.41
Maths::RoundToString(5, '.', -30.76994323730469100) = -30.768
Maths::RoundToString(5, '.', 30.76994323730469100) = 30.770
Maths::RoundToString(5, '.', -130.76994323730469000) = -130.75
Maths::RoundToString(5, '.', 130.76994323730469000) = 130.77
Maths::RoundToString(5, '.', -540.00000000000000000) = -539.99
Maths::RoundToString(5, '.', 540.00000000000000000) = 540.00
Maths::RoundToString(5, '.', -12345.00000000000000000) = -12344
Maths::RoundToString(5, '.', 12345.00000000000000000) = 12345
Maths::RoundToString(5, '.', -123456.00000000000000000) = -123450
Maths::RoundToString(5, '.', 123456.00000000000000000) = 123460
Maths::RoundToString(5, '.', -540911.00000000000000000) = -540900
Maths::RoundToString(5, '.', 540911.00000000000000000) = 540910
Maths::RoundToString(5, '.', -9.22337203685477560e+056) = -9.2232e56
Maths::RoundToString(5, '.', 9.22337203685477560e+056) = 9.2234e56

如果您有興趣比較 C++ 與 C#,請檢視 C# 程式設計四捨五入數字示例。如果您想比較 C++ 與 Java,請將其與 Java 程式設計四捨五入數字示例 中的四捨五入程式碼進行比較。

華夏公益教科書