Лекции.ИНФО


Перед вами короткая программа, в которой демонстрируется использование оператора вывода.



// Использование перегруженного оператора вывода.

#include <iostream>

using namespace std;

class three_d {

 public:

  int x, y, z; // 3-мерные координаты

  three_d(int a, int b, int с) { x = a; у = b; z = c; }

};

/* Отображение координат X, Y, Z (оператор вывода для класса three_d).

*/

ostream &operator<<(ostream &stream, three_d obj)

{

 stream << obj.x << ", ";

 stream << obj.у << ", ";

 stream << obj.z << "";

 return stream; // возвращает параметр stream

}

Int main()

{

 three_d a(1, 2, 3), b(3, 4, 5), c(5, 6, 7);

 cout << a << b << c;

 return 0;

}

При выполнении эта программа возвращает следующие результаты:

1, 2, 3

3, 4, 5

5, 6, 7

Если удалить код, относящийся конкретно к классу three_d, останется "скелет", подходящий для любой функции вывода данных.

ostream &operator<<(ostream &stream, class_type obj)

{

 // код, относящийся к конкретному классу

 return stream; // возвращает параметр stream

}

Как уже отмечалось, для параметра obj разрешается использовать передачу по ссылке. В широком смысле конкретные действия функции вывода определяются программистом. Но если вы хотите следовать профессиональному стилю программирования, то ваша функция вывода должна все-таки выводить информацию. И потом, всегда нелишне убедиться в том, что она возвращает параметр stream.

Прежде чем переходить к следующему разделу, подумайте, почему функция вывода для класса three_d не была закодирована таким образом.

/* Версия ограниченного применения (использованию не подлежит).

*/

ostream &operator<<(ostream &stream, three_d obj)

{

 cout << obj.x << ", ";

 cout << obj.у << ", ";

 cout << obj.z << "";

 return stream; // возвращает параметр stream

}

В этой версии функции жестко закодирован поток cout. Это ограничивает круг ситуаций, в которых ее можно использовать. Помните, что оператор "<<" можно применить к любому потоку и что поток, который использован в "<<"-выражении, передается параметру stream. Следовательно, вы должны передавать функции поток, который корректно работает во всех случаях. Только так можно создать функцию вывода данных, которая подойдет для использования в любых выражениях ввода-вывода.

Использование функций-"друзей" для перегрузки операторов вывода

В предыдущей программе перегруженная функция вывода не была определена как член класса three_d. В действительности ни функция вывода, ни функция ввода не могут быть членами класса. Дело здесь вот в чем. Если операторная функция является членом класса, левый операнд (неявно передаваемый с помощью указателя this) должен быть объектом класса, который сгенерировал обращение к этой операторной функции. И это изменить нельзя. Однако при перегрузке операторов вывода левый операнд должен быть потоком, а правый — объектом класса, данные которого подлежат выводу. Следовательно, перегруженные операторы вывода не могут быть функциями-членами.

В связи с тем, что операторные функции вывода не должны быть членами класса, для которого они определяются, возникает серьезный вопрос: как перегруженный оператор вывода может получить доступ к закрытым элементам класса? В предыдущей программе переменные х, у z были определены как открытые, и поэтому оператор вывода без проблем мог получить к ним доступ. Но ведь сокрытие данных — важная часть объектно-ориентированного программирования, и требовать, чтобы все данные были открытыми, попросту нелогично. Однако существует решение и для этой проблемы: оператор вывода можно сделать "другом" класса. Если функция является "другом" некоторого класса, то она получает легальный доступ к его private-данным. Как можно объявить "другом" класса перегруженную функцию вывода, покажем на примере класса three_d.

// Использование "дружбы" для перегрузки оператора "<<"

#include <iostream>

using namespace std;

class three_d {

  int x, y, z; // 3-мерные координаты (теперь это private-члены)

 public:

  three_d(int a, int b, int с) { x = a; у = b; z = c; }

