【问题标题】:Custom stream manipulator for streaming integers in any base用于在任何基础上流式传输整数的自定义流操纵器
【发布时间】:2011-09-22 15:40:07
【问题描述】:

我可以让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 的私有实现细节吗?

请注意,我知道我可以编写一个函数,该函数接受一个数字并将其转换为一个字符串,该字符串是该数字在任何基数中的表示形式。或者我可以使用一个已经存在的。我在问自定义流操纵器 - 它们可能吗?

【问题讨论】:

  • @Armen:我写了一个代码,但是当你写std::cout &lt;&lt; std::base(4) &lt;&lt; 20 时,它不会打印50。你能解释一下为什么要50 作为输出吗?我的程序改为打印110。我有点困惑,所以我没有发布我的代码作为答案。
  • @Nawaz:当然,"50" 不是有效的以 4 为基数的数字,因为唯一允许的数字是 0-3。
  • @Ben:是的。这就是混乱的根源。我认为@Armen 算错了,快点!
  • @Armen:如果你把主题改成Custom stream manipulator for streaming integers in any base,或者更具描述性的东西会更好。但想法是,标题应该包含Custom stream manipulator短语。

标签: c++ iostream


【解决方案1】:

您可以执行以下操作。我已经注释了代码以解释每个部分在做什么,但本质上它是这样的:

  • 创建一个“操纵器”结构,该结构使用xallociword 在流中存储一些数据。
  • 创建一个自定义的num_put facet,用于查找您的操纵器并应用操纵。

这里是代码...

编辑:请注意,我不确定我在这里正确处理了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;
}

【讨论】:

  • @Ben - 谢谢,作为图书馆的一部分完成一次,它并没有太多的工作。但是,是的,使用更简单的代码也可以达到同样的效果,我主要是想展示您可以制作自定义流操纵器。
  • 警告:此代码有一些错误: 1. 不输出“0”。 2. 不计算填充的减号。 3. 左对齐的填充使用“i”,它已经倒数到0。
【解决方案2】:

自定义操纵器确实是可能的。参见例如this question。我不熟悉任何特定的通用基础。

【讨论】:

    【解决方案3】:

    你真的有两个不同的问题。我认为您要问的问题是完全可以解决的。不幸的是,另一个则要少得多。

    在流中分配和使用一些空间来保存一些流状态是可以预见的问题。流有几个成员(xallociwordpword),可以让您在流中的数组中分配一个点,并在那里读/写数据。因此,流操纵器本身是完全可能的。您基本上可以使用xalloc 在流的数组中分配一个点来保存当前基数,供插入运算符在转换数字时使用。

    我没有看到解决方案的问题相当简单:标准库已经提供了一个 operator&lt;&lt; 来将一个 int 插入到流中,而且它显然 知道关于您的假设数据,以保持转换的基础。你不能重载它,因为它需要与现有签名完全相同的签名,所以你的重载会模棱两可。

    但是,intshort 等的重载是重载的成员函数。我如果你非常想要,你可以使用模板来重载operator&lt;&lt;。如果我没记错的话,这将比库提供的与非模板函数的完全匹配更可取。您仍然会违反规则,但是如果您将这样的模板放在命名空间 std 中,那么它至少有一些可能会起作用。

    【讨论】:

      【解决方案4】:

      我尝试编写代码,但它的工作存在一些限制。它不是流操纵器,因为这根本不可能,正如其他人(尤其是@Jerry)所指出的那样。

      这是我的代码:

      struct base
      {
         mutable std::ostream *_out;
         int _value;
      
         base(int value=10) : _value(value) {}
      
         template<typename T>
         const base& operator << (const T & data) const
         {
              *_out << data;
              return *this;
         }
         const base& operator << (const int & data) const
         {
              switch(_value)
              {
                  case 2:  
                  case 4:  
                  case 8:  return print(data);
                  case 16: *_out << std::hex << data; break;
                  default:  *_out << data; 
              }
              return *this;
         }
         const base & print(int data) const
         {
              int digits[CHAR_BIT * sizeof(int)], i = 0;
              while(data)
              {
                   digits[i++] = data % _value;  
                   data /= _value;
              }
              while(i) *_out << digits[--i] ;
              return *this;
         }
         friend const base& operator <<(std::ostream& out, const base& b)   
         {
             b._out = &out;
             return b;
         }
      };
      

      这是测试代码:

      int main() {
         std::cout << base(2) << 255 <<", " << 54 << ", " << 20<< "\n";
         std::cout << base(4) << 255 <<", " << 54 << ", " << 20<< "\n";
         std::cout << base(8) << 255 <<", " << 54 << ", " << 20<< "\n";
         std::cout << base(16) << 255 <<", " << 54 << ", " << 20<< "\n";
      }
      

      输出:

      11111111, 110110, 10100
      3333, 312, 110
      377, 66, 24
      ff, 36, 14
      

      在线演示:http://www.ideone.com/BWhW5

      限制:

      • 基础不能更改两次。所以这将是一个错误:

        std::cout << base(4) << 879 << base(8) << 9878 ; //error
        
      • 使用base后无法使用其他机械手:

        std::cout << base(4) << 879 << std::hex << 9878 ; //error
        std::cout << std::hex << 879 << base(8) << 9878 ; //ok
        
      • std::endl使用base后不能使用:

        std::cout << base(4) << 879 << std::endl ; //error
        //that is why I used "\n" in the test code.
        

      【讨论】:

      • @gigantt 的链接提供了相同的功能,但限制要少得多。
      • +1 这大概就是我会做的。您可以通过重载operator&lt;&lt;(const base&amp;) 轻松添加更改碱基的功能,并可能通过重载operator&lt;&lt;(ostream&amp;(*)(ostream&amp;))operator&lt;&lt;(ios&amp;(*)(ios&amp;))operator&lt;&lt;(ios_base&amp;(*)(ios_base&amp;)) 来解决操纵器问题。
      • @Jon:好主意。有时间我会做的。谢谢。 :-)
      【解决方案5】:

      我不认为任意流的语法是可能的(使用操纵器,@gigantt 链接了一个显示一些替代非操纵器解决方案的答案)。标准操纵器仅设置在流内部实现的选项。

      OTOH,你当然可以使这个语法工作:

      std::cout << base(4, 20);
      

      其中base 是一个提供流插入操作符的对象(无需返回临时的string)。

      【讨论】:

      • +1:这无疑是最强大、最简单、最通用的解决方案。
      • 您可以使用xallociword 和自定义方面制作自定义流操纵器。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-11
      • 2020-02-22
      • 1970-01-01
      • 2010-10-22
      • 1970-01-01
      • 2016-09-14
      相关资源
      最近更新 更多