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

时间:2012-12-29 18:37:52

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

我想在'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<<来电是不明确的。


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

6 个答案:

答案 0 :(得分:3)

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

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&lt;&lt;()的一般格式,它首先完成它的工作,然后返回一些东西。

答案 1 :(得分:3)

“显而易见”的解决方案是使用操纵器安装自定义std::num_put<char>构面,根据需要格式化int

上述陈述可能有点神秘,尽管它完全描述了解决方案。下面是实际实现逻辑的代码。第一个成分是一个特殊的std::num_put<char> facet ,它只是一个派生自std::num_put<char>并覆盖其virtual函数之一的类。使用的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主要设置此索引。如果没有为流twodigits()安装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()

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

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

{{1}}

答案 2 :(得分:3)

在 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);

答案 3 :(得分:2)

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

答案 4 :(得分:1)

为方便输出格式化,您可以将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;
}

答案 5 :(得分:0)

operator<<(std::ostream& s, int i)“含糊不清”因为这样的功能已经存在。

您需要做的就是为该功能提供一个不冲突的签名。