C++的manipulator(控制符)的工作原理

如果要简单打印一个bool值:

std::cout << true;

结果是1,也就是true这个literal的字节存储表示。

但是有时候我们希望打印出来“true”,可以这么写:

std::cout << std::boolalpha << true;

其中std::boolalpha属于一类叫做manipulator(控制符)的函数,原型是:

ios_base& std::boolalpha(ios_base& str);

这个函数的作用是改变输入输出流str的格式属性,使其之后在遇到bool参数时以文本方式处理。注意这个状态的变更是持久的,之后所有std::cout的输出都会受到影响,直到再调用std::noboolalpha()恢复。

这里最让人困惑的,就是这个函数在上面使用的时候,看起来并不像一个函数,因为没有括号,而且参数也没看到。

先看看正常的写法:

std::boolalpha(std::cout);
std::cout << true;

这么写很好理解,先改变状态,然后打印。结果是一致的。

那最初的写法是什么原理呢?

在Visual Studio里面跟踪操作符<<的定义,发现它是basic_ostream类的这么一个重载方法:

basic_ostream& __CLR_OR_THIS_CALL operator<<(ios_base&(__cdecl* _Pfn)(ios_base&) ) { // call ios_base manipulator
    _Pfn(*this);
    return *this;
}

原来<<接受的是一个函数指针,这个函数的参数和返回值都是ios_base的引用。里面只是简单地调用了一下这个函数而已。

我们可以换个写法验证一下:

std::ios_base& (*func)(std::ios_base&) = std::boolalpha;
std::cout << func << true;

这里我们先存储函数指针到func,然后再传进来,最终结果也是一样的。

另外,std::endl也是一种manipulator,甚至我们还能定义自己的manipulator,只要符合同样的函数声明。

 

扩展:

上述的manipulator是无参的,实际上还有带参的,例如常见的std::setprecision(n),这个似乎没法通过函数指针来解释了。

首先看它的声明:

_Smanip<long long> setprecision(long long n);

和之前不太一样,参数只有一个n,返回的是这个东西:

// STRUCT TEMPLATE _Smanip
template <class _Arg>
struct _Smanip { // store function pointer and argument value
    _Smanip(void(__cdecl* _Left)(ios_base&, _Arg), _Arg _Val) : _Pfun(_Left), _Manarg(_Val) {}

    void(__cdecl* _Pfun)(ios_base&, _Arg); // the function pointer
    _Arg _Manarg; // the argument value
};

简单来说,就是返回了两个信息:一个函数指针,一个参数n。

最后再看看<<操作符:

template <class _Elem, class _Traits, class _Arg>
basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& _Ostr,
    const _Smanip<_Arg>& _Manip) { // insert by calling function with output stream and argument
    (*_Manip._Pfun)(_Ostr, _Manip._Manarg);
    return _Ostr;
}

和之前无参的<<不同,这个<<是全局函数,而不是某个类的成员函数,因此有两个参数:第一个参数对应我们的std::cout,第二个参数就是setprecision()的返回值。

里面实现很简单:调用相应函数,将流和n作为参数传递进去。

其实大体上还是类似,仍然是基于函数指针,只是将参数封装了一下。

 

总结一下:manipulator是C++IO标准库里面的一类特殊函数(不属于C++语言特性),作用是修改输入输出流的状态,这类函数可以通过<<操作符传递函数指针的方式来简化语法。

 

参考:

https://en.cppreference.com/w/cpp/io/manip

posted @ 2020-07-27 13:41  Xrst  阅读(911)  评论(0编辑  收藏  举报