【问题标题】:How to print a bunch of integers with the same formatting?如何打印一堆具有相同格式的整数?
【发布时间】:2012-12-29 18:37:51
【问题描述】:

我想在两个字段上打印一堆整数,'0' 作为填充字符。我可以做到,但这会导致代码重复。我应该如何更改代码以便排除重复代码?

#include <ctime>
#include <sstream>
#include <iomanip>
#include <iostream>

using namespace std;

string timestamp() {

    time_t now = time(0);

    tm t = *localtime(&now);

    ostringstream ss;

    t.tm_mday = 9; // cheat a little to test it
    t.tm_hour = 8;

    ss << (t.tm_year+1900)
       << setw(2) << setfill('0') << (t.tm_mon+1) // Code duplication
       << setw(2) << setfill('0') <<  t.tm_mday
       << setw(2) << setfill('0') <<  t.tm_hour
       << setw(2) << setfill('0') <<  t.tm_min
       << setw(2) << setfill('0') <<  t.tm_sec;

    return ss.str();
}

int main() {

    cout << timestamp() << endl;

    return 0;
}

我试过了

std::ostream& operator<<(std::ostream& s, int i) {

    return s << std::setw(2) << std::setfill('0') << i;
}

但它不起作用,operator&lt;&lt; 调用是模棱两可的。


编辑我得到了 4 个很棒的答案,我选择了一个可能是最简单和最通用的答案(也就是说,不假设我们正在处理时间戳)。对于实际问题,我可能会使用std::put_timestrftime

【问题讨论】:

  • int 创建一个新的ostream&amp; operator&lt;&lt; 是一个非常糟糕的主意。 :)
  • @LightnessRacesinOrbit 是的,我也想通了。有什么建议可以代替吗?
  • 如果您尝试格式化日期/时间,您可能需要查看:en.cppreference.com/w/cpp/io/manip/put_time
  • @zahir Awesome:你能把它作为答案发布吗?我想投赞成票!

标签: c++ c++11 code-duplication iomanip


【解决方案1】:

在 C++20 中,您可以使用 std::format 以一种不那么冗长的方式执行此操作:

    ss << std::format("{}{:02}{:02}{:02}{:02}{:02}",
                      t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
                      t.tm_hour, t.tm_min, t.tm_sec);

使用直接支持tm 格式的the {fmt} library 更容易:

auto s = fmt::format("{:%Y%m%d%H%M%S}", t);

