简单的类型安全format输出

boost就有一个实现类型安全format的类,不过类比较庞大,而且也比较复杂,用起来也不是很习惯。
所以还是自己简单实现一个了。
    先看看需求:format_string.format("[%] = %") % a % strName
    其实就是希望后面的a和strName分别替代格式化字符串"[%] = %"中的两个%号,相当于:
    stringstream format_string;
    format_string << "[" << a << "] = " << strName;

    当然,要做到通用,还是希望不单能输出到stringstream,最好还是能输出到std::ostream。
    class format_stream
    {
    public:
        explicit format_stream(std::ostream & outS) : m_output(outS),m_lpszFormat(&g_nEndFlags)
        {
        }

        inline format_stream & format(const char * lpszFormat)
        {
            flushFormat();
            m_lpszFormat = lpszFormat;
            return outputPrefix();
       }
       
        template<typename typeArg1>
        inline format_stream & arg(const typeArg1 & val) {
                getOutput() << val;
                return outputPrefix();
        }

       ~format_stream(void) {}
    protected:
        inline std::ostream & getOutput(void)
        {
                return m_output;
        }
        void    flushFormat(void)
        {
                if (*m_lpszFormat)
                {
                        getOutput() << m_lpszFormat;
                        m_lpszFormat = &g_nEndFlags;
                }      
        }
        format_stream & outputPrefixLoop(void);
        format_stream & outputPrefix(void);
    
        static  char    g_nEndFlags;
        std::ostream &  m_output;
        const char *    m_lpszFormat;
    };
       
    char format_stream::g_nEndFlags = char();

    format_stream & format_stream::outputPrefix(void)
    {
        char * lpPos = strchr(m_lpszFormat,'%');
        if (lpPos != NULL)
        {
                getOutput().write(m_lpszFormat, lpPos - m_lpszFormat);
                m_lpszFormat = lpPos + 1;
                if (*m_lpszFormat == '%' && *(m_lpszFormat + 1) != '%')
                        return outputPrefixLoop();
        } // if (lpPos != NULL)
        else
                flushFormat();
        return *this;
    }

    format_stream & format_stream::outputPrefixLoop(void)  
    {
        while (*m_lpszFormat == '%')
        {
                char * lpPos = strchr(m_lpszFormat + 1, '%');
                if (lpPos != NULL)
                {
                        getOutput().write(m_lpszFormat, lpPos - m_lpszFormat);
                        m_lpszFormat = lpPos + 1;
                        if (*m_lpszFormat != '%' || *(m_lpszFormat + 1) == '%')
                                break;
                } // if (lpPos != NULL)
                else
                {
                        flushFormat();
                        break;
                }
        } // while (*m_lpszFormat)
        return *this;
    }

    1、规定以'%'作为占位符,表示后续的变量替换的位置。
       
    2、两个连续'%'即 '%%'表示一个真正的'%'。不过需要注意的是:一般想输出百分数的时候,就是要写%%%,
分析程序发现三个连续'%',则认为第一个是占位符,后两个表示一个'%'。而发现四个'%'的时候,前两个都会被认
为是占位符,最后两个被认为是一个'%'。

    3、boost用%连接后面的多个变量,也有些类库使用逗号。个人都不是很喜欢:'%'用得太多,程序看起来不好
