152

我最近在创建 a 时遇到了问题stringstream,因为我错误地假设std::setw()每次插入都会影响字符串流,直到我明确更改它。但是,插入后它始终未设置。

// With timestruct with value of 'Oct 7 9:04 AM'
std::stringstream ss;
ss.fill('0'); ss.setf(ios::right, ios::adjustfield);
ss << setw(2) << timestruct.tm_mday;
ss << timestruct.tm_hour;
ss << timestruct.tm_min;
std::string filingTime = ss.str(); // BAD: '0794'

所以,我有几个问题:

  • 为什么会setw()这样?
  • 其他机械手也这样吗?
  • std::ios_base::width()和之间的行为有区别std::setw()吗?
  • 最后是否有明确记录这种行为的在线参考资料?我的供应商文档(MS Visual Studio 2005)似乎没有清楚地表明这一点。
4

3 回答 3

100

以下评论的重要说明:

马丁:

@Chareles:那么根据这个要求,所有操纵器都是粘性的。除了 setw 似乎在使用后被重置。

查尔斯:

确切地!并且 setw 表现不同的唯一原因是因为格式化输出操作需要明确 .width(0) 输出流。

以下是导致上述结论的讨论:


查看代码,以下操纵器返回对象而不是流:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

这是一种将操作仅应用于应用于流的下一个对象的常用技术。不幸的是,这并不排除它们具有粘性。测试表明,除了粘性之外,所有这些setw都是粘性的。

setiosflags:  Sticky
resetiosflags:Sticky
setbase:      Sticky
setfill:      Sticky
setprecision: Sticky

所有其他操纵器都返回一个流对象。因此,它们更改的任何状态信息都必须记录在流对象中,因此是永久性的(直到另一个操纵器更改状态)。因此,以下操纵器必须是粘性操纵器。

[no]boolalpha
[no]showbase
[no]showpoint
[no]showpos
[no]skipws
[no]unitbuf
[no]uppercase

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

这些操纵器实际上对流本身而不是流对象执行操作(尽管从技术上讲,流是流对象状态的一部分)。但我不相信它们会影响流对象状态的任何其他部分。

ws/ endl/ ends/ flush

结论是 setw 似乎是我的版本中唯一不粘的操纵器。

对于 Charles 来说,一个简单的技巧只影响链中的下一项:
这是一个示例,如何使用对象临时更改状态,然后通过使用对象将其放回原处:

#include <iostream>
#include <iomanip>

// Private object constructed by the format object PutSquareBracket
struct SquareBracktAroundNextItem
{
    SquareBracktAroundNextItem(std::ostream& str)
        :m_str(str)
    {}
    std::ostream& m_str;
};

// New Format Object
struct PutSquareBracket
{};

// Format object passed to stream.
// All it does is return an object that can maintain state away from the
// stream object (so that it is not STICKY)
SquareBracktAroundNextItem operator<<(std::ostream& str,PutSquareBracket const& data)
{
    return SquareBracktAroundNextItem(str);
}

// The Non Sticky formatting.
// Here we temporariy set formating to fixed with a precision of 10.
// After the next value is printed we return the stream to the original state
// Then return the stream for normal processing.
template<typename T>
std::ostream& operator<<(SquareBracktAroundNextItem const& bracket,T const& data)
{
    std::ios_base::fmtflags flags               = bracket.m_str.flags();
    std::streamsize         currentPrecision    = bracket.m_str.precision();

    bracket.m_str << '[' << std::fixed << std::setprecision(10) << data << std::setprecision(currentPrecision) << ']';

    bracket.m_str.flags(flags);

    return bracket.m_str;
}


int main()
{

    std::cout << 5.34 << "\n"                        // Before 
              << PutSquareBracket() << 5.34 << "\n"  // Temp change settings.
              << 5.34 << "\n";                       // After
}


> ./a.out 
5.34
[5.3400000000]
5.34
于 2009-10-07T17:56:55.397 回答
31

width看起来没有“粘性”的原因是某些操作可以保证调用.width(0)输出流。那些是:

21.3.7.9 [lib.string.io]:

template<class charT, class traits, class Allocator>
  basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>& os,
               const basic_string<charT,traits,Allocator>& str);

22.2.2.2.2 [lib.facet.num.put.virtuals]:模板do_put的所有重载。num_put这些由operator<<采用 abasic_ostream和内置数字类型的重载使用。

22.2.6.2.2 [lib.locale.money.put.virtuals]:模板do_put的所有重载。money_put

27.6.2.5.4 [lib.ostream.inserters.character]:采用 basic_ostream 实例化的 char 类型中的operator<<a和其中之一的重载,或者,有符号的或或指向这些 char 类型的数组的指针。basic_ostreamcharcharunsigned char

老实说,我不确定这样做的理由,但是ostream格式化的输出函数不应重置 an 的其他状态。当然,如果输出操作失败,可能会设置badbit和之类的东西,但这应该是可以预料的。failbit

我能想到的重置宽度的唯一原因是,如果在尝试输出某些分隔字段时填充了分隔符,这可能会令人惊讶。

例如

std::cout << std::setw(6) << 4.5 << '|' << 3.6 << '\n';

"   4.5     |   3.6      \n"

要“纠正”这将需要:

std::cout << std::setw(6) << 4.5 << std::setw(0) << '|' << std::setw(6) << 3.6 << std::setw(0) << '\n';

而使用重置宽度,可以使用更短的值生成所需的输出:

std::cout << std::setw(6) << 4.5 << '|' << std::setw(6) << 3.6 << '\n';
于 2009-10-07T19:42:34.650 回答
6

setw()只影响下一次插入。这就是setw()行为方式。的行为setw()与 相同ios_base::width()。我setw()cplusplus.com获得了我的信息。

您可以在此处找到完整的操纵器列表。从该链接中,所有流标志都应设置为设置,直到被另一个操纵器更改。关于left,rightinternal操纵器的一点说明:它们就像其他标志一样,并且持续存在直到更改。但是,它们只有在设置了流的宽度时才起作用,并且必须每行设置宽度。所以,例如

cout.width(6);
cout << right << "a" << endl;
cout.width(6);
cout << "b" << endl;
cout.width(6);
cout << "c" << endl;

会给你

>     a
>     b
>     c

cout.width(6);
cout << right << "a" << endl;
cout << "b" << endl;
cout << "c" << endl;

会给你

>     a
>b
>c

输入和输出操纵器不具有粘性,仅在使用它们的地方出现一次。参数化的机械手各不相同,以下是每个机械手的简要说明:

setiosflags让您手动设置标志,可以在此处找到其列表,因此具有粘性。

resetiosflagssetiosflags除了取消设置指定的标志外,其行为类似于。

setbase设置插入到流中的整数的基数(因此基数 16 中的 17 将是“11”,而基数 2 中的将是“10001”)。

setfill设置填充字符以在使用时插入流setw中。

setprecision设置插入浮点值时要使用的小数精度。

setw通过填充指定的字符,仅在下一个插入指定宽度setfill

于 2009-10-07T16:23:21.073 回答