C++ 编程/代码/IO
也常被称为 C++ 标准库 的 C++ I/O,因为库还包括 C 标准库及其 I/O 实现,如之前在 标准 C I/O 部分 所示。
输入 和 输出 对任何计算机软件都是必不可少的,因为它们是程序与用户通信的唯一途径。最简单的输入/输出形式是纯文本的,即应用程序以控制台形式显示,使用简单的 ASCII 字符提示用户输入,用户使用键盘提供输入。
程序可以通过多种方式获得输入和输出,包括
- 文件 I/O,即读写文件
- 控制台 I/O,读写控制台窗口,如基于 UNIX 的操作系统中的终端或 Windows 中的 DOS 提示符。
- 网络 I/O,从网络设备读写
- 字符串 I/O,将字符串视为输入或输出设备进行读写
虽然这些看起来可能不相关,但它们的工作方式非常相似。实际上,遵循 POSIX 规范的操作系统使用一种类型的句柄来处理文件、设备、网络套接字、控制台以及许多其他东西,即文件描述符。但是,操作系统提供的低级接口往往难以使用,因此 C++ 与其他语言一样,提供了抽象来简化编程。这种抽象是 流。
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;
}
几乎所有输入和输出都可以非常有效地建模为流。拥有一个通用模型意味着只需要学习一次。如果你了解流,你就了解了如何输出到文件、屏幕、套接字、管道以及任何其他可能出现的东西的基础知识。
流是一个对象,允许我们按顺序将数据推入或推出介质。通常,流只能输出或只能输入。可以有一个既能输出又能输入的流,但这很少见。我们可以将流想象成一辆汽车沿着信息的一条单行道行驶。输出流可以插入数据并继续前进。它(通常)不能返回并调整已经写入的内容。同样,输入流可以读取下一位数据,然后等待接下来的数据。它不会跳过数据,也不会倒带查看五分钟前读取的内容。
流的读写操作的语义取决于流的类型。在文件的情况下,输入文件流按顺序读取文件的内容,不会倒带,输出文件流按顺序写入文件。对于控制台流,输出表示显示文本,输入表示通过控制台从用户那里获取输入。如果用户没有输入任何内容,则程序会 阻塞 或等待用户输入内容。
iostream
是用于输入/输出的 头文件。它是 C++ 标准库的一部分。该名称代表 Input/Output Stream。在 C++ 中,没有用于流式传输数据输入或输出的特殊语法。相反,这些被合并为一个 库 函数。正如我们已经使用 C 标准库使用 <cstdio>
头文件 一样,iostream
为 I/O 提供基本 OOP 服务。
iostream
自动定义并使用一些标准对象
cin
,istream 类的对象,从标准输入设备读取数据。cout
,ostream 类的对象,将数据显示到标准输出设备。cerr
,ostream 类的另一个对象,将未缓冲的输出写入标准错误设备。clog
,与 cerr 相似,但使用缓冲输出。
用于将数据发送到 标准流 输入、输出、错误(未缓冲)和错误(缓冲)分别进行。作为 C++ 标准库的一部分,这些对象是 std 命名空间 的一部分。
- 标准输入、输出和错误
最常用的流是 cout、cin 和 cerr(分别读作“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::cout
和 std::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
[edit | edit source]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 |
类描述了输入和输出流共有的存储和成员函数,这些函数不依赖于模板参数。 |
与cout和cin一起,我们可以进行与用户的基本通信。对于更复杂的 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()
的详细信息,请参见参考文档。
从流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_iX
和 m_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 编程四舍五入数字示例 中的四舍五入代码进行比较。