如何用相同的格式打印一堆整数

How to print a bunch of integers with the same formatting?

本文关键字:一堆 整数 式打印 何用相 格式      更新时间:2023-10-16

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

#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<<调用是含糊不清的。


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

在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格式的{fmt}库更容易:

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

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

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

那么你的格式代码将是:

stream 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();

p。注意我的流::operator<<()的一般格式,它先完成它的工作,然后返回一些东西。

"显而易见"的解决方案是使用操纵器来安装一个自定义的std::num_put<char> facet,它只是按需要格式化int

上面的语句可能有点模糊,尽管它完全描述了解决方案。下面是实际实现逻辑的代码。第一个成分是一个特殊的std::num_put<char> facet,它只是一个从std::num_put<char>派生的类,并覆盖它的一个virtual函数。使用的facet是一个过滤facet,它查看与流一起存储的标志(使用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(),它们在本例中被命名为friend,以提供对index()成员函数的访问。index()成员函数只是在调用std::ios_base::iword()时分配一个可用的索引,然后返回这个索引。操作符 twodigits()notwodigits()主要设置该索引。如果num_put facet没有为流安装,twodigits()也会安装facet:

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对象中安装一个facet会进行必要的清理。使用out.getloc()访问流的原始std::locale。它由关节面改变。理论上,notwodigits可以恢复原来的std::locale,而不是使用一个标志。然而,imbue()可能是一个相对昂贵的操作,而使用标志应该便宜得多。当然,如果有很多类似的格式化标志,事情可能会变得不同…

为了演示操纵符的使用,下面有一个简单的测试程序。它设置了两次格式化标志twodigits,以验证只创建了一次facet(创建std::locale s链来传递格式化会有点愚蠢:
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';
}

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

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

#include <boost/format.hpp>
#include <iostream>
int main() {
    int i1 = 1, i2 = 10, i3 = 100;
    std::cout << boost::format("%03i %03i %03in") % 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;
}

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

你所需要做的就是给这个函数一个不冲突的签名