1. 程式人生 > 實用技巧 >C++的manipulator(控制符)的工作原理

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