Что на самом деле endl
? Конечно, он печатает новую строку и очищает буфер Ostream. Но что это на самом деле?
std::endl
- это функция, которая принимает ссылку std::ostream&
в качестве ввода и возвращает ссылку std::ostream&
в качестве вывода:
template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );
std::basic_ostream::operator<<
имеет перегрузку, которая принимает указатель на такую функцию:
template<
class CharT,
class Traits = std::char_traits<CharT>
> class basic_ostream : virtual public std::basic_ios<CharT,Traits>
{
...
basic_ostream& operator<<(
std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&)
);
...
};
Эта перегрузка вызовет переданную функцию, присвоив ей std::ostream
объект, для которого вызывается оператор, например:
template<class CharT, class Traits>
basic_ostream<CharT,Traits>& basic_ostream<CharT,Traits>::operator<<(
std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&) )
{
func(*this);
return *this;
}
Реализация std::endl
может затем записать в данный std::ostream
и flu sh его, например:
template<class CharT, class Traits>
std::basic_ostream<CharT,Traits>& endl( std::basic_ostream<CharT,Traits>& os )
{
os.put(os.widen('\n'));
os.flush();
return os;
}
Итак, когда у вас есть такой оператор:
std::cout << std::endl
Он будет вызывать это внутренне:
std::cout.operator<<(&std::endl)
Что затем вызовет:
std::endl(std::cout)
Может ли программист определить такие "объекты", как endl?
Да. Любая функция, которая соответствует указанной выше сигнатуре (std::basic_ostream<CharT,Traits>& (*)(std::basic_ostream<CharT,Traits>&)
), может быть передана в operator<<
.
, что на самом деле происходит при выполнении такой команды, как: cout << setprecision(5);
setprecision()
выглядит как вызов функции
Это IS вызов функции. Манипуляторы ввода-вывода, которые принимают вводимые пользователем данные, работают немного иначе, чем манипуляторы ввода-вывода, которые не принимают никаких вводимых пользователем данных. object), такой манипулятор возвращает экземпляр определенного реализацией типа, который содержит ввод, а затем перегружает не-член operator<<
, чтобы принять этот тип. Когда эта перегрузка вызывается, она может затем применить ввод к std::ostream
(или std::istream
) по мере необходимости.
В случае std::setprecision()
он принимает int
в качестве ввода, и возвращает определяемый реализацией тип, содержащий этот int
, а затем этот тип передает int
в std::ostream::precision()
, например:
struct PrecisionType { int value; };
PrecisionType setprecision( int n )
{
return PrecisionType{ n };
}
template<class CharT, class Traits>
std::basic_ostream<CharT,Traits>& operator<<( std::basic_ostream<CharT,Traits>& os, const PrecisionType &input )
{
os.precision(input.value);
return os;
}
Таким образом, такой оператор:
std::cout << std::setprecision(5)
На самом деле вызовет что-то вроде этого:
PrecisionType temp = std::setprecision(5);
operator<<(std::cout, temp)
Что затем вызовет внутренне:
std::cout.precision(temp.value)
, но при использовании экземпляра cout ничего не печатается. Он изменяет точность
Правильно, потому что манипулятор, который возвращает set::setprecision()
, ничего не записывает в выходной буфер std::ostream
, он просто настраивает сам std::ostream
.
Ничто не мешает манипулятору ввода-вывода писать в std::ostream
(или читать из std::istream
), если он этого хочет.
почему бы просто не использовать соответствующую функцию элемент вместо добавления дополнительной «абстракции» к написанию кода?
Конечно, вы могли бы вызывать элементы напрямую, но тогда вы не смогли бы связать последующие выражения, используя <<
. То, что каждый манипулятор возвращает ссылку на управляемый объект std::ostream
(или std::istream
), - вот что позволяет связывание. Методы-члены не возвращают такие ссылки.
Например:
cout << setprecision(5) << 123.45 << endl;
Преобразуется в это:
operator<<(cout, setprecision(5)).operator<<(123.45).operator<<(&endl);
Что в конечном итоге вызовет что-то вроде этого внутри:
//operator<<(cout, setprecision(5));
cout.precision(5);
//cout.operator<<(123.45);
use_facet<num_put<char>>(cout.getloc()).put(
ostreambuf_iterator it{cout},
cout,
cout.fill(),
123.45
);
//cout.operator<<(&endl)
endl(cout);
Не так красиво, как просто использовать <<
перегрузки, не так ли?