问题引入:今天老师讲解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::left
和ios::right
做了一个或运算(|)
。
那么接着看,ios::left
和ios::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)