13.4 读取货币值
问题
如何从一个流中读或写一个已格式化的货币值?
解决方案
使用money_put和money_get facet来写和读货币值,如示例13-6所示。
示例13-6 写和读货币
#include <iostream>
#include <locale>
#include <string>
#include <sstream>
using namespace std;
long double readMoney(istream& in, bool intl = false) {
long double val;
// Create a reader facet
const money_get<char>& moneyReader =
use_facet<money_get<char> >(in.getloc());
// End marker
istreambuf_iterator<char> end;
// State variable for detecting errors
ios_base::iostate state = 0;
moneyReader.get(in, end, intl, in, state, val);
// failbit will be set if something went wrong
if (state != 0 && !(state & ios_base::eofbit))
throw "Couldn't read money!\n";
return(val);
}
void writeMoney(ostream& out, long double val, bool intl = false) {
// Create a writer facet
const money_put<char>& moneyWriter =
use_facet<money_put<char> >(out.getloc());
// Write to the stream. Call failed() (the return value is an
// ostreambuf_iterator) to see if anything went wrong.
if (moneyWriter.put(out, intl, out, out.fill(), val).failed())
throw "Couldn't write money!\n";
}
int main() {
long double val = 0;
float exchangeRate = 0.775434f; // Dollars to Euros
locale locEn("english");
locale locFr("french");
cout << "Dollars: ";
cin.imbue(locEn);
val = readMoney(cin, false);
cout.imbue(locFr);
// Set the showbase flag so the currency char is printed
cout.setf(ios_base::showbase);
cout << "Euros: ";
writeMoney(cout, val * exchangeRate, true);
}
如果你运行13-6,你的输出如下所示:
Dollars: $100
Euros: EUR77,54
讨论
这个money_put和money_get facet分别向一个流写入格式化的货币值或从从一个流中读货币值。它们和前几节中描述的date/time和数字的facet工作方式相同。标准需要提供一个窄字符或宽字符的示例,如money_put<char>和money_put<wchar_t>。就像其他的facet一样,这个get和put函数也是冗长的,但是一旦你多使用几次以后,它们的参数也是容易记住的。money_get和money_put都是使用一个moneypunct的类来存储格式化的信息。
首先,让我们来讨论向一个流写入货币值。货币值的显示涉及到几个方面:货币符号、正或者负的符号、千位分隔符和小数点。除了小数点之外的这些都是可选的。
你可以使用一个字符类型和一个locale来创建一个money_put,如下所示:
const money_put<char>& moneyWriter =
use_facet<money_put<char> >(out.getloc());
money_put的char和wchar_t版本都是C++标准要求的。当你向一个流写入时使用与这个流相同的locale是一个好的主意,这样可以避免这个流和这个money_put写入的对象不匹配。接下来,调用put方法来把货币值写入输出流:
if (moneyWriter.put(out, // Output iterator
intl, // bool: use intl format?
out, // ostream&
out.fill(), // fill char to use
val) // currency value as long double
.failed())
throw "Couldn't write money!\n";
money_put::put把日期写入输出流,这个输出流是你使用这个locale传递的,并且这个money_put对象也是你通过这个locale创建的。money_put::put返回一个指向最后一个字符输出之后的ostreambuf_iterator,这个ostreambuf_iterator有一个成员函数,并且你可以使用这个成员函数来检测这个迭代器是否处在一个被损坏的状态中。
money_put::put的参数都是自解释的,或许除了第二个之外(在这个例子中是这个intl参数)。它是一个布尔型值,用来确定是否使用货币符号(如$)还是使用国际化的三字符代码(如 USD、EUR)。设置为false则是使用货币符号,设置成true则是使用国际化的三字符代码。
把货币输出到一个输出流中须遵循这个流上的一些格式标志。下面就是每个标志和它作用在货币上的效果:
ios_base::internal
在这个货币格式中无论是有空格还是没有任何东西,填充字符都会被使用(不是空格字符)。更多的关于使用格式的模式信息请参考下面有关moneypunct的讨论。
ios_base::left和ios_base::right
导致货币值左对齐或右对齐,对齐过程中达到宽度值的余下空格用填充字符填充(参考下面的宽度描述)。这是方便的,因为它使得用表格格式化货币变得容易。
ios_base::width
money_put值为流中字段宽度遵循标准规则。默认的情况下,值是左对齐的。如果字段比这个值大,money_put的填充字符就会使用。
ios_base::showbase
当这个值为true时,货币符号就会打印出来;否则,不打印。
就像我在前面说的那样,money_get和money_put都是用一个moneypunct的类,事实上是它来存储格式信息。你不需要考虑这个moneypunct类,除非你正在实现一个标准库,但是你可以用它来为某个特殊的locale探查格式信息。moneypunct类包含的信息有使用的货币符号、为小数点使用的字符、正值和负值格式等等。示例13-7中为某个给定的locale呈现了一个打印货币格式信息的简短程序。
示例13-7 打印货币信息
#include <iostream>
#include <locale>
#include <string>
using namespace std;
string printPattern(moneypunct<char>::pattern& pat) {
string s(pat.field); // pat.field is a char[4]
string r;
for (int i = 0; i < 4; ++i) {
switch (s[i]) {
case moneypunct<char>::sign:
r += "sign ";
break;
case moneypunct<char>::none:
r += "none ";
break;
case moneypunct<char>::space:
r += "space ";
break;
case moneypunct<char>::value:
r += "value ";
break;
case moneypunct<char>::symbol:
r += "symbol ";
break;
}
}
return(r);
}
int main() {
locale loc("danish");
const moneypunct<char>& punct =
use_facet<moneypunct<char> >(loc);
cout << "Decimal point: " << punct.decimal_point() << '\n'
<< "Thousands separator: " << punct.thousands_sep() << '\n'
<< "Currency symbol: " << punct.curr_symbol() << '\n'
<< "Positive sign: " << punct.positive_sign() << '\n'
<< "Negative sign: " << punct.negative_sign() << '\n'
<< "Fractional digits: " << punct.frac_digits() << '\n'
<< "Positive format:"
<< printPattern(punct.pos_format()) << '\n'
<< "Negative format:"
<< printPattern(punct.neg_format()) << '\n';
// Grouping is represented by a string of chars, but the meaning
// of each char is its integer value, not the char it represents.
string s = punct.grouping();
for (string::iterator p = s.begin(); p != s.end(); ++p)
cout << "Groups of: " << (int)*p << '\n';
}
这些方法的大部分都是自说明的,但是还是有一些需要进一步的说明。首先,这个grouping方法返回一个字符串,这个字符串解释为一串整型数。每个字符代表一个整型数对应的特殊索引的组,这个组从这个数的右边开始。如果对应这个索引没有值的话,就使用紧挨着这个索引的前面那个索引对应的值。换句话说,对于标准的美国格式,在这个字符串中,索引0处具有的值为3,也就意味着在索引0处,这些数字应该按照三个一组这样来分组。由于没有更多的值,也就意味着所有比0大的索引也应该是使用三个一组来分组。
pos_format和neg_format返回一个类型为moneypunct<T>::pattern的对象,它有一个T[4]的成员字段,这个T是一个字符类型。这个字段中的每一个元素都是枚举类型moneypunct<T>::part当中的一个枚举值,这个枚举类型有五个可能的值:none、space、symbol、sign和value。一个代表货币的字符串有四部分(因此这个数组的长度是四)。典型地,这些部分是货币符号、空格、正负值符号、值,如$ -32.00。常常,正值符号是一个空串,因为一个没有符号的值总是被认为是正的。负值符号可以多于一个字符,如“( )”,在这种情况下,第一个字符打印在这个货币符号在neg_format格式中出现的位置,并且其余的打印在后面,因此你可以认为$(32.00)是一个负值。
大部分时间你不需要担心这个存储在moneypunct中的格式信息。但是你如果需要在不同的locale中处理很多货币格式的话,那么做一些试验来验证不同的locale中是如何格式化的就会很有价值。
请参考13.2节和13.3节。






