Как работают манипуляторы ручьи?
Вопрос
Хорошо известно, что пользователь может определить манипуляторы потока, как это:
ostream& tab(ostream & output)
{
return output<< '\t';
}
И это можно использовать в основной() так:
cout<<'a'<<tab<<'b'<<'c'<<endl;
Пожалуйста, объясните мне, как это работает? Если Оператор < предполагает в качестве второго параметра указатель на функцию, которая берет и возвращает ostream &, Тогда, пожалуйста, объясните, почему нужно? Что было бы неправ, если функция не берет и возвращает ostream & но это было пустота вместо ostream &?
Также интересно, почему «декабря», «шестнадцатеричные» манипуляторы вступают в силу, пока не изменяем между ними, а пользовательские манипуляторы должны всегда использоваться для того, чтобы вступить в силу для каждой потоковой передачи?
Решение
Стандарт определяет следующее operator<<
Перегрузка в basic_ostream
Шаблон класса:
basic_ostream<charT,traits>& operator<<(
basic_ostream<charT,traits>& (*pf) (basic_ostream<charT,traits>&) );
Эффекты: нет. Не ведет себя как функцию отформатированной вывода (как описано в 27.6.2.5.1).
Возвращает:
pf(*this)
.
Параметр является указателем на функцию, принимающую и возврату ссылки на std::ostream
.
Это означает, что вы можете «поток» функцию с этой подписью к ostream
Объект, и это влияет на вызов этой функции на поток. Если вы используете имя функции в выражении, то он (обычно) преобразуется в указатель на эту функцию.
std::hex
является std::ios_base
Манипулятор определяется следующим образом.
ios_base& hex(ios_base& str);
Эффекты: Звонки
str.setf(ios_base::hex, ios_base::basefield)
.Возвращает: ул.
Это означает, что потоковое hex
чтобы ostream
Установит флаги вывода базового форматирования для вывода номеров в шестнадцатеричном порядке. Манипулятор не выводит ничего самого.
Другие советы
В этом нет ничего плохого, кроме того, что нет перегруженного << Оператор, определенный для него. Существующие перегрузки для << ожидают манипулятора с подписью Ostream & (* FP) (Ostream &).
Если вы дали ему манипулятор с типом Ostream & (* FP) () Вы получите сообщение об ошибке компилятора, так как не иметь определение для Оператор << (Ostream &, Ostream & (* FP) ()). Отказ Если вы хотите эту функциональность, вам придется перегружать оператор << для принятия манипуляторов этого типа.
Вам придется написать определение для этого:
OREAM & OSTREAM :: Оператор << (Orstream & (* m) ())
Имейте в виду здесь, что здесь ничего магического не происходит. Библиотеки потока сильно полагаются на стандартный Особенности C ++: перегрузка оператора, классы и ссылки.
Теперь, когда вы знаете, как вы можете создать функциональность, которые вы описали, вот почему мы не делаем:
Не передавая ссылку на поток, мы пытаемся манипулировать, мы не можем внести модификации потока, подключенного к конечному устройству (CIN, OUT, ERR, FSTREAM и т. Д.). Функция (MODITIFIER'S все просто функционирует с модными именами). Подключитесь с остальным всем к правой части модификатора, не сделает его до окончательного устройства, но скорее будет отправлено на любой острую функцию / модификатор.
Думать о потоках, как это
cout << "something here" << tab << "something else"<< endl;
действительно значит
(((cout << "something here") << tab ) << "something else" ) << endl);
Там, где каждый набор скобок делает что-то для Cout (запись, модифицировать и т. Д.), а затем возвращает Cout, чтобы на нем можно работать следующий набор скобок.
Если ваша вкладка модификатор / функция не предпринимала ссылку на Ostream, это придется каким-то догадаться, что остром оставил от << оператора для выполнения своей задачи. Вы работали с Cour, Cerr, какой-то файловый поток ...? Интернеты функции никогда не узнают, если они не передают эту информацию какой-то как, и почему бы не то, как настолько простую, как ссылка на него.
Теперь, чтобы действительно водить точку домой, давайте посмотрим на что endl На самом деле есть и какая перегруженная версия оператора << мы используем:
Этот оператор выглядит так:
ostream& ostream::operator<<(ostream& (*m)(ostream&))
{
return (*m)(*this);
}
endl выглядит так:
ostream& endl(ostream& os)
{
os << '\n';
os.flush();
return os;
}
Целью ENDL является добавление новой строки и промывки потока, убедившись, что все содержимое внутреннего буфера потока были записаны на устройство. Чтобы сделать это, сначала нужно написать « N» к этому потоку. Затем он должен сказать поток, чтобы промыть. Единственный способ ENDL, чтобы узнать, какой поток для записи и вспомогательного, предназначен для оператора, чтобы передать эту информацию в функцию ENDL, когда она его вызывает. Это было бы похоже, что я говорю вам вымыть мою машину, но никогда не говорите, какая машина моя на полной парковке. Вы никогда не сможете сделать вашу работу. Тебе нужна, чтобы избрать тебя, моя машина, или я могу мыть его сам.
Я надеюсь, что это проясняет вещи
PS - если вы случитесь случайно найти мою машину, пожалуйста, мыть ее.
Как правило, манипулятор потока устанавливает некоторые флаги (или другие настройки) на объекте потока, так что в следующий раз он используется, он будет действовать в соответствии с флагами. Следовательно, манипулятор возвращает тот же объект, который прошел. То operator<<
Перегрузка, которая называемыми «Manipulator» уже имеет этот объект, конечно, так как вы заметили, возвращаемое значение не требуется для этого случая. Я думаю, что это охватывает все стандартные манипуляторы - все они возвращают свой вклад.
Тем не менее, с возвращаемой стоимостью структура достаточно гибкой, чтобы таможенный манипулятор потока мог Верните другой объект, предположительно обертку для объекта его данного. Этот другой объект будет возвращен из cout << 'a' << tab
, и мог сделать то, что встроенный ostream
Параметры форматирования не поддерживают.
Не уверен, как вы устроили этот другой объект, чтобы освободиться, поэтому я не знаю, насколько это практично. Возможно, это должно быть что-то своеобразное, как прокси-объект, который управляется ostream
сам. Тогда манипулятор будет работать только для пользовательских рубричных классов, которые активно поддерживают его, что обычно не является точкой манипуляторов.