这个问题有两个独立的部分:
- 问题的无聊部分是如何将 an
int
转换为具有值的罗马表示的字符序列。
- 如何截取 的输出,
int
并将其变成刚刚描述的序列。
罗马数字遵循相当直接的规则,使用简单的查找表似乎最容易处理。由于问题的主要焦点是如何使其与 IOStreams 一起工作,因此使用了一种直接的算法:
template <typename To>
To make_roman(int value, To to) {
if (value < 1 || 3999 < value) {
throw std::range_error("int out of range for a Roman numeral");
}
static std::string const digits[4][10] = {
{ "", "M", "MM", "MMM", "", "", "", "", "", "" },
{ "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" },
{ "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" },
{ "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" },
};
for (int i(0), factor(1000); i != 4; ++i, factor /= 10) {
std::string const& s(digits[i][(value / factor) % 10]);
to = std::copy(s.begin(), s.end(), to);
}
return to;
}
每个“数字”都是通过查找相应的字符串并将其复制到迭代器来简单生成的。如果整数超出可以使用罗马数字表示的值的范围,则会引发异常。可以生成的最长字符串为 15 个字符长 (3888)。
下一步是设置std::cout
它int
使用上述转换格式化 s。当 anstd::ostream
需要转换任何内置数字类型(整数、浮点数)或类型bool
时void const*
,它会从流中获取std::num_put<cT>
facetstd::locale
并调用put()
对象,本质上是使用
std::use_facet<std::num_put<cT>>(s.getloc())
.put(std::ostreambuf_iterator<char>(s), s, s.fill(), value);
通过派生std::num_put<char>
和覆盖do_put()
以 a 作为参数的版本的成员函数,long
可以更改数字的格式:
class num_put
: public std::num_put<char>
{
iter_type do_put(iter_type to, std::ios_base& fmt, char fill, long v) const {
char buffer[16];
char* end(make_roman(v, buffer));
std::streamsize len(end - buffer);
std::streamsize width(std::max(fmt.width(0), len));
std::streamsize fc(width - (end - buffer));
switch (fmt.flags() & std::ios_base::adjustfield) {
default:
case std::ios_base::left:
to = std::copy(buffer, end, to);
to = std::fill_n(to, fc, fill);
break;
case std::ios_base::right:
case std::ios_base::internal:
to = std::fill_n(to, fc, fill);
to = std::copy(buffer, end, to);
}
return to;
}
};
尽管该函数相对较长,但它相当简单:
- 该值
v
被转换为罗马数字的字符串并存储在buffer
.
- 结果字符串的长度和要生成的字符数被确定(并且流
width()
被重置为0
)。
- 根据输出的对齐位置,要么复制值,然后存储填充字符(如果有),要么反过来。
剩下的就是std::locale
使用这个版本的std::num_put<char>
构面创建一个并将结果安装std::locale
到std::cout
:
std::cout.imbue(std::locale(std::cout.getloc(), new num_put));
std::cout << "year " << 2013 << '\n';
这是一个实时示例,显示了具有不同对齐方式的几个不同值。该示例还实现了所有四个整数版本do_put()
(即 for long
、long long
、unsigned long
和unsigned long long
)。