  friend ostream &operator<<(ostream &stream, three_d obj);

};

// Отображение координат X, Y, Z (оператор вывода для класса three_d).

ostream &operator<<(ostream &stream, three_d obj)

{

 stream << obj.x << ", ";

 stream << obj.у << ", ";

 stream << obj.z << "";

 return stream; // возвращает поток

}

Int main()

{

 three_d a(1, 2, 3), b(3, 4, 5), с (5, 6, 7);

 cout << a << b << c;

 return 0;

}

Обратите внимание на то, что переменные х, у и z в этой версии программы являются закрытыми в классе three_d, тем не менее, операторная функция вывода обращается к ним напрямую. Вот где проявляется великая сила "дружбы": объявляя операторные функции ввода и вывода "друзьями" класса, для которого они определяются, мы тем самым поддерживаем принцип инкапсуляции объектно-ориентированного программирования.

Перегрузка операторов ввода

Для перегрузки операторов ввода используйте тот же метод, который мы применяли при перегрузке оператора вывода. Например, следующий оператор ввода обеспечивает ввод трехмерных координат. Обратите внимание на то, что он также выводит соответствующее сообщение для пользователя.

/* Прием трехмерных координат (оператор ввода для класса three_d).

*/

istream &operator>>(istream &stream, three_d &obj)

{

 cout << "Введите координаты X, Y и Z:

 stream >> obj.x >> obj.у >> obj.z;

 return stream;

}

Оператор ввода должен возвращать ссылку на объект типа istream. Кроме того, первый параметр должен представлять собой ссылку на объект типа istream. Этот тип принадлежит потоку, указанному слева от оператора ">>". Второй параметр является ссылкой на переменную, которая принимает вводимое значение. Поскольку второй параметр — ссылка, он может быть модифицирован при вводе информации.

Общий формат оператора ввода имеет следующий вид.

istream &operator>>(istream &stream, object_type &obj)

{

 // код операторной функции ввода данных

 return stream;

}

Использование функции ввода данных для объектов типа three_d демонстрируется в следующей программе.

// Использование перегруженного оператора ввода.

#include <iostream>

using namespace std;

class three_d {

  int x, y, z; // 3-мерные координаты

 public:

  three_d(int a, int b, int с) { x = a; у = b; z = c; }

  friend ostream &operator<<(ostream &stream, three_d obj);

  friend istream &operator>>(istream &stream, three_d &obj);

};

// Отображение координат X, Y, Z (оператор вывода для класса three_d).

ostream &operator<<(ostream &stream, three_d obj)

{

 stream << obj.x << ", ";

 stream << obj.у << ", ";

 stream << obj.z << "";

 return stream; // возвращает параметр stream

}

// Прием трехмерных координат (оператор ввода для класса three_d).

istream &operator>>(istream &stream, three_d &obj)

{

 cout << "Введите координаты X, Y и Z: ";

 stream >> obj.x >> obj.у >> obj.z;

 return stream;

}

Int main()

{

 three_d a(1, 2, 3);

 cout << a;

 cin >> a;

 cout << a;

 return 0;

}

Вот как выглядит один из возможных результатов выполнения этой программы.

1, 2, 3

Введите координаты X, Y и Z: 5 6 7

5, 6, 7

Подобно функциям вывода, функции ввода не должны быть членами класса, для обработки данных которого они предназначены. Они могут быть "друзьями" этого класса или просто независимыми функциями.

За исключением того, что функция ввода должна возвращать ссылку на объект типа istream, тело этой функции может содержать все, что вы считаете нужным в нее включить. Но логичнее использовать операторы ввода все же по прямому назначению, т.е. для выполнения операций ввода.

Сравнение С- и С++-систем ввода-вывода

