自定义流操纵器用于流在任何基地的整数 [英] Custom stream manipulator for streaming integers in any base

查看:101
本文介绍了自定义流操纵器用于流在任何基地的整数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我可以使用十六进制形式的 std :: ostream 对象输出整数,例如

  std :: cout<< std :: hex<< 0xabc; //打印`abc`,而不是base-10表示

所有基地?像

  std :: cout< std :: base(4)<< 20; //我想要输出110 

如果有一个,那么我没有进一步的问题。
如果没有一个,那么我可以写一个吗?是否需要我访问 std :: ostream


的私有实现细节注意,我知道我可以写一个函数,它接受一个数字,并将其转换为一个字符串,它是任何基数中该数字的表示。或者我可以使用已经存在的。



提前感谢

解决方案

div>

您可以执行以下操作。我已经评论了代码来解释每个部分正在做什么,但基本上它的:





这是代码...



编辑:请注意,我不知道我在这里正确处理 std :: ios_base :: internal 标记 - 知道它的用途。



编辑2:我发现了 std :: ios_base :: internal 添加了对的调用 std :: locacle :: global 以显示如何使所有标准流类默认支持新的流操纵器,而不是 imbue

  #include< algorithm> 
#include< cassert>
#include< climits>
#include< iomanip>
#include< iostream>
#include< locale>

命名空间StreamManip {

//定义一个基本操纵器类型,它是什么内置的流操纵器
//当他们采取参数,只有他们返回不透明型。
struct BaseManip
{
int mBase;

BaseManip(int base):mBase(base)
{
assert(base> = 2);
assert(base< = 36);
}

static int getIWord()
{
//调用xalloc一次以获得一个索引,我们可以为此存储数据
//操纵器。
static int iw = std :: ios_base :: xalloc();
return iw;
}

void apply(std :: ostream& os)const
{
//存储操作器中的基本值。
os.iword(getIWord())= mBase
}
};

//我们需要这个,所以我们可以应用我们的自定义流操纵器到流。
std :: ostream& operator<<<(std :: ostream& os,const BaseManip& bm)
{
bm.apply(os);
return os;
}

// convience函数,所以我们可以做std :: cout<<基底(16) 100;
BaseManip base(int b)
{
return BaseManip(b);
}

//自定义输出构面。这些由
// streams中的std :: locale代码使用。 num_put构面将流中的数字值的输出处理为字符
//。在这里我们创建一个知道我们的自定义操纵器。
struct BaseNumPut:std :: num_put< char>
{
//这些absVal函数需要std :: abs不支持
// unsigned类型,但是模板化的doPutHelper适用于signed和
// unsigned类型。
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
{
//读取存储在我们的xalloc位置的值。
const int base = str.iword(BaseManip :: getIWord());

//我们只希望这个操纵器影响下一个数值,所以
//重置它的值。
str.iword(BaseManip :: getIWord())= 0;

//正常数字输出,使用内置推杆。
if(base == 0 || base == 10)
{
return std :: num_put< char> :: do_put(out,str,fill,val);
}

//我们想要收敛基数,所以做它和输出。
//从Nawaz的回答中提取的基本转换代码

int digits [CHAR_BIT * sizeof(NumType)];
int i = 0;
NumType tempVal = absVal(val);

while(tempVal!= 0)
{
digits [i ++] = tempVal%base;
tempVal / = base;
}

//获取格式标志。
const std :: ios_base :: fmtflags flags = str.flags();

//如果需要,添加填充(即它们使用std :: setw)。
//仅适用于我们是右对齐,或没有指定。
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 ++ =' - ';
}

//处理内部调整标志。
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是一个迭代器,接受字符
* out ++ = digitChar [digits [ - i]];
}

//如果需要,添加填充(即它们使用std :: setw)。
//仅适用于左对齐。
if(str.flags()& std :: ios_base :: left)
{
std :: fill_n(out,str.width() - i,fill);
}

//清除宽度
str.width(0);

return out;
}

//覆盖虚拟do_put成员函数。

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,填充,val);
}
};

} //命名空间StreamManip

int main()
{
//创建本地使用我们的自定义num_put
std: :locale myLocale(std :: locale(),new StreamManip :: BaseNumPut());

//将我们的locacle设置为默认情况下在创建的所有流中使用的全局变量
//从这里开始。在此应用程序中创建的任何流都将支持
/ / StreamManip :: base修饰符。
std :: locale :: global(myLocale);

// imbue std :: cout,所以它使用的是自定义本地。
std :: cout.imbue(myLocale);
std :: cerr.imbue(myLocale);

//输出一些东西。
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;
}


I can make an std::ostream object output integer numbers in hex, for example

std::cout << std::hex << 0xabc; //prints `abc`, not the base-10 representation

Is there any manipulator that is universal for all bases? Something like

std::cout << std::base(4) << 20; //I want this to output 110

If there is one, then I have no further question. If there isn't one, then can I write one? Won't it require me to access private implementation details of std::ostream?

Note that I know I can write a function that takes a number and converts it to a string which is the representation of that number in any base. Or I can use one that already exists. I am asking about custom stream manipulators - are they possible?

Thanks in advance

解决方案

You can do something like the following. I have commented the code to explain what each part is doing, but essentially its this:

  • Create a "manipulator" struct which stores some data in the stream using xalloc and iword.
  • Create a custom num_put facet which looks for your manipulator and applies the manipulation.

Here is the code...

Edit: Note that im not sure I handled the std::ios_base::internal flag correctly here - as I dont actually know what its for.

Edit 2: I found out what std::ios_base::internal is for, and updated the code to handle it.

Edit 3: Added a call to std::locacle::global to show how to make all the standard stream classes support the new stream manipulator by default, rather than having to imbue them.

#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;
}

这篇关于自定义流操纵器用于流在任何基地的整数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