【c++】cout.setf(left)、cout.setf(right)详解

271 阅读3分钟

问题引入:今天老师讲解c++的时候,演示cout.setf(left)cout.setf(right)时发现了一个奇怪的问题,setf(left)会被setf(right)覆盖,老师猜测可能是left和right是两个不同的变量,然后优先级不一样,我当时觉得奇怪,就翻开源码一探究竟。

示例代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    cout.fill('_');//为了突出比较,将空白处填充为下划线(_)
    cout.setf(ios::right);
    cout.width(8);
    cout<<"a";
    cout.setf(ios::left);
    cout.width(8);
    cout<<"a";
    return 0;
}

按照预期,先右对齐再左对齐,输出的应该是长这样_______aa_______,然而实际情况确是:_______a_______a,也就是说,第二次的setf(ios::left)竟然不起作用。

翻看源码:

/**
     *  @brief  Setting new format flags.
     *  @param  __fmtfl  Additional flags to set.
     *  @return  The previous format control flags.
     *
     *  This function sets additional flags in format control.  Flags that
     *  were previously set remain set.
    */
    fmtflags
    setf(fmtflags __fmtfl)
    {
      fmtflags __old = _M_flags;
      _M_flags |= __fmtfl;//重载了 |= 运算符
      return __old;//返回旧的标志
    }

重载的|=运算函数:

inline const _Ios_Fmtflags&
operator|=(_Ios_Fmtflags& __a, _Ios_Fmtflags __b)
{ return __a = __a | __b; }//实际上就是将新旧标志做'或'运算

我们可以看到,setf()函数在我们的例子中实际上是将ios::leftios::right做了一个或运算(|)。 那么接着看,ios::leftios::right分别是什么呢?

/// Adds fill characters on the right (final positions) of certain
/// generated output.  (I.e., the thing you print is flush left.)
static const fmtflags left =        _S_left;
static const fmtflags right =       _S_right;

再进一步看:

// The following definitions of bitmask types are enums, not ints,
// as permitted (but not required) in the standard, in order to provide
// better type safety in iostream calls.  A side effect is that in C++98
// expressions involving them are not compile-time constants.
enum _Ios_Fmtflags 
{ 
	//···(三点代表此处省略了其他标志的定义)
	_S_left 		= 1L << 5,
	//···
	_S_right 		= 1L << 7,
	//···
};

那么 1L << 5是什么意思呢? 我们可以简单测试看看:

int main(){
	int left = 1L << 5;
	cout<<left<<endl;
}

输出发现left的值为32,也就是说,其实1L<<5是将1左移5位,事实上2^5 = 32;同理right的值也应当是2^7 = 128。读者们可以动手试试。 那么这是什么意思呢?我们假设该标志为是八位的00000000,设置为right后,则变为01000000,再设置为left时,与00010000进行或运算,得到01010000

// 假设初始的 fmtflags 为 0000 0000
cout.setf(ios::right);//此时fmtflags变为 0100 0000,因为right是 1L<<7
//···
cout.setf(ios::left);//此时fmtflags变为 0101 0000,经历过right和left相或

到这里,我们可以大致猜测cout输出时根据fmtflags的标志位,从左往右去判断,因为right的优先级高于left,使得在二者都为1的情况下,使用了right右对齐的格式。(如果有大佬清楚cout输出时是如何读取flag的,恳请留言告诉我,谢谢~)

那么,为了实现一开始我们的目的,有没有别的办法呢?我还发现另一个函数flags(),它可以实现我们的要求:

cout.fill('_');
cout.flags(ios::right);
cout.width(8);
cout<<"a";
cout.flags(ios::left);
cout.width(8);
cout<<"a";

查看输出:_______aa_______,成功~

为啥呢?查看flags()源码:

/**
     *  @brief  Setting new format flags all at once.
     *  @param  __fmtfl  The new flags to set.
     *  @return  The previous format control flags.
     *
     *  This function overwrites all the format flags with @a __fmtfl.
    */
    fmtflags
    flags(fmtflags __fmtfl)
    {
      fmtflags __old = _M_flags;
      _M_flags = __fmtfl;//直接将flag设置为传入的参数
      return __old;
    }

显然,这样的flags()是满足我们的要求的。同时我们如果需要一次设置多个flag,也可以利用上面的源码中的或运算实现cout.flags(FlagA | FlagB)