zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    flume sink两种类型 file_rool 自定义sing com.mycomm.MySink even if there is only one event, the event has to be sent in an array
    为什么引入进程20年后,又引入线程?
    As of Flume 1.4.0, Avro is the default RPC protocol.
    Google Protocol Buffer 的使用和原理
    Log4j 2
    统一日志 统一订单
    网站行为跟踪 Website Activity Tracking Log Aggregation 日志聚合 In comparison to log-centric systems like Scribe or Flume
    Percolator
    友盟吴磊:移动大数据平台的架构、实践与数据增值
    Twitter的RPC框架Finagle简介
  • 原文地址:https://www.cnblogs.com/xrst/p/13384995.html
Copyright © 2011-2022 走看看