Как вы знаете, предшественник C++, язык С, оснащен одной из самых гибких (среди структурированных языков) и при этом очень мощных систем ввода-вывода. (Не будет преувеличением сказать, что среди всех известных структурированных языков С-система ввода-вывода не имеет себе равных.) Почему же тогда, спрашивается, в C++ определяется собственная система ввода-вывода, если в ней продублирована большая часть того, что содержится в С (имеется в виду мощный набор С-функций ввода-вывода)? Ответить на этот вопрос нетрудно. Дело в том, что С-система ввода-вывода не обеспечивает никакой поддержки для объектов, определяемых пользователем. Например, если создать в С такую структуру

struct my_struct {

 int count;

 char s [80];

 double balance;

} cust;

то существующую в С систему ввода-вывода невозможно настроить так, чтобы она могла выполнять операции ввода-вывода непосредственно над объектами типа my_struct. Но поскольку центром объектно-ориентированного программирования являются именно объекты, имеет смысл, чтобы в C++ функционировала такая система ввода-вывода, которую можно было бы динамически "обучать" обращению с любыми объектами, создаваемыми программистом. Именно поэтому для C++ и была изобретена новая объектно-ориентированная система ввода-вывода. Как вы уже могли убедиться, С++-подход к вводу-выводу позволяет перегружать операторы "<<" и ">>", чтобы они могли работать с классами, создаваемыми программистами.

И еще. Поскольку C++ является супермножеством языка С, все содержимое С-системы ввода-вывода включено в C++. (См. приложение А, в котором представлен обзор С-ориентированных функций ввода-вывода.) Поэтому при переводе С-программ на язык C++ вам не нужно изменять все инструкции ввода-вывода подряд. Работающие С-инструкции скомпилируются и будут успешно работать и в новой С++-среде. Просто вы должны учесть, что старая С-система ввода-вывода не обладает объектно-ориентированными возможностями.

Форматированный ввод-вывод данных

До сих пор при вводе или выводе информации в наших примерах программ действовали параметры форматирования, которые по умолчанию использует С++-система ввода-вывода. Но программист может сам управлять форматом представления данных, причем двумя способами. Первый способ предполагает использование функций-членов класса ios, а второй— функций специального типа, именуемых манипуляторами (manipulator). Мы же начнем освоение возможностей форматирования с функций-членов класса ios.

Форматирование данных с использованием функций-членов класса ios

В системе ввода-вывода C++ каждый поток связан с набором флагов форматирования, которые управляют процессом форматирования информации. В классе ios объявляется перечисление fmtflags, в котором определены следующие значения. (Точнее, эти значения определены в классе ios_base, который, как упоминалось выше, является базовым для класса ios.)

Эти значения используются для установки или очистки флагов форматирования с помощью таких функций, как setf() и unsetf(). При использовании старого компилятора может оказаться, что он не определяет тип перечисления fmtflags. В этом случае флаги форматирования будут кодироваться как целочисленные long-значения.

Если флаг skipws установлен, то при потоковом вводе данных ведущие "пробельные" символы, или символы пропуска (т.е. пробелы, символы табуляции и новой строки), отбрасываются. Если же флаг skipws сброшен, пробельные символы не отбрасываются.

Если установлен флаг left, выводимые данные выравниваются по левому краю, а если установлен флаг right — по правому. Если установлен флаг internal, числовое значение дополняется пробелами, которыми заполняется поле между ним и знаком числа или символом основания системы счисления. Если ни один из этих флагов не установлен, результат выравнивается по правому краю по умолчанию.

По умолчанию числовые значения выводятся в десятичной системе счисления. Однако основание системы счисления можно изменить. Установка флага oct приведет к выводу результата в восьмеричном представлении, а установка флага hex — в шестнадцатеричном. Чтобы при отображении результата вернуться к десятичной системе счисления, достаточно установить флаг dec.

Установка флага showbase приводит к отображению обозначения основания системы счисления, в которой представляются числовые значения. Например, если используется шестнадцатеричное представление, то значение 1F будет отображено как 0x1F.

