Главная страница
Навигация по странице:

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

  • Потоки C++

  • Текущая позиция

  • Встроенные С++-потоки

  • Классы потоков

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

  • Создание перегруженных операторов вывода

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

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

  • Шилдт c++_базовый_курс издание 3. Герберт Шилдт С базовый курс


    Скачать 9.37 Mb.
    НазваниеГерберт Шилдт С базовый курс
    АнкорШилдт c++_базовый_курс издание 3.pdf
    Дата13.02.2017
    Размер9.37 Mb.
    Формат файлаpdf
    Имя файлаШилдт c++_базовый_курс издание 3.pdf
    ТипКнига
    #2637
    страница21 из 33
    1   ...   17   18   19   20   21   22   23   24   ...   33
    Глава 18: С++-система ввода-вывода
    С самого начала книги мы использовали С++-систему ввода-вывода, но не давали подробных пояснений по этому поводу. Поскольку С++-система ввода-вывода построена на иерархии классов, ее теорию и детали невозможно освоить, не рассмотрев сначала классы,
    наследование и механизм обработки исключений. Теперь настало время для подробного изучения С++-средств ввода-вывода.
    В этой главе рассматриваются средства как консольного, так и файлового ввода-вывода.
    Необходимо сразу отметить, что С++-система ввода-вывода — довольно обширная тема, и здесь описаны лишь самые важные и часто применяемые средства. В частности, вы узнаете,
    как перегрузить операторы "<<" и ">>" для ввода и вывода объектов созданных вами классов, а также как отформатировать выводимые данные и использовать манипуляторы ввода-вывода. Завершает главу рассмотрение средств файлового ввода-вывода.
    Сравнение старой и новой С++-систем ввода-вывода
    В настоящее время существуют две версии библиотеки объектно-ориентированного ввода-вывода, причем обе широко используются программистами: более старая, основанная на оригинальных спецификациях языка C++, и новая, определенная стандартом языка C++.
    Старая библиотека ввода-вывода поддерживается за счет заголовочного файла ,
    а новая — посредством заголовка . Новая библиотека ввода-вывода, по сути,
    представляет собой обновленную и усовершенствованную версию старой. Основное различие между ними состоит в реализации, а не в том, как их нужно использовать.
    С точки зрения программиста, есть два существенных различия между старой и новой С
    ++-библиотеками ввода-вывода.
    Во-первых, новая библиотека содержит ряд дополнительных средств и определяет несколько новых типов данных. Таким образом,
    новую библиотеку ввода-вывода можно считать супермножеством старой. Практически все программы, написанные для старой библиотеки, успешно компилируются при использовании новой, не требуя внесения каких-либо значительных изменений. Во-вторых,
    старая библиотека ввода-вывода была определена в глобальном пространстве имен, а новая использует пространство имен std. (Вспомните, что пространство имен std используется всеми библиотеками стандарта C++.) Поскольку старая библиотека ввода-вывода уже устарела, в этой книге описывается только новая, но большая часть информации применима и к старой.
    Потоки C++
    Потокэто последовательный логический интерфейс, который связан с физическим
    файлом.
    Принципиальным для понимания С++-системы ввода-вывода является то, что она опирается на понятие потока. Поток (stream) — это общий логический интерфейс с различными устройствами, составляющими компьютер. Поток либо синтезирует информацию, либо потребляет ее и связывается с любым физическим устройством с помощью С++-системы ввода-вывода. Характер поведения всех потоков одинаков, несмотря на различные физические устройства, с которыми они связываются. Поскольку потоки действуют одинаково, то практически ко всем типам устройств можно применить одни и те
    же функции и операторы ввода-вывода. Например, методы, используемые для записи данных на экран, также можно использовать для вывода их на принтер или для записи в дисковый файл.
    В самой общей форме поток можно назвать логическим интерфейсом с файлом. С++- определение термина "файл" можно отнести к дисковому файлу, экрану, клавиатуре, порту,
    файлу на магнитной ленте и пр. Хотя файлы отличаются по форме и возможностям, все потоки одинаковы. Достоинство этого подхода (с точки зрения программиста) состоит в том, что одно устройство компьютера может "выглядеть" подобно любому другому. Это значит, что поток обеспечивает интерфейс, согласующийся со всеми устройствами.
    Поток связывается с файлом при выполнении операции открытия файла, а отсоединяется от него с помощью операции закрытия.
    Существует два типа потоков: текстовый и двоичный. Текстовый поток используется для ввода-вывода символов. При этом могут происходить некоторые преобразования символов. Например, при выводе символ новой строки может быть преобразован в последовательность символов: возврата каретки и перехода на новую строку. Поэтому может не быть взаимно-однозначного соответствия между тем, что посылается в поток, и тем, что в действительности записывается в файл. Двоичный поток можно использовать с данными любого типа, причем в этом случае никакого преобразования символов не выполняется, и между тем, что посылается в поток, и тем, что потом реально содержится в файле, существует взаимно-однозначное соответствие.
    Текущая позицияэто место в файле, с которого будет выполняться следующая
    операция доступа к файлу.
    Говоря о потоках, необходимо понимать, что вкладывается в понятие "текущей позиции". Текущая позиция — это место в файле, с которого будет выполняться следующая операция доступа к файлу. Например, если длина файла равна 100 байт, и известно, что уже прочитана половина этого файла, то следующая операция чтения произойдет на байте 50,
    который в данном случае и является текущей позицией.
    Итак, в языке C++ механизм ввода-вывода функционирует с использованием логического интерфейса, именуемого потоком. Все потоки имеют аналогичные свойства, которые позволяют выполнять одинаковые функции ввода-вывода, независимо от того, с файлом какого типа существует связь. Под файлом понимается реальное физическое устройство,
    которое содержит данные. Если файлы различаются между собой, то потоки — нет.
    (Конечно, некоторые устройства могут не поддерживать все операции ввода-вывода,
    например операции с произвольной выборкой, поэтому и связанные с ними потоки тоже не будут поддерживать эти операции.)
    Встроенные С++-потоки
    В C++ содержится ряд встроенных потоков (cin, cout, cerr и clog), которые автоматически открываются, как только программа начинает выполняться. Как вы знаете,
    cin — это стандартный входной, а cout — стандартный выходной поток. Потоки cerr и clog
    (они предназначены для вывода информации об ошибках) также связаны со стандартным выводом данных. Разница между ними состоит в том, что поток clog буферизирован, а поток
    cerr — нет. Это означает, что любые выходные данные, посланные в поток cerr, будут немедленно выведены, а при использовании потока clog данные сначала записываются в буфер, и реальный их вывод происходит только тогда, когда буфер полностью заполняется.

    Обычно потоки cerr и clog используются для записи информации об отладке или ошибках.
    В C++ также предусмотрены двухбайтовые (16-битовые) символьные версии стандартных потоков, именуемые wcin, wcout, wcerr и wclog. Они предназначены для поддержки таких языков, как китайский, для представления которых требуются большие символьные наборы. В этой книге двухбайтовые стандартные потоки не используются.
    По умолчанию стандартные С++-потоки связываются с консолью, но программным способом их можно перенаправить на другие устройства или файлы. Перенаправление может также выполнить операционная система.
    Классы потоков
    Как вы узнали в главе 2, С++-система ввода-вывода использует заголовок , в котором для поддержки операций ввода-вывода определена довольно сложная иерархия классов. Эта иерархия начинается с системы шаблонных классов. Как отмечалось в главе 16,
    шаблонный класс определяет форму, не задавая в полном объеме данные, которые он должен обрабатывать. Имея шаблонный класс, можно создавать его конкретные экземпляры. Для библиотеки ввода-вывода стандарт C++ создает две специализации шаблонных классов: одну для 8-, а другую для 16-битовых ("широких") символов. В этой книге описываются классы только для 8-битовых символов, поскольку они используются гораздо чаще.
    С++-система ввода-вывода построена на двух связанных, но различных иерархиях шаблонных классов. Первая выведена из класса низкоуровневого ввода-вывода
    basic_streambuf. Этот класс поддерживает базовые низкоуровневые операции ввода и вывода и обеспечивает поддержку для всей С++-системы ввода-вывода. Если вы не собираетесь заниматься программированием специализированных операций ввода-вывода, то вам вряд ли придется использовать напрямую класс basic_streambuf. Иерархия классов, с которой С
    ++-программистам наверняка предстоит работать вплотную, выведена из класса basic_ios.
    Это — класс высокоуровневого ввода-вывода, который обеспечивает форматирование,
    контроль ошибок и предоставляет статусную информацию, связанную с потоками ввода- вывода. (Класс basic_ios выведен из класса ios_base, который определяет ряд нешаблонных свойств, используемых классом basic_ios.) Класс basic_ios используется в качестве базового для нескольких производных классов, включая классы basic_istream, basic_ostream и
    basic_iostream. Эти классы используются для создания потоков, предназначенных для ввода
    данных, вывода и ввода-вывода соответственно.
    Как упоминалось выше, библиотека ввода-вывода создает две специализированные иерархии шаблонных классов: одну для 8-, а другую для 16-битовых символов. Ниже приводится список имен шаблонных классов и соответствующих им "символьных" версий.

    В остальной части этой книги используются имена символьных классов, поскольку именно они применяются в программах. Те же имена используются и старой библиотекой ввода-вывода. Вот поэтому старая и новая библиотеки совместимы на уровне исходного кода.
    И еще: класс ios содержит множество функций-членов и переменных, которые управляют основными операциями над потоками или отслеживают результаты их выполнения. Поэтому имя класса ios будет употребляться в этой книге довольно часто. И
    помните: если включить в программу заголовок , она будет иметь доступ к этому важному классу.
    Перегрузка операторов ввода-вывода
    В примерах из предыдущих глав при необходимости выполнить операцию ввода или вывода данных, связанных с классом, создавались функции-члены, назначение которых и состояло лишь в том, чтобы ввести или вывести эти данные. Несмотря на то что в самом этом решении нет ничего неправильного, в C++ предусмотрен более удачный способ выполнения операций ввода-вывода "классовых" данных: путем перегрузки операторов ввода-вывода "<<" и ">>".
    Оператор "<<" выводит информацию в поток, а оператор ">>" вводит информацию из
    потока.
    В языке C++ оператор "<<" называется оператором вывода или вставки, поскольку он вставляет символы в поток. Аналогично оператор ">>" называется оператором ввода или
    извлечения, поскольку он извлекает символы из потока.
    Как вы знаете, операторы ввода-вывода уже перегружены (в заголовке ),
    чтобы они могли выполнять операции потокового ввода или вывода данных любых встроенных С++-типов. Здесь вы узнаете, как определить эти операторы для собственных классов.
    Создание перегруженных операторов вывода
    В качестве простого примера рассмотрим создание оператора вывода для следующей версии класса three_d.
    class three_d {
    public:
    int x, у, z; // 3-мерные координаты three_d(int a, int b, int с) { x = a; у = b; z = c; }
    };
    Чтобы создать операторную функцию вывода для объектов типа three_d, необходимо перегрузить оператор "<<". Вот один из возможных способов.
    /* Отображение координат X, Y, Z (оператор вывода для класса three_d).
    */
    ostream &operator<<(ostream &stream, three_d obj)
    {
    stream << obj.x << ", ";
    stream << obj.у << ", ";
    stream << obj.z << "\n";
    return stream; // возвращает параметр stream
    }
    Рассмотрим внимательно эту функцию, поскольку ее содержимое характерно для многих функций вывода данных. Во-первых, отметьте, что согласно объявлению она возвращает ссылку на объект типа ostream. Это позволяет несколько операторов вывода объединить в одном составном выражении. Затем обратите внимание на то, что эта функция имеет два параметра. Первый представляет собой ссылку на поток, который используется в левой части оператора. Вторым является объект, который стоит в правой части этого оператора.
    (При необходимости второй параметр также может иметь тип ссылки на объект.) Само тело функции состоит из инструкций вывода трех значений координат, содержащихся в объекте типа three_d, и инструкции возврата потока stream.
    Перед вами короткая программа, в которой демонстрируется использование оператора вывода.
    // Использование перегруженного оператора вывода.
    #include
    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 << "\n";
    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 << "\n";
    return stream; // возвращает параметр stream
    }
    В этой версии функции жестко закодирован поток cout. Это ограничивает круг ситуаций,
    в которых ее можно использовать. Помните, что оператор "<<" можно применить к любому потоку и что поток, который использован в "<<"-выражении, передается параметру stream.
    Следовательно, вы должны передавать функции поток, который корректно работает во всех случаях. Только так можно создать функцию вывода данных, которая подойдет для использования в любых выражениях ввода-вывода.

    Использование функций-"друзей" для перегрузки операторов вывода
    В предыдущей программе перегруженная функция вывода не была определена как член класса three_d. В действительности ни функция вывода, ни функция ввода не могут быть членами класса. Дело здесь вот в чем. Если операторная функция является членом класса,
    левый операнд (неявно передаваемый с помощью указателя this) должен быть объектом класса, который сгенерировал обращение к этой операторной функции. И это изменить нельзя. Однако при перегрузке операторов вывода левый операнд должен быть потоком, а правый — объектом класса, данные которого подлежат выводу. Следовательно,
    перегруженные операторы вывода не могут быть функциями-членами.
    В связи с тем, что операторные функции вывода не должны быть членами класса, для которого они определяются, возникает серьезный вопрос: как перегруженный оператор вывода может получить доступ к закрытым элементам класса? В предыдущей программе переменные х, у z были определены как открытые, и поэтому оператор вывода без проблем мог получить к ним доступ. Но ведь сокрытие данных — важная часть объектно- ориентированного программирования, и требовать, чтобы все данные были открытыми,
    попросту нелогично. Однако существует решение и для этой проблемы: оператор вывода можно сделать "другом" класса. Если функция является "другом" некоторого класса, то она получает легальный доступ к его private-данным. Как можно объявить "другом" класса перегруженную функцию вывода, покажем на примере класса three_d.
    // Использование "дружбы" для перегрузки оператора "<<"
    #include
    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 << "\n";
    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
    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 << "\n";
    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, тело этой функции может содержать все, что вы считаете нужным в нее включить.
    Но логичнее использовать операторы ввода все же по прямому назначению, т.е. для выполнения операций ввода.
    1   ...   17   18   19   20   21   22   23   24   ...   33


    написать администратору сайта