看;很多书都再三声明最好不要重载逗号运算符。所以还是使用函数比较稳妥,所以就用函数arg(a)的形式。如果真
的喜欢使用'%'或者逗号,只需要增加成员函数:
        template<typename typeArg1>
            inline format_stream & operator%(const typeArg1 & val) { return arg(val); }
        template<typename typeArg1>
            inline format_stream & operator,(const typeArg1 & val) { return arg(val); }
               
    4、arg还可以继续扩展,
        a、同一个占位符输出多个变量,只需要增加多几个成员函数:
        template<typename typeArg1,typename typeArg2>
        inline format_stream & arg(const typeArg1 & val1,const typeArg2 & val2)
        {
                getOutput() << val << val2;
                return outputPrefix();
        }
        template<typename typeArg1,typename typeArg2,typename typeArg3>
        inline format_stream & arg(const typeArg1 & val1,
                                const typeArg2 & val2,
                                const typeArg3 & val3)
        {
                getOutput() << val1 << val2 << val3;
                return outputPrefix();
        }
        例如有时候想输出一个范围:
       
        stream.format("range1:% range2:%").arg(lowerbound1,'-',upperbound1);
        stream.arg(lowerbound2,'~',upperbound2)
       
        b、格式化输出。printf那么多的格式化输出参数写在格式化字符串中,我老是会记错,一旦写错
        程序就容易出问题了(不单是显示错误,还有可能会coredump),另外发现不同平台格式还会有
        些不一样。还是写在arg里面比较稳妥,而且程序也容易阅读。
       
        为了和上面"同一个占位符输出多个变量"的函数区分,还是另外取一个函数名:
       
        enum    INT_BASE
                {BASE10=std::ios::dec, BASE8=std::ios::oct, BASE16=std::ios::hex};
        enum    CHAR_CASE
                {CHAR_UPCASE=0, CHAR_LOWCASE=1 };
        enum    BASE_FLAG
                {HIDE_BASE=0, SHOW_BASE=1 };
        enum    POS_FLAG
                {HIDE_POS=0, SHOW_POS=1 };
        enum    FIX_FLAG
                { FIXED=0, SCIENTIFIC=1 };
        enum    POINT_FLAG
                { HIDE_POINT=0, SHOW_POINT=1 };
        enum    ADJUSTFIELD_FLAG
        {
                ADJUST_LEFT=std::ios::left,
                ADJUST_RIGHT=std::ios::right,
                ADJUST_INTERNAL=std::ios::internal
        };

        template<typename typeInt>
                format_stream & argWithFormat(typeInt nVal,
                        INT_BASE nBase = BASE10,
                        CHAR_CASE bUpcase = CHAR_UPCASE,
                        POS_FLAG bShowPos = HIDE_POS,
                        BASE_FLAG bShowBase = HIDE_BASE,
                        ADJUSTFIELD_FLAG nAdjust= ADJUST_LEFT,
                        int nWidth = -1,char chFill = ' ')
        {
                std::ios::fmtflags nFlags = getOutput().flags();
                getOutput().setf((std::ios::fmtflags)nBase, std::ios::basefield);
                if (bShowPos == SHOW_POS)
                        getOutput().setf(std::ios::showpos);
                else
                        getOutput().unsetf(std::ios::showpos);
                if (bUpcase == CHAR_UPCASE)
                        getOutput().setf(std::ios::uppercase);
                else
                        getOutput().unsetf(std::ios::uppercase);
                if (bShowBase == SHOW_BASE)
                        getOutput().setf(std::ios::showbase);
                else
                        getOutput().unsetf(std::ios::showbase);
                getOutput().setf((std::ios::fmtflags)nAdjust, std::ios::adjustfield);
                if (nWidth != -1)
                        nWidth = getOutput().width(nWidth);
                chFill = getOutput().fill(chFill);

                getOutput() << static_cast<typeInt>(nVal);

                getOutput().flags(nFlags);
                if (nWidth != -1)
                        getOutput().width(nWidth);
                getOutput().fill(chFill);

                return outputPrefix();
        }
        还可以增加浮点数、字符串等等的格式处理。
               
    5、现在输入的格式字符串,在类里面只是使用const char * m_lpszFormat来保存,一旦传入的
lpszFormat所指的资源已经被释放,则会造成非法内存访问。例如:
    std::string     getString(void) { ..... }
    stream.format(getString().c_str());
    stream.arg(...);
    如果要避免这种情况,应该在format_stream里面增加一个std::string成员,记录字符串指针。
又或者应该写成:
    std::string strTemp = getString();
    stream.format(strTemp.c_str());
    stream.arg(...)
应该如何处理,还是看各人的习惯和需求了。
       
    6、当输入参数比占位符多的时候,则多余的参数都会输出到格式字符串的后面。
    7、当输入参数比占位符少的时候,输出则停留在第一个多余的占位符的前面。
    8、对于字符串,可以使用stringstream来辅助:
    stringstream str;
    format_stream fmt(str);
    fmt.format(".....").arg(...);
    myFun(str.str());
    当然,经过适当的改造,可以使类更方便使用,这里就不再多说,各位高手自己发挥了。

阅读更多
个人分类: C/C++
上一篇是否真的那么快?
下一篇flex_string和SlimStringStorage
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