326 _______Часть II. Программирование на C++
есть, поэтому спецификатор применяется не для членов (см. главу 14 "Базовые концепции классов"). В нашем случае этот спецификатор необходим потому, что потоковые операции должны быть глобальными функциями, а не членами класса.
Причина этого проста. Если бы функция, как, например, operator«, была функцией-членом,- то объекты для этой функции должны были бы быть левосторонними аргументами. То есть если бы operator« (или operator») были бы функциями-членами, то вывод объекта х выглядел бы примерно так:
х » cout; // Семантика нарушена!
Вспомнив принципы раскрытия выражений (см. главу 8), мы увидим здесь неявный вызов функции-члена operator«:
ostream& x.operator«( ostream&); // Непорядок!
Это может привести к путанице, поскольку ожидается совсем другая форма вызова:
cout «х; // Хотелось бы, чтобы было так
Раскрывая предыдущее выражение, получим
cout.operator«( operator«( ostream&, х&) ) ;
где вложенная функция operator« возвращает аргумент типа ostream&, а внешняя функция operator« передает ссылку в поток ostream.
Резюмируем: функции потоков ввода-вывода должны быть дружественными, потому что:
• Они должны иметь доступ к данным, которые могут оказаться закрытыми (private).
• Объекты потоков должны располагаться с левой стороны операций во избежание путаницы с непоследовательной семантикой.
• Оператор-функции operator« необходимо два объекта; а поскольку она является глобальной, оба объекта следует передавать как аргументы.
Ссылка на поток в качестве возвращаемого значения поток о-ВЫХ операций. Для потоковых операций достаточно типично выстраивание их в длинные цепочки в операторах вывода. Примеры таких цепочек с комбинациями данных самых различных типов вам уже не раз встречались на страницах этой книги. Чтобы не нарушать их целостность, потоковые операции должны возвращать ссылки на объекты потоков.
Для нашего класса DATE (до сих пор не определенного) следует написать:
cout « "Сегодня " « date « endl; // date — объект класса DATE