简单的类型安全format输出

博客展示了format_stream类的代码实现,包含format、arg等函数,以及getOutput、flushFormat等方法,还涉及outputPrefix和outputPrefixLoop函数的具体逻辑,主要围绕格式化流的输出操作展开。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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());
    当然,经过适当的改造,可以使类更方便使用,这里就不再多说,各位高手自己发挥了。

<think>嗯,用户之前已经问过关于Python的format函数的问题,现在他们又问“format 格式化输出”,可能想更深入地了解格式化输出的各个方面,或者需要更多的例子和应用场景。首先,我需要确认用户是否已经掌握了之前介绍的基础知识,或者是否需要更详细的内容。 在之前的回答中,我已经覆盖了format函数的基础用法、高级格式化、嵌套格式化、动态参数等。现在用户再次询问,可能希望得到更系统或更全面的解释,或者想了解与其他格式化方法的对比,比如%格式化或者f-string。 接下来,我需要考虑用户可能的背景。他们可能是刚开始学习Python的新手,所以需要确保解释清晰,步骤详细,避免使用过于专业的术语。同时,用户可能在实际编程中遇到了具体问题,比如输出对齐、数字格式处理等,需要实际例子来解决这些问题。 另外,用户可能没有提到他们具体遇到的困难,所以我需要涵盖format的各种常见用法,并强调注意事项和最佳实践。比如,参数数量的匹配、格式规范语法,以及与f-string的对比,这些内容对用户理解不同方法的优缺点可能有帮助。 还要注意结构清晰,分点说明,这样用户更容易查找所需的信息。可能需要加入更多的示例代码,尤其是实际应用场景,比如表格输出、报告生成,这样用户可以看到如何在实际项目中应用这些知识。 最后,检查是否有遗漏的重要点,比如类型转换中的不同进制、日期格式化,或者处理特殊字符的情况。确保覆盖所有用户可能需要的内容,同时保持回答的简洁和易读性。</think>以下是Python中`format()`函数实现格式化输出的完整指南,内容分为基础到进阶的层级结构: --- ### **一、基础占位符机制** 1. **空占位符**(自动按顺序匹配参数) ```python print("{}的{}成绩是{}分".format("小明", "数学", 95)) # 输出:小明的数学成绩是95分 ``` 2. **索引定位**(支持重复使用参数) ```python print("{1}看见{0},{0}在吃东西".format("小猫", "小明")) # 输出:小明看见小猫,小猫在吃东西 ``` 3. **关键字定位** ```python print("坐标:x={x}, y={y}".format(x=3.14, y=2.718)) # 输出:坐标:x=3.14, y=2.718 ``` --- ### **二、格式控制符号** 通过`:`后的格式描述符实现精准控制,完整语法: `[[fill]align][sign][#][0][width][grouping_option][.precision][type]` #### **1. 数字格式化** | 类型符 | 功能 | 示例 | 输出结果 | |--------|----------------------|--------------------------|---------------| | `d` | 十进制整数 | `"{:d}".format(42)` | `42` | | `f` | 浮点数 | `"{:.2f}".format(3.141)` | `3.14` | | `%` | 百分比格式 | `"{:.1%}".format(0.85)` | `85.0%` | | `e` | 科学计数法 | `"{:.2e}".format(1234)` | `1.23e+03` | | `b` | 二进制 | `"{:b}".format(10)` | `1010` | | `x` | 十六进制小写 | `"{:x}".format(255)` | `ff` | | `X` | 十六进制大写 | `"{:X}".format(255)` | `FF` | #### **2. 对齐与填充** ```python print("{:*>8}".format("ID")) # 右对齐:******ID print("{:0<6}".format(3.14)) # 左对齐:3.1400 print("{:^10}".format("中")) # 居中: 中 ``` #### **3. 符号控制** ```python print("{:+} {:+}".format(5, -5)) # +5 -5 print("{:-} {:-}".format(5, -5)) # 5 -5 (默认行为) print("{: } {: }".format(5, -5)) # 5 -5 (正数留空) ``` --- ### **三、复杂场景应用** #### **1. 嵌套字典格式化** ```python data = {"name": "Alice", "scores": {"math":90, "english":85}} print("{name}的数学:{scores[math]}".format(**data)) # Alice的数学:90 ``` #### **2. 列表参数展开** ```python params = ["Python", 3.9] print("语言:{} 版本:{}".format(*params)) # 语言:Python 版本:3.9 ``` #### **3. 动态格式控制** ```python precision = 3 print("结果:{value:.{p}f}".format(value=2.71828, p=precision)) # 结果:2.718 ``` --- ### **四、特殊格式处理** #### **1. 千分位分隔符** ```python print("{:,}".format(123456789)) # 123,456,789 print("{:_x}".format(0xdeadbeef))# 输出:dead_beef (十六进制分隔) ``` #### **2. 日期时间格式化**(需配合datetime模块) ```python from datetime import datetime print("{:%Y-%m-%d %H:%M}".format(datetime.now())) # 2023-08-15 14:30 ``` #### **3. 转义大括号** ```python print("{{需要转义}} → {}".format("这里显示")) # {需要转义} → 这里显示 ``` --- ### **五、与其他格式化方式对比** | 特性 | % 格式化 | `str.format()` | f-string (Python 3.6+) | |--------------------|----------------|-----------------------|------------------------| | 语法直观性 | 一般 | 良好 | 优秀 | | 动态格式化能力 | 有限 | 强大 | 一般 | | 类型安全 | 弱 | 较强 | 强 | | 执行速度 | 较慢 | 中等 | 最快 | --- ### **六、最佳实践建议** 1. **优先使用f-string**(当Python版本≥3.6时) ```python name = "李华" print(f"{name:>10}") # 输出:' 李华' ``` 2. **复杂格式模板推荐方案** ```python template = """ 实验报告 ============= 温度:{temp:.1f}°C 压强:{pressure:04d} hPa 状态:{status!r} # 自动调用repr() """ print(template.format(temp=23.456, pressure=98, status="正常")) ``` 3. **国际化的多语言处理** ```python locales = { "en": "Welcome, {}!", "zh": "欢迎,{}!" } print(locales["zh"].format("王先生")) ``` --- ### **七、错误排查指南** 1. `KeyError`:检查关键字参数名称是否一致 2. `IndexError`:确认位置参数数量是否足够 3. `ValueError`:检查格式说明符与数据类型的兼容性 通过以下代码理解典型错误: ```python try: print("{greeting}, {name}!".format(greeting="Hello")) except KeyError as e: print(f"缺少参数:{e}") # 输出:缺少参数:'name' ``` $$ \text{格式化本质} \quad \text{输出} = \text{文本模板} + \text{数据替换} + \text{格式修饰} $$ 掌握这些技巧后,可以轻松处理以下场景: - 生成符合财务规范的金额显示 - 输出对齐的终端表格 - 创建动态精度的科学报告 - 构建多语言系统的文本模板
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值