По умолчанию при использовании экспоненциального представления чисел отображается строчной вариант буквы "е". Кроме того, при отображении шестнадцатеричного значения используется также строчная буква "х". После установки флага uppercase отображается прописной вариант этих символов.

Установка флага showpos вызывает отображение ведущего знака "плюс" перед положительными значениями.

Установка флага showpoint приводит к отображению десятичной точки и хвостовых нулей для всех чисел с плавающей точкой — нужны они или нет.

После установки флага scientific числовые значения с плавающей точкой отображаются в экспоненциальном представлении. Если установлен флаг fixed, вещественные значения отображаются в обычном представлении. Если не установлен ни один из этих флагов, компилятор сам выбирает соответствующий метод представления.

При установленном флаге unitbuf содержимое буфера сбрасывается на диск после каждой операции вывода данных.

Если установлен флаг boolalpha, значения булева типа можно вводить или выводить, используя ключевые слова true и false.

Поскольку часто приходится обращаться к полям oct, dec и hex, на них допускается коллективная ссылка ios::basefield. Аналогично поля left, right и internal можно собирательно назвать ios::adjustfield. Наконец, поля scientific и fixed можно назвать ios::floatfield.

Чтобы установить флаги форматирования, обратитесь к функции setf().

Для установки любого флага используется функция setf(), которая является членом класса ios. Вот как выглядит ее формат.

fmtflags setf(fmtflags flags);

Эта функция возвращает значение предыдущих установок флагов форматирования и устанавливает их в соответствии со значением, заданным параметром flags. Например, чтобы установить флаг showbase, можно использовать эту инструкцию.

stream.setf(ios::showbase);

Здесь элемент stream означает поток, параметры форматирования которого вы хотите изменить. Обратите внимание на использование префикса ios:: для уточнения принадлежности параметра showbase. Поскольку параметр showbase представляет собой перечислимую константу, определенную в классе ios, то при обращении к ней необходимо указывать имя класса ios. Этот принцип относится ко всем флагам форматирования. В следующей программе функция setf() используется для установки флагов showpos и scientific.

#include <iostream>

using namespace std;

Int main()

{

 cout.setf(ios::showpos);

 cout.setf(ios::scientific);

 cout << 123 << " " << 123.23 << " ";

 return 0;

}









Читайте также:

  1. D. ПРОЕКТ ДОГОВОРА ВОИС ПО УРЕГУЛИРОВАНИЮ СПОРОВ МЕЖДУ ГОСУДАРСТВАМИ В ОБЛАСТИ ИНТЕЛЛЕКТУАЛЬНОЙ СОБСТВЕННОСТИ
  2. F. ИСПОЛЬЗОВАНИЕ ЗАПАТЕНТОВАННОГО ИЗОБРЕТЕНИЯ
  3. I Использование заемных средств в работе предприятия
  4. I. Дополните предложения данными словами. Переведите предложения на русский язык.
  5. I. Роль объективного Оператора F.
  6. L. ПРЕДОСТАВЛЕНИЕ ЛИЦЕНЗИИ НА ИСПОЛЬЗОВАНИЕ ТОВАРНОГО ЗНАКА
  7. VI. Расчет параметров цепной передачи
  8. А. РОЛЬ ИНФОРМАЦИИ О ПРОМЫШЛЕННОЙ СОБСТВЕННОСТИ В ПЕРЕДАЧЕ ТЕХНОЛОГИИ
  9. А. Только совершением работы. Б. Только теплопередачей. В. Совершением работы и теплопередачей. Г. Внутреннюю энергию тела изменить нельзя.
  10. Алгоритм расчета клиноременной передачи
  11. Античность в Греции предстала перед В.А. Серовым в её чистом виде ( ) и художник воспринял эту страну как реализованную мечту о большом искусстве.
  12. Аппаратная реализация передачи данных по сети.


Последнее изменение этой страницы: 2016-03-17; Просмотров: 102;


lektsia.info 2017 год. Все права принадлежат их авторам! Главная