• 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

  • 相关阅读:
    项目笔记:导出Excel功能设置导出数据样式
    前后台JSON传值得一个问题和异常处理net.sf.json.JSONException: Unquotted string '"name"'
    ES6中的async函数
    zoj 1203 Swordfish
    C#:excel导入导出
    &quot;undefined reference to strptime&quot;之自己定义strptime函数
    OpenGL蓝宝书第七章:立体天空和纹理折射、双纹理(下)
    HDOJ 5384 Danganronpa AC自己主动机
    Hibernate复习之Hibernate基本介绍
    Java集合---ConcurrentHashMap原理分析
  • 原文地址:https://www.cnblogs.com/xrst/p/13384995.html
Copyright © 2020-2023  润新知