【讨论】:

    【解决方案2】:

    您需要这样的字符串流代理:

    struct stream{
        std::ostringstream ss;
        stream& operator<<(int i){
            ss << std::setw(2) << std::setfill('0') << i;
            return *this; // See Note below
        }
    } ss; 
    

    那么你的格式化代码就是这样:

    ss << (t.tm_year+1900)
       << (t.tm_mon+1)
       << t.tm_mday
       << t.tm_hour
       << t.tm_min
       << t.tm_sec;
    
    return ss.ss.str();
    

    ps。注意我的 stream::operator

    【讨论】:

    • 赞成,谢谢!我不太确定我是否理解您的说明。是的,您必须返回一个类似流的对象,以便可以在其上调用下一个 operator&lt;&lt;。我的尝试也返回了一些东西,即std::ostream
    【解决方案3】:

    “显而易见”的解决方案是使用操纵器安装自定义的 std::num_put&lt;char&gt; facet,它只是根据需要格式化 ints。

    上面的陈述可能有点神秘,尽管它完全描述了解决方案。下面是实际实现逻辑的代码。第一个成分是一个特殊的std::num_put&lt;char&gt; facet,它只是一个派生自std::num_put&lt;char&gt; 并覆盖其virtual 函数之一的类。使用的构面是一个过滤构面,它查看与流一起存储的标志(使用iword())以确定它是否应该改变行为。代码如下:

    class num_put
        : public std::num_put<char>
    {
        std::locale loc_;
        static int index() {
            static int rc(std::ios_base::xalloc());
            return rc;
        }
        friend std::ostream& twodigits(std::ostream&);
        friend std::ostream& notwodigits(std::ostream&);
    
    public:
        num_put(std::locale loc): loc_(loc) {}
        iter_type do_put(iter_type to, std::ios_base& fmt,
                         char fill, long value) const {
            if (fmt.iword(index())) {
                fmt.width(2);
                return std::use_facet<std::num_put<char> >(this->loc_)
                    .put(to, fmt, '0', value);
            }
            else {
                return std::use_facet<std::num_put<char> >(this->loc_)
                    .put(to, fmt, fill, value);
            }
        }
    };
    

    主要部分是 do_put() 成员函数,它决定了值需要如何格式化:如果 fmt.iword(index()) 中的标志非零,它将宽度设置为 2 并调用格式化函数填充0 的字符。无论如何都会重置宽度,并且填充字符不会随流存储,也就是说,不需要任何清理。

    通常,代码可能存在于单独的翻译单元中,并且不会在标头中声明。在标头中真正声明的唯一函数是twodigits()notwodigits(),在这种情况下它们被设为friends,以提供对index() 成员函数的访问。 index() 成员函数只是在调用时间时分配一个可与std::ios_base::iword() 一起使用的索引,然后它只返回该索引。 操纵器 twodigits()notwodigits() 主要设置此索引。如果没有为流 twodigits() 安装 num_put 构面,也会安装构面:

    std::ostream& twodigits(std::ostream& out)
    {
        if (!dynamic_cast<num_put const*>(
                 &std::use_facet<std::num_put<char> >(out.getloc()))) {
            out.imbue(std::locale(out.getloc(), new num_put(out.getloc())));
        }
        out.iword(num_put::index()) = true;
        return out;
    }
    
    std::ostream& notwodigits(std::ostream& out)
    {
        out.iword(num_put::index()) = false;
        return out;
    }
    

    twodigits() 操纵器使用new num_put(out.getloc()) 分配num_put facet。它不需要任何清理,因为在std::locale 对象中安装构面会进行必要的清理。使用out.getloc() 访问流的原始std::locale。它被刻面改变。理论上notwodigits 可以恢复原来的std::locale 而不是使用标志。但是,imbue() 可能是一个相对昂贵的操作,使用标志应该便宜很多。当然,如果有很多类似的格式化标志,事情可能会变得不同......

    为了演示操纵器的使用,下面有一个简单的测试程序。它设置格式化标志twodigits 两次以验证构面只创建一次(创建std::locales 链来传递格式化有点傻:

    int main()
    {
        std::cout << "some-int='" << 1 << "' "
                  << twodigits << '\n'
                  << "two-digits1='" << 1 << "' "
                  << "two-digits2='" << 2 << "' "
                  << "two-digits3='" << 3 << "' "
                  << notwodigits << '\n'
                  << "some-int='" << 1 << "' "
                  << twodigits << '\n'
                  << "two-digits4='" << 4 << "' "
                  << '\n';
    }
    

    【讨论】:

    • 您能详细说明一下吗?这对我来说并不是那么“明显”:(
    • 对不起,我不知道。我会等你的答复!
    • 赞成,非常感谢您的努力!我还不接受任何答案,我可能会得到其他答案,并且现有答案也可能会被投票。
    【解决方案4】:

    除了使用std::setw / std::setfillios_base::width / basic_ios::fill 格式化整数之外,如果您想格式化日期/时间对象,您可能需要考虑使用std::put_time / std::gettime

    【讨论】:

      【解决方案5】:

      为了方便输出格式,您可以使用boost::format()sprintf 类似的格式选项:

      #include <boost/format.hpp>
      #include <iostream>
      
      int main() {
          int i1 = 1, i2 = 10, i3 = 100;
          std::cout << boost::format("%03i %03i %03i\n") % i1 % i2 % i3; 
          // output is: 001 010 100
      }
      

      很少的代码重复,额外的实施工作是微不足道的。


      如果您只想输出时间戳的格式,您显然应该使用strftime()。这就是它的用途:

      #include <ctime>
      #include <iostream>
      
      std::string timestamp() {
          char buf[20];
          const char fmt[] = "%Y%m%d%H%M%S";
          time_t now = time(0);
          strftime(buf, sizeof(buf), fmt, localtime(&now));
          return buf;
      }
      
      int main() {
          std::cout << timestamp() << std::endl;
      }
      

      【讨论】:

      • 谢谢。但你不需要为此提升:snprintf(buffer, 15, "%d%02d%02d%02d%02d%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);。我个人不喜欢 C 风格的格式
      • 点赞!我不知道strftime 是标准的一部分。我知道它,但我认为它只存在于 POSIX 中。
      【解决方案6】:

      operator&lt;&lt;(std::ostream&amp; s, int i) 是“模棱两可的”,因为这样的函数已经存在。

      您需要做的就是给该函数一个不冲突的签名。

      【讨论】:

      • 你能举个例子吗,最好是算子函数?
      • 德鲁,你打算怎么做?
      猜你喜欢
      • 1970-01-01
      • 2017-04-07
      • 2021-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多