3

'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_timeorthough strftime

4

6 回答 6

4

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

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

auto s = fmt::format("{:%Y%m%d%H%M%S}", t);
于 2021-03-05T04:40:15.610 回答
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();

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

于 2012-12-29T19:09:13.393 回答
2

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

上面的陈述可能有点神秘,尽管它完全描述了解决方案。下面是实际实现逻辑的代码。第一个成分是一个特殊的std::num_put<char> 方面,它只是一个派生std::num_put<char>并覆盖它的一个virtual函数的类。used 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()决定如何格式化值的成员函数:如果 in 中的标志fmt.iword(index())非零,它将宽度设置为2并使用填充字符调用格式化函数0。无论如何,宽度都会被重置,并且填充字符不会与流一起存储,即不需要任何清理。

通常,代码可能存在于单独的翻译单元中,并且不会在标题中声明。在头文件中真正声明的唯一函数是twodigits()并且在这种情况下notwodigits()被制成friends 以提供对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::localeout.getloc()。它被刻面改变。从理论上讲,notwodigits可以恢复原始std::locale而不是使用标志。但是,imbue()这可能是一项相对昂贵的操作,使用标志应该便宜很多。当然,如果有很多类似的格式化标志,事情可能会变得不同......

为了演示操纵器的使用,下面有一个简单的测试程序。它设置了twodigits两次格式化标志,以验证 facet 只创建一次(创建一个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';
}
于 2012-12-29T19:11:55.760 回答
2

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

于 2012-12-30T21:43:47.280 回答
1

为了方便输出格式,您可以使用boost::format()-likesprintf格式选项:

#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;
}
于 2012-12-29T20:11:41.350 回答
0

operator<<(std::ostream& s, int i)是“模棱两可的”,因为这样的功能已经存在。

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

于 2012-12-29T18:50:13.153 回答