哪些iomanip操纵器是“粘性”的?


140

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)似乎没有清楚地表明这一点。

Answers:


87

以下评论的重要说明:

马丁:

@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

不错的备忘单。添加参考信息的来源,这将是一个完美的答案。
Mark Ransom

1
但是,尽管它返回一个对象,但我可以验证setfill()实际上是否“粘滞”。所以我认为这个答案是不正确的。
约翰K,2009年

2
返回流的对象必须是粘性的,而返回对象的对象可能是粘性的,但这不是必需的。我将用John's Info更新答案。
马丁·约克

1
我不确定我是否理解您的理由。所有带有参数的操纵器都实现为自由函数,返回一个未指定对象,该对象在将对象插入流中时会作用于该流,因为这是保留带有参数的插入语法的唯一方法。无论哪种方式,适合operator<<于操纵器的方式都可以确保以某种方式更改流的状态。两种形式都没有建立任何形式的州哨兵。只有下一个格式化的插入操作才能确定重置状态的哪一部分(如果有)。
CB Bailey

3
究竟!setw表现不同的唯一原因是因为对格式化输出操作有明确.width(0)显示输出流的要求。
CB Bailey

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<<a basic_ostream和内置数字类型使用。

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

27.6.2.5.4 [lib.ostream.inserters.character]:的重载operator<<服用basic_ostream和焦炭类型basic_ostream实例化或之一char,签署charunsigned char或指针这些字符类型的数组。

老实说,我不确定这样做的理由,但是ostream不应通过格式化输出函数来重置的其他状态。当然,如果输出操作失败badbitfailbit则可以设置和,但是应该可以预料到的。

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

例如

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';

6

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

您可以在此处找到机械手的完整列表。从该链接开始,所有流标志都应置位,直到被另一个操纵器更改为止。一个说明有关leftrightinternal操纵:他们就像其他标志和继续下去,直到改变。但是,它们仅在设置流的宽度时才有效,并且必须在每行中设置宽度。因此,例如

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


好吧,其中大多数只是设置标志,因此它们是“粘性的”。setw()似乎是仅影响一次插入的唯一方法。有关详细信息,请访问cplusplus.com/reference/iostream/manipulators
David Brown 2009年

嗯,它std::hex也不是粘性的,而且显然std::flush也不std::setiosflags是粘性的。所以我认为不是那么简单。
09年

仅仅测试hex和setiosflags(),它们似乎都很粘(它们都只是设置了在该流中持久存在的标志,直到您更改它们为止)。
大卫·布朗

是的,声称std::hex不粘的网页是错误的-我也发现了这一点。但是,即使您不std::setiosflags再次插入流标志,流标志也可能会更改,因此人们可能会认为这是非粘性的。另外,std::ws它也不粘。因此,它不是那个容易。
2009年

您已经付出了很多努力来改善答案。+1
sbi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.