我可以让std::ostream对象以十六进制输出整数,例如
std::cout << std::hex << 0xabc; //prints `abc`, not the base-10 representation有没有对所有基地都通用的操纵器?就像这样
std::cout << std::base(4) << 20; //I want this to output 110如果有的话,我就没有问题了。如果没有,我可以写一个吗?这不需要我访问std::ostream的私有实现细节吗
请注意,我知道我可以编写一个函数,它接受一个数字,并将其转换为一个字符串,该字符串是该数字在任何基数中的表示。或者我可以使用一个已经存在的。我问的是自定义流操纵器--它们是可能的吗?
发布于 2011-06-26 02:11:57
您可以执行类似以下的操作。我注释了代码来解释每个部分都在做什么,但本质上是这样的:
xalloc在流中存储一些数据,并使用iword.num_put方面来查找您的操纵器并应用操作。这是代码。
编辑:请注意,我不确定这里是否正确处理了std::ios_base::internal标志-因为我实际上不知道它的用途。
编辑2:我发现了std::ios_base::internal的用途,并更新了代码来处理它。
编辑3:添加了对std::locacle::global的调用,以展示如何使所有标准流类在默认情况下支持新的流操纵器,而不是必须imbue它们。
#include <algorithm>
#include <cassert>
#include <climits>
#include <iomanip>
#include <iostream>
#include <locale>
namespace StreamManip {
// Define a base manipulator type, its what the built in stream manipulators
// do when they take parameters, only they return an opaque type.
struct BaseManip
{
int mBase;
BaseManip(int base) : mBase(base)
{
assert(base >= 2);
assert(base <= 36);
}
static int getIWord()
{
// call xalloc once to get an index at which we can store data for this
// manipulator.
static int iw = std::ios_base::xalloc();
return iw;
}
void apply(std::ostream& os) const
{
// store the base value in the manipulator.
os.iword(getIWord()) = mBase;
}
};
// We need this so we can apply our custom stream manipulator to the stream.
std::ostream& operator<<(std::ostream& os, const BaseManip& bm)
{
bm.apply(os);
return os;
}
// convience function, so we can do std::cout << base(16) << 100;
BaseManip base(int b)
{
return BaseManip(b);
}
// A custom number output facet. These are used by the std::locale code in
// streams. The num_put facet handles the output of numberic values as characters
// in the stream. Here we create one that knows about our custom manipulator.
struct BaseNumPut : std::num_put<char>
{
// These absVal functions are needed as std::abs doesnt support
// unsigned types, but the templated doPutHelper works on signed and
// unsigned types.
unsigned long int absVal(unsigned long int a) const
{
return a;
}
unsigned long long int absVal(unsigned long long int a) const
{
return a;
}
template <class NumType>
NumType absVal(NumType a) const
{
return std::abs(a);
}
template <class NumType>
iter_type doPutHelper(iter_type out, std::ios_base& str, char_type fill, NumType val) const
{
// Read the value stored in our xalloc location.
const int base = str.iword(BaseManip::getIWord());
// we only want this manipulator to affect the next numeric value, so
// reset its value.
str.iword(BaseManip::getIWord()) = 0;
// normal number output, use the built in putter.
if (base == 0 || base == 10)
{
return std::num_put<char>::do_put(out, str, fill, val);
}
// We want to conver the base, so do it and output.
// Base conversion code lifted from Nawaz's answer
int digits[CHAR_BIT * sizeof(NumType)];
int i = 0;
NumType tempVal = absVal(val);
while (tempVal != 0)
{
digits[i++] = tempVal % base;
tempVal /= base;
}
// Get the format flags.
const std::ios_base::fmtflags flags = str.flags();
// Add the padding if needs by (i.e. they have used std::setw).
// Only applies if we are right aligned, or none specified.
if (flags & std::ios_base::right ||
!(flags & std::ios_base::internal || flags & std::ios_base::left))
{
std::fill_n(out, str.width() - i, fill);
}
if (val < 0)
{
*out++ = '-';
}
// Handle the internal adjustment flag.
if (flags & std::ios_base::internal)
{
std::fill_n(out, str.width() - i, fill);
}
char digitCharLc[] = "0123456789abcdefghijklmnopqrstuvwxyz";
char digitCharUc[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *digitChar = (str.flags() & std::ios_base::uppercase)
? digitCharUc
: digitCharLc;
while (i)
{
// out is an iterator that accepts characters
*out++ = digitChar[digits[--i]];
}
// Add the padding if needs by (i.e. they have used std::setw).
// Only applies if we are left aligned.
if (str.flags() & std::ios_base::left)
{
std::fill_n(out, str.width() - i, fill);
}
// clear the width
str.width(0);
return out;
}
// Overrides for the virtual do_put member functions.
iter_type do_put(iter_type out, std::ios_base& str, char_type fill, long val) const
{
return doPutHelper(out, str, fill, val);
}
iter_type do_put(iter_type out, std::ios_base& str, char_type fill, unsigned long val) const
{
return doPutHelper(out, str, fill, val);
}
};
} // namespace StreamManip
int main()
{
// Create a local the uses our custom num_put
std::locale myLocale(std::locale(), new StreamManip::BaseNumPut());
// Set our locacle to the global one used by default in all streams created
// from here on in. Any streams created in this app will now support the
// StreamManip::base modifier.
std::locale::global(myLocale);
// imbue std::cout, so it uses are custom local.
std::cout.imbue(myLocale);
std::cerr.imbue(myLocale);
// Output some stuff.
std::cout << std::setw(50) << StreamManip::base(2) << std::internal << -255 << std::endl;
std::cout << StreamManip::base(4) << 255 << std::endl;
std::cout << StreamManip::base(8) << 255 << std::endl;
std::cout << StreamManip::base(10) << 255 << std::endl;
std::cout << std::uppercase << StreamManip::base(16) << 255 << std::endl;
return 0;
}发布于 2011-06-25 23:40:54
自定义操纵器确实是可能的。有关示例this question,请参阅。我不熟悉任何通用基础的具体实例。
发布于 2011-06-26 00:40:19
你真的有两个独立的问题。我想你问的这个问题是完全可以解决的。不幸的是,另一种情况就没那么严重了。
分配和使用流中的一些空间来保存一些流状态是一个可以预见的问题。Streams有几个成员(xalloc、iword、pword),它们允许您在流中的数组中分配一个点,并在那里读/写数据。因此,流操纵器本身是完全可能的。基本上是使用xalloc在流的数组中分配一个点来保存当前的基数,插入操作符在转换数字时将使用这个基数。
我看不到解决方案的问题相当简单:标准库已经提供了一个operator<<来将int插入到流中,而且它显然不知道您的假设数据来保存转换的基础。您不能重载它,因为它需要与现有签名完全相同的签名,因此您的重载将是不明确的。
但是,int、short等的重载是重载的成员函数。我猜如果你非常想这样做,你可以使用一个模板来重载operator<<。如果我没记错的话,这比库提供的与非模板函数的精确匹配更可取。您仍然违反了规则,但是如果您将这样一个模板放在名称空间std中,那么它至少有一些机会可以工作。
https://stackoverflow.com/questions/6478745
复制相